From 90c16b61d585daa5c02d8227b5484647a08998ec Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 24 Sep 2022 22:40:37 -0600 Subject: [PATCH 001/172] [api-docs] Daily api_docs build (#141727) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_common.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- api_docs/kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- api_docs/kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- api_docs/kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- api_docs/kbn_core_application_browser_internal.mdx | 2 +- api_docs/kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- api_docs/kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- api_docs/kbn_core_deprecations_browser_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- api_docs/kbn_core_deprecations_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_client_server_internal.mdx | 2 +- api_docs/kbn_core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- api_docs/kbn_core_elasticsearch_server_internal.mdx | 2 +- api_docs/kbn_core_elasticsearch_server_mocks.mdx | 2 +- api_docs/kbn_core_environment_server_internal.mdx | 2 +- api_docs/kbn_core_environment_server_mocks.mdx | 2 +- api_docs/kbn_core_execution_context_browser.mdx | 2 +- api_docs/kbn_core_execution_context_browser_internal.mdx | 2 +- api_docs/kbn_core_execution_context_browser_mocks.mdx | 2 +- api_docs/kbn_core_execution_context_common.mdx | 2 +- api_docs/kbn_core_execution_context_server.mdx | 2 +- api_docs/kbn_core_execution_context_server_internal.mdx | 2 +- api_docs/kbn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- api_docs/kbn_core_http_context_server_mocks.mdx | 2 +- api_docs/kbn_core_http_router_server_internal.mdx | 2 +- api_docs/kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- api_docs/kbn_core_injected_metadata_browser.mdx | 2 +- api_docs/kbn_core_injected_metadata_browser_mocks.mdx | 2 +- api_docs/kbn_core_integrations_browser_internal.mdx | 2 +- api_docs/kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_collectors_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- api_docs/kbn_core_notifications_browser_internal.mdx | 2 +- api_docs/kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- api_docs/kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_api_browser.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_base_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- api_docs/kbn_core_saved_objects_browser_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- .../kbn_core_saved_objects_import_export_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_migration_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- api_docs/kbn_core_saved_objects_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- api_docs/kbn_core_test_helpers_deprecations_getters.mdx | 2 +- api_docs/kbn_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- api_docs/kbn_core_ui_settings_browser_internal.mdx | 2 +- api_docs/kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- api_docs/kbn_core_ui_settings_server_internal.mdx | 2 +- api_docs/kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- api_docs/kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- api_docs/kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_alerting_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- api_docs/kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_user_profile_components.mdx | 2 +- api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_analytics_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_template.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_config.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- api_docs/kbn_shared_ux_prompt_no_data_views.mdx | 2 +- api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 402 files changed, 402 insertions(+), 402 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 9422af0f654d9..523b35e23d824 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index b3f310db1363a..02771ba710fdb 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index dd1f0725156f2..78517d5274832 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index e2952bd555eb2..afd108a647b06 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 0d5e1a8a16013..5fbf5d33e22b4 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 817d1a9d9e349..74061357b4cbd 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index b9b63b1d6b44c..569f291ea723a 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index a1960b0026238..18d82643dcb53 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 32c090dbfa1a1..d41012a6f12fe 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 3da28b0bad7fd..c0a2c46b1b66a 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index bc45db69cc1a6..4967212b2eeb3 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 8535c5a7082f3..dd405fbc6b061 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 94352a497b7cf..16b214cb7b906 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 11e5a000bc0f7..e32c64956870e 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 1cdddefa3f573..3b78b88b19c4d 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 65cff6e9c9ae0..cedf9b7d511e9 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index b1923914ba36b..39cafd746a810 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 4c0545dc4bbe4..89233ddb6400e 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 98f3e7141a10c..c64ee09e24b43 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index e0f8e807ffb40..6d299e810d836 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index aefb0e297c65b..5f5aaa8ba34d7 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 52115cd39ca0c..595a332bf85e2 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 511552542ef48..209ea2c67f004 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 21cd1c4f73ce1..57a46f7cd2e44 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index d95532f82b1c6..a31ee4977d84d 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 7355bdbd438d9..0ae5f537a2619 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index c5b8caa7cb8f8..4a630e692306c 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index c724b31d268da..468569eee703d 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index dbf60f18b4e07..1378177a0371c 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 43aad9198df22..ea2c130dcfa62 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index ee4b58ad6a39b..4a13c3ab2005d 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 7cb6b1cef229c..7e16d002791df 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index efddbbc3916ec..fddba79ee0cbf 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index eb6a4725d55d1..5e2cc5c286307 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 7318d71853f18..aa413dfa56c63 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 3466c99aba602..2bcf83455dc82 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 8600b14ccd0c7..adfa9b15c4fc2 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index eaeb120c8aca9..6fdc02a6fbf6a 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index d431228998ee4..df8ed9e77eb55 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 3b14ac3b38cdf..06645e7da6957 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index e28ca58c98d8a..143aaa1174fbe 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 9ed9d2a67bfd7..6515294c5cbec 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 62f645598cc3e..99dd7bf7ae22f 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index e14697578e3a5..070d1b553b82e 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 967f0e15bde38..0226cb43454cb 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 333cbcb1f5890..a47392f324ba4 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index f2b118c99a7b4..8cadd213c69f9 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 7e900092940cb..49ec9f2e61066 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 19427e2c1e8af..114c03b9e79b0 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 6d7c6e6c85cd5..717793915fa53 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index e54835f28d762..8e1f7243806e1 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 5b5b9430f6023..53efbbcc4ba43 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index bbf844476905b..5e19bf62d9c37 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 4fadad12b2dea..dbf9e7dcbc784 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 972eaff242d68..a213eafbca18e 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 0225740e0976c..7baec891889c9 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 5a7a187da833f..0e9f1c5eddf0b 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 88478910d3bdb..d716e0e689afc 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 16f908b5a6559..4995f922f7f4c 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index df42a342ea190..025212c384a01 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 7036c153af9c9..e0be93ea14888 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index ea551b71ca3cc..3b299ecc947c0 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index cb31945f45c53..8bd0909dcec73 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 500587900e2a9..bc7b7211340ec 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 60945a0fa95fe..36ec64d62f217 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 0a17336a71a73..3c2acd0a02490 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 5de6f23e953e9..d7d5c9f1af97c 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 6b9ee23b0ad29..146934c4032ef 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 448c51ad25606..7eceec51a126f 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 9ef8150e1696a..815248b6e5a98 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 7c1ea2975b24e..1be78d3e1249a 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index f704647f7e7e3..815613f597850 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index b088520ed67c6..ab13859c2bd93 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 6b61b9bf71499..50c8360def2c3 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 78d07f946255c..acc569f0afe58 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index e5db75837a62f..2e449fd6e6971 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 90382d27113c7..e7f8c71e35d4a 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 73d11b590be67..db5ba070fd391 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 1c3531004269a..96514bce36cc2 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 3711e212e5708..41f1cd526a714 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 48eec0e7ae00b..4b1c164b8fe7d 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 8da4d01493b55..66e3cb4da20e9 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 4d3ed1b971d81..621c7e06cd551 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 3383ae0297690..c908381f1ceb7 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index c36dabc793b02..41da78c5c26ff 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 684ae729f0b53..f99ec0b85b0cf 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 56327b337a7c2..6ed162bf50134 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index b999b7fba09cc..80c03b181fa76 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 89dccaa44cab4..22aed65af01ad 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 492cfc1af6639..26e9a038991fd 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index e4d47a7a5e993..8f90f9b5b532b 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 3963d88ebe0dc..dbde85b393f48 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 23c466871d930..de6701208b662 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index b41585c2f4881..002ea7bdbd34a 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 30b966682e32d..c4113b75cda09 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 0a67e2a249ecd..7b055b05da28a 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index d54c96d96a78d..71739baa9530f 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 15f452f602f3a..435307c93ed8a 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index d2ca1e1cdf64f..40ccfba60717c 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 4e5ea9b96abf5..56d3a1b6a7bdf 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 6180696154344..b3dbcfa18718f 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index d2691bfa66636..3a715743b9c2d 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index a97c1b99cebe2..3f265149c8a4d 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index d0afb23fc2964..96ea8c31cb5d2 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 51d7ca9f8ab89..6da6157c4f428 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index d7cdf10b971d5..a3817f0ba4bb2 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 46d148fd00f47..0c912ef719b58 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 8a2759159936f..76d2dce6f4a58 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 33ab3d655c3d6..c4da7f42a9b6a 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 8c0b095a8af9f..d5683e3676d21 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 5eb8c9868d0e0..8daaeb8ec6b2f 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 1d58a5f7e61aa..edbd2d5f9db13 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index ed179dfdb1276..5448ba79f5eb4 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 759a640237749..b0028b66dc4f4 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index f8bd9caed5b1c..3b4bf58a41865 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 5ddab954f056f..a061bcf68e9d2 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index f81419be79cb5..c750a00f4d914 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 615f4b126dc8c..8883a090503fc 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 6d9794ef57332..5a918149da0d2 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index e8fb9f138c2ad..2294c581fc08a 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 3a4926f8f7749..6ba8520a69357 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index a35bb9c60290f..2066531a80f54 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 9afc41514e5e6..9cf6037f52d34 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 3255e485c9b0a..8b9e0744b12b6 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 1063fef175e9a..f5f381875181d 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index ff9a0baaa5d59..1e0503d76e617 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index d6435555e96e6..db201e141125e 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 4bb98103a64d4..73cf9071a46c8 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 7ad9e23acb721..d93f36be4f26b 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index d9f63f24452cb..894ea23dfbaf2 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index d2e0eac9b9816..32451ffcc336b 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 9a2fe27257295..f546db43e683a 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 44333efae72e1..91e5ebbd7309d 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index cc8aaa60e702a..c9bb6157f45f7 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 5d3c46ebc068a..150d2ca20bc58 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index a1fb8bd905ca0..4f2698107ada9 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 67c3abd06ea84..fd22c33c41cbb 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index db91209848963..76bfac7116c66 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 788b1d01bbec3..5d7d72337bb98 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index ce5add93ded9b..2a4e059eb400e 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index c4622e5ed407b..172ab855e694c 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 00ed1200e185d..489cff811d669 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index bacf194851ae1..bf157c200ce03 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 6cc3300153374..13f986dbec881 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 245ed8af6bab0..92ac7f605836f 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 4f2adfbc0e897..9ca9a084ba5e0 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index e3ec06a3a639d..92bfeb9b108d7 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 45107aab5e05d..0efb8657cdb73 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 5c288a131070c..d4ffb27a76d21 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 0a8cb658945e9..742c1d577c81f 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index a8401fce4884f..5a356ae85b8bd 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 93cb07e285c54..48a1989dcc3b3 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 0e03b322e5fe7..3f7cdde8d96a2 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index d45eca1ec9b95..66d154acee4bc 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 2f38c40e7b1d0..cdabd61bb2ea9 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 4b103d193e7dc..3137892277515 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index f4f5febf27a39..28e75101f481c 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 138e613d2db1a..d38a9aeac44b6 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index ec00287941891..a43b2021ac80e 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index e4e1d4c03aed4..b63afb2c8bc4a 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 228c1de238119..78a19bfcbb4cf 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 341910977ce43..c9fc3751c817a 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 74084f769426b..58b449bbb0903 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index f596b9e2c1cec..50bb050553b94 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index b26dafe15f86e..05041cc6620a5 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 9fc66b3eff5c5..18338c681848f 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index dd7dc366a7f53..5edb77778f853 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index c7a4675a50f26..4f7830c44774e 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index f75d6e2e4df86..e5d888fc4b8c9 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 27eb84bcb629d..49f249175a767 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 931250f276d88..075b26999cf82 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 0ea10d0228459..2a56985bde7ba 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index d6ada13d2b459..ce468a02b7b9f 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 055a033501826..797eb6c4832bd 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index e34899253c5c5..6ae9927e726b0 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 7ef10010ee5db..e0829d8c5e48b 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 1b3e5556d6df3..e9a385345abf4 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 74050c9f387f7..5ef686a17d7d0 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 955f110fe4809..949bcb4eee582 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 84ef476c88624..5158be010c9fd 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 2b5918007517a..59cf9607d0607 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 1d7f7defbc093..e0067b586ef78 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index a7df07f457116..8203928488522 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index f91e8ae1b93d3..56b6f935909cf 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 77eb97cc9c81c..d4fa8ac8e7734 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 26f71de8a5ce5..3cda7f136ae45 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 16b0b1af9e207..3b9bb80712aef 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index a1436d596ee1c..5851e113231a4 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index a28f6bdd8a24d..70990e5669905 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 4961251518500..5040352661740 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 447e54cee040b..67bc99fa66400 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 2a3f0b6aa99dc..4f0f67f6c1bd0 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index a98c1c6e2be22..f9fb31fd71123 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index efb252b353d8d..06ee8559323b1 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 803208ad83bde..10f411fa7535a 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 1593a6563a9f1..87874a45ad8a5 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 4e20ad04423fd..9cc8d3e7222e6 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 9c33ea4c0d532..e40978f69dbd0 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 3cee9dd92656d..3cc063ad12ba9 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 19f37abb20263..67b6ef4c92378 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 81272d53ba6d1..38e1558e81c9e 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index be028a3b85168..8ddbeafefea4d 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index afc1a85e2f556..a2abf1613245a 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 0711b45cc57d1..2396a2d803f53 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 4d73717fa6bbb..1967e1a7a3e72 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index acde498787c65..eebbeb6a3591b 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 1c59293c82fe1..3c687a4b4c14f 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 4436c0815f04a..a22e3dd83bf90 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index afd52adf21d20..e85ecd38c73e7 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index d4b695e741006..56cee1a6b264f 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 58f6cbfeab598..cc8738bf3bba8 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 5f48aa2745570..2569f870e981b 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 793e79b6aae60..79c7170c9e16e 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 3018311b36ca0..860c59b656496 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 0c83d35792645..e69b2dfa3732a 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 3e5fc158db0e8..358c8eccf725f 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 7a4d2e6f76c05..380657f84e9ee 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index ceb4e7510df6c..1a8d086d2112a 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index c3f41e7a93c39..54391475ff7a6 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 009da9871cacb..ab7bde06233e2 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index cee9f5d6574ef..68d2281aaed4b 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index dde6cc13f0d7a..c38d8580810b1 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 51a7bd54317e2..fe87ab8b9957a 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index b34ce13cff481..db68fcedcfeec 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 9f429a934f52e..9fb7459e79644 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index ac7832eee896e..bc958109dc166 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 0f86f2ff4b9f4..afed170bdc182 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 45dd5563f16e7..0a2a92e9380be 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index b04c85e281cf0..7175a58926448 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 88cb75e8f3f22..78c2cb7328bf6 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 616f1cca3ea9d..09e37f77abbaf 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 53bb660a8e7b1..f8e1ac5449558 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index a0e9a2c8017ef..7851f9ebd5f43 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 9411c11c7447d..71d95485f0e0c 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 88df541d3f845..234ff5f238a1b 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 1a5e2446af816..0c320109cb282 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 4894e6741a7cc..571aca20b6bc2 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index bfe263038499e..0bb92aff8e6c6 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 6cc96f29596cb..e8aebf9dc5ee9 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 61e4b8c3d726d..f4c4fe0f873c0 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 84efde87a1fde..72be5664f66b5 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 104726e50bc58..3e94ed7d2c36f 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index d37093b825fe6..412e3769a945d 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index ee29767cd0d97..101820fe72e2b 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 700389481b7c7..fdb084e6e8a06 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 0ad6ecfa1134f..a680258482c5d 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 859c50588c5d6..cc76730569875 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 8ee94c2d71b0e..0e8d60e55d85c 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index c847655641fb9..690b62a59d23a 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index d9c5b743c7992..0e49ada53277b 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 35c6c024c30aa..ef5b1bdc74ad1 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 4227de2378990..9450cfe017a78 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 39f179d7f006b..c0814cf748b70 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index aefca257ec51d..5347faf751b3d 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index e842c1eb7e347..145ebb771b650 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 3b27866609cf4..371cf829ef910 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 3f639cd9df6de..6d118b7d0fd0e 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 57ce7a27a5c6c..d93d46a651a70 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 699d038a62b36..64e80f2e1fcf4 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 2e93d4df6a7c0..fdee848035e37 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index e54522717c361..4ac44b240e7ed 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 4cf3e42607ebc..11b794f06cea0 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index aa06f117f4ad3..d2643b11052ca 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 07c0bcadb88c2..347d3b807c846 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index c7a1e479d8338..9070910b11a1d 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 849a9f36eb3b5..f0b93c6cfcfad 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index a9ee3347d4153..94aed461ef2bd 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index be22071f32f0f..3b3dedbbe5c82 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index aaac08a4676bb..db2962a213f6a 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 5f065f4241db9..7a459acfd17a1 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 20d2b0fa67d73..679281e14f4bc 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 231bccc342205..49206e6f66018 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index b6eabe236bc52..248a916dcb633 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 497c2c3d7d8bd..35eba02d2850c 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index fbf50097d69a4..32010a03fd439 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 45ef5aade835f..7fd665f44b66a 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index c3bb2e7e2ee49..db671fccebd80 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index f41af821ce9b8..5182b9523c895 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 00d3453ea1564..da7c81fcf36ec 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 7f950925e143c..aded6b0382205 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index a87c153759c2e..e24dcbb2b4068 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index a8a0a5be5ed8f..c6318b785fced 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index ceb9e4a1a4c4b..db4a9ad91636b 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 62afc60aba44b..bdda86287180f 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 24d192fa7cbfa..3d98c7df1451b 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index a46263ee9f1c1..962e3a5686f96 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index cf89e44a80e04..c40e122c91d1f 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 564e869ba0739..4ab343d342b19 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 4af4131d0d5cf..a5b9ff1195371 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 8faaf694df8d7..c09d4b5019bba 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index ac580aab9a27b..411b28aded9f0 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 773e3c7234c27..18a6c2ee1cce6 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 0f940aadb3daa..9300446d93c07 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 1ffc0e131ca1d..982a29f1734fe 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 6aa2d0a90d57c..9286b1b2c1d38 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index cf773f26fba40..9be439a29205c 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 232f4f31d8750..90daba2542ea8 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 59d78c3af89d0..42bb401753001 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 65d2654e0e4c0..d7deacac8eaba 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 3099d81b2efb1..c27df5ad93cda 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 91ebc86264d87..fdc221035eb02 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 4d580254bc41d..a0bf47fbd4205 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 3b09d9851a4ad..78745f607a386 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index cc8e890cebed1..a96fbc2ea13f8 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 85615d64e6b93..2f8e2b9c537ee 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 23769d6634d61..22b9cc2eb3db7 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index b0cae6ebc2b47..d60fc82220f22 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index fdeb9e70af30f..9e0061218aa53 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 4e4d6fc4c43d4..abb5b3ad08690 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index fcfeea3f91e2b..e06043a7d114c 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 9341074e595cf..ebd10357fecf7 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 9f9fb98016df2..734185f7e0b00 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 085818f1a51e5..343de86d179c0 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index e16b35454f5d6..73bb16188960e 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index a8f40a8a4eb4a..db86bf5efb581 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 1a56b36b7ecf7..b74ccb611c937 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index cef4816dbfcb2..6a1ef62935ab5 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 3ce0d27a31aa2..6601133e083d7 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 8c68a064c4eea..3b43690f16346 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 80810336bb2e1..0521780fc4f33 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index dcfdbe0b9b3d9..691c6683c98eb 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index e42d04d4b96da..7aa3822c1b482 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 7ff0d31edaa88..fd937a3285759 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index bee4f19aa6a1b..5cad73de99266 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 89d19b610938a..ff9c5a68d0cc0 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 42b1e58ea7503..933096588e1d8 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index b7d198915a35e..c5bd216c14920 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 52b2243fe3b35..42370f1ad51a5 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 9687cfb2270f9..1b2804875408f 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 02a4c9d1adf55..0e03c797aeccf 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 2b7ea1ef4ae65..9d756b3b67f13 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 1cbdf607979ab..537ebca1c0782 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 60ffe9f9f9cac..5f004f093e7a2 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 5b9aa1fc4b26b..b4ecf5f18cfba 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 25c547b62bd9b..8437ff7ab50f0 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 57912de87ecfc..da9b825c2a89e 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index c5ae4cf492113..61d37414d9ee7 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 51291ac526da1..a3f32d72af37f 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index fe67f8623dad7..f6c86ac164484 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 25315e4aca2c1..7488325f0e017 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 421951a45f4de..36baac3527cef 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 93bf7efb3e546..ab7c60253a2a4 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 1b8da980d454d..5f70812d77aca 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index f1a20952c6833..dd74a2bd59fae 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 021ad1e483ce2..d361b3e693ec0 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 83f556aea3173..da2928df7d6e0 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index cf5c08c4b7ca9..a288fc3ff05ab 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 0da031f9285c5..7ea34dfd044f8 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 80235fb0c5de6..09f552ab7ff86 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index f81af5365ebf2..f7f3067938aec 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 909445d75e7e2..ab630ead2ad2e 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 6624c8da62bc0..078d7dd4533fd 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index b6918294e2a03..f95eeb8fc20de 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 654fe2d108913..905044ecd65a8 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 2bc4d41ac91fa..a1d41a4f28478 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index cdf7e02e91c3f..e951dda64a0f1 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 4ddc32f331d94..e6e152781b3ec 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index d84f83787e909..9503fb4b3d0a7 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 4444520e994a7..23edd86672399 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 15a8248a430dd..525846f648a02 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index b82b505e41344..9d3df81a67b2a 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 85434d08daeba..65b7d51fc81cf 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index fc726fe5227b6..de71f6c7265b3 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index bb71e1eb7e460..101d6360cf674 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index c8f54246304fc..f4d60cf4f0eba 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 8da7ec4e85bfa..a89aa8b61e4ec 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 641107ddb0e5b..d642f2008f58a 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 4dfc5304eeabc..6951295f9c930 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 3d6c984a674d4..1882ec7f9732b 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index c99d1ff617c66..d16b573cf821f 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 3e77d10e0c059..f9510448f8c6d 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 992d92531e643..8c5b8086ed7d4 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index c2e72b56ca6cd..937ada3e43bb1 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 26e060f27c7b3..03bda040c0276 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index f5999857a32d8..837a9ce42f252 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 3fb0b4f960ea8..4b431397b6d5b 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index f111e7af6b6be..9aa758c971d1c 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 8af81d0546f13..f00b7c797983f 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index ef4366af03426..0a34b3c0dfa28 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 3ee232106709f..32751e60c3971 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index d84205e784a76..4d1529bd6c3e4 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index de5cb836726ce..4cf82d47174d3 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 3f536cddf81ed..7f7e3c11b5c03 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index cb2edf03ef9bd..d884ad52a3ebe 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 198157440ac2b..e205515072523 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 7ab32ae9e80e9..18e0b90a4d3f7 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 975909dbd1859..9c432d5ebb04b 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index bcd8790a9ef03..03e67cbb308b6 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 01be62ca3cbc1..1609d1c636462 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 937cfe134fca4..2f6d2e6608817 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 22f7f14e7b87e..42a69bf7bc36e 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index a989426eb17c7..930321e1fdd7c 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 1902e6787e057..bcaedda27df2d 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 0c2173d8562a2..24b46be19109c 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 00f0bedd3b44c..29ebc2881a651 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 2af9256f17d00..83fedc7cf42d1 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 6f9803ae692e7..29838f1d2c21f 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index f1b4cd2f37124..5e564b8225047 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 3ac63c8449032..100c1f9795e34 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index f1167efa39ac4..9ec4c0b6cc24b 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index a89e736598144..7542536a104e5 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 524429916331f..4bae5ab65b32b 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-09-24 +date: 2022-09-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 824ff8189cecb31f7350c877f68d1fa016bf923a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:38:11 -0600 Subject: [PATCH 002/172] [api-docs] Daily api_docs build (#141730) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_common.mdx | 2 +- api_docs/kbn_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- api_docs/kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- api_docs/kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- api_docs/kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- api_docs/kbn_core_application_browser_internal.mdx | 2 +- api_docs/kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- api_docs/kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- api_docs/kbn_core_deprecations_browser_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- api_docs/kbn_core_deprecations_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_client_server_internal.mdx | 2 +- api_docs/kbn_core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- api_docs/kbn_core_elasticsearch_server_internal.mdx | 2 +- api_docs/kbn_core_elasticsearch_server_mocks.mdx | 2 +- api_docs/kbn_core_environment_server_internal.mdx | 2 +- api_docs/kbn_core_environment_server_mocks.mdx | 2 +- api_docs/kbn_core_execution_context_browser.mdx | 2 +- api_docs/kbn_core_execution_context_browser_internal.mdx | 2 +- api_docs/kbn_core_execution_context_browser_mocks.mdx | 2 +- api_docs/kbn_core_execution_context_common.mdx | 2 +- api_docs/kbn_core_execution_context_server.mdx | 2 +- api_docs/kbn_core_execution_context_server_internal.mdx | 2 +- api_docs/kbn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- api_docs/kbn_core_http_context_server_mocks.mdx | 2 +- api_docs/kbn_core_http_router_server_internal.mdx | 2 +- api_docs/kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- api_docs/kbn_core_injected_metadata_browser.mdx | 2 +- api_docs/kbn_core_injected_metadata_browser_mocks.mdx | 2 +- api_docs/kbn_core_integrations_browser_internal.mdx | 2 +- api_docs/kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_collectors_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- api_docs/kbn_core_notifications_browser_internal.mdx | 2 +- api_docs/kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- api_docs/kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_api_browser.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_api_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_base_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- api_docs/kbn_core_saved_objects_browser_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- .../kbn_core_saved_objects_import_export_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_migration_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- api_docs/kbn_core_saved_objects_server_internal.mdx | 2 +- api_docs/kbn_core_saved_objects_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- api_docs/kbn_core_test_helpers_deprecations_getters.mdx | 2 +- api_docs/kbn_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- api_docs/kbn_core_ui_settings_browser_internal.mdx | 2 +- api_docs/kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- api_docs/kbn_core_ui_settings_server_internal.mdx | 2 +- api_docs/kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- api_docs/kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- api_docs/kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_alerting_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- api_docs/kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_user_profile_components.mdx | 2 +- api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_analytics_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_template.mdx | 2 +- api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_config.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- api_docs/kbn_shared_ux_prompt_no_data_views.mdx | 2 +- api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 402 files changed, 402 insertions(+), 402 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 523b35e23d824..ede1ddfd1b090 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 02771ba710fdb..9dd079407ab6c 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 78517d5274832..5bf8f6542ccd1 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index afd108a647b06..d858edbdedf9b 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 5fbf5d33e22b4..0afb756926159 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 74061357b4cbd..fc7599f168896 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 569f291ea723a..c63dca849d910 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 18d82643dcb53..88da1736d5ebd 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index d41012a6f12fe..72b9e7b6e447e 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index c0a2c46b1b66a..3ead0fe10942f 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 4967212b2eeb3..0de2f3b987f74 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index dd405fbc6b061..8c2b0d3bdda55 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 16b214cb7b906..5fa699f9f0bd2 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index e32c64956870e..f8d553ceb4bfe 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 3b78b88b19c4d..67c3f4c2c8711 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.mdx b/api_docs/core.mdx index cedf9b7d511e9..36b55369e5c0a 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 39cafd746a810..8faa9b144c520 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 89233ddb6400e..1f8a54716d56a 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index c64ee09e24b43..10e75395926d1 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 6d299e810d836..59dd44dc07763 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 5f5aaa8ba34d7..c8c053c7b5ca5 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 595a332bf85e2..15b457e57a329 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 209ea2c67f004..1ed2c22bd9856 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 57a46f7cd2e44..2ca0a8d2f0ba2 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index a31ee4977d84d..5277f71f1d3dd 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 0ae5f537a2619..d47a4f9daa55f 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 4a630e692306c..ce2cd5119ddb0 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 468569eee703d..826b95556b7c5 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 1378177a0371c..849c6c1c88fc4 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index ea2c130dcfa62..cebc81eaa7f7e 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 4a13c3ab2005d..b7d3c963abf2b 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 7e16d002791df..2fa566f082091 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index fddba79ee0cbf..01d16f604fd83 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 5e2cc5c286307..ac1db5c87cca3 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index aa413dfa56c63..74f016f3f94c8 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 2bcf83455dc82..6e5dd4305864a 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index adfa9b15c4fc2..0df7233b53270 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 6fdc02a6fbf6a..50d60e1a1f2c9 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index df8ed9e77eb55..73847652b7034 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 06645e7da6957..0fbe81013238d 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 143aaa1174fbe..54c128a3a1609 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 6515294c5cbec..cf24875c1242d 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 99dd7bf7ae22f..45b092ac58bdf 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 070d1b553b82e..2c755fa2fc81f 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 0226cb43454cb..8b20c473d5514 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index a47392f324ba4..bce9a08357803 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 8cadd213c69f9..cdc56d029f4b0 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 49ec9f2e61066..5b04089ad0f1a 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 114c03b9e79b0..1a25a412c39d0 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 717793915fa53..a56fc83dd26a3 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 8e1f7243806e1..120617e54501e 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 53efbbcc4ba43..444c05f6ceb5a 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 5e19bf62d9c37..213d1edd926fd 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index dbf9e7dcbc784..2d96611dfeb39 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index a213eafbca18e..d98963fe4d66a 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 7baec891889c9..36a7e6d4685c1 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 0e9f1c5eddf0b..044c6e58328ae 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index d716e0e689afc..7b6e21054a85e 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 4995f922f7f4c..374d04b3e52e5 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 025212c384a01..6c708f5ad470c 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index e0be93ea14888..604c7acbae0fd 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 3b299ecc947c0..68678af8f9ee5 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 8bd0909dcec73..1621311ffc7df 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index bc7b7211340ec..407e7732ad947 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 36ec64d62f217..39d7876c10861 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 3c2acd0a02490..a4aaec9817ae3 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index d7d5c9f1af97c..a3fecbf61097d 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 146934c4032ef..10ea930d88d01 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 7eceec51a126f..f15645592bed5 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 815248b6e5a98..18c46827b5775 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 1be78d3e1249a..d6d2fc6b2ce87 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 815613f597850..0c6d93303734f 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index ab13859c2bd93..fb40c3a6d1ebb 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 50c8360def2c3..bb08718097b9f 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index acc569f0afe58..a2a61c275f4e5 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 2e449fd6e6971..baf43cc906d1c 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index e7f8c71e35d4a..287bfd2ff5d81 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index db5ba070fd391..8d826cc0958c1 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 96514bce36cc2..0b76528343988 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 41f1cd526a714..2cbf87044cd77 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 4b1c164b8fe7d..234d6104f601c 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 66e3cb4da20e9..46f49b9660db4 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 621c7e06cd551..1459132fe2c97 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index c908381f1ceb7..68990474b42c1 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 41da78c5c26ff..24a14d2aef1c2 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index f99ec0b85b0cf..c589ecba7e2df 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 6ed162bf50134..fed58f168d8be 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 80c03b181fa76..08c49c3eb81aa 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 22aed65af01ad..92b4083a9bb43 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 26e9a038991fd..805071e48d605 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 8f90f9b5b532b..ab69cd4d6a69f 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index dbde85b393f48..697f32d5a32c1 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index de6701208b662..909e9933df071 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 002ea7bdbd34a..d89fb32724da4 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index c4113b75cda09..00ec4b5ef0b87 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 7b055b05da28a..6d43aaea5d4a8 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 71739baa9530f..ca795d6887d01 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 435307c93ed8a..feb8249b263af 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 40ccfba60717c..6353b853b75da 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 56d3a1b6a7bdf..4ccd910e9c854 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index b3dbcfa18718f..57ca826ee2790 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 3a715743b9c2d..6edee369dba21 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 3f265149c8a4d..eafdf4b402b44 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 96ea8c31cb5d2..cc36f15b1ebc3 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 6da6157c4f428..96b693c7772ed 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index a3817f0ba4bb2..0bdb1a2fd6024 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 0c912ef719b58..3a8a11486b91d 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 76d2dce6f4a58..73fa79e82d723 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index c4da7f42a9b6a..83cb7a3a226b7 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index d5683e3676d21..db2f37634a36a 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 8daaeb8ec6b2f..6a9175ac9e6d8 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index edbd2d5f9db13..6791dfa422f31 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 5448ba79f5eb4..7ddb5fa99580a 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index b0028b66dc4f4..b282941a27a0e 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 3b4bf58a41865..aa9990a0b855c 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index a061bcf68e9d2..cd0ed8c810ef9 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index c750a00f4d914..daa193327e0b1 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 8883a090503fc..156fdda6dc807 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 5a918149da0d2..b093eedb0c3a9 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 2294c581fc08a..ad30901f82030 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 6ba8520a69357..9d57d94f5a07c 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 2066531a80f54..1e8cf6217f00c 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 9cf6037f52d34..d0f14840512f3 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 8b9e0744b12b6..2bc0611b6ba88 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index f5f381875181d..bf2faaedb09e5 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 1e0503d76e617..2b10ea2601b1e 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index db201e141125e..c3b9a1bbceb2d 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 73cf9071a46c8..137bb7f40b9d6 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index d93f36be4f26b..b1ec099e96222 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 894ea23dfbaf2..aabccea0ba2d9 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 32451ffcc336b..32511431d67ed 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index f546db43e683a..b428f24f8a449 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 91e5ebbd7309d..608f4b7dfd1d8 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index c9bb6157f45f7..154d3ddd0c6f7 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 150d2ca20bc58..4c8d0fe979c03 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 4f2698107ada9..865624983d3a9 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index fd22c33c41cbb..85b0cd0b15092 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 76bfac7116c66..d9a881ae38e33 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 5d7d72337bb98..19733f115b02c 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 2a4e059eb400e..2d09cd0aedb2b 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 172ab855e694c..3cc3aee2a0d6e 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 489cff811d669..13da3a5ec6169 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index bf157c200ce03..d7651228361cf 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 13f986dbec881..9e013dc8fe0e7 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 92ac7f605836f..2a05b8a951bdd 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 9ca9a084ba5e0..5d4f4c33489ac 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 92bfeb9b108d7..223d1f48f38d7 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 0efb8657cdb73..394b7517d61f8 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index d4ffb27a76d21..9501a0ec85e8a 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 742c1d577c81f..5c499f63e4164 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 5a356ae85b8bd..5e53a8611610d 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 48a1989dcc3b3..aaaaa91454760 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 3f7cdde8d96a2..a516c58bfc4c7 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 66d154acee4bc..e11acda897e25 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index cdabd61bb2ea9..bd3eae858db51 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 3137892277515..c8dfa3b6b28a6 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 28e75101f481c..5a7f145e9f9f8 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index d38a9aeac44b6..69a77c8c4f96f 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index a43b2021ac80e..62c5882d56a6a 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index b63afb2c8bc4a..a3850e7f23e98 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 78a19bfcbb4cf..093ac2d0c1a13 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index c9fc3751c817a..d9e591359ab58 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 58b449bbb0903..51db906dd4743 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 50bb050553b94..86cc637cdf6a8 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 05041cc6620a5..c7464858aae94 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 18338c681848f..eb3c5ebe3c362 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 5edb77778f853..1be122dec707e 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 4f7830c44774e..6af293e6fdae8 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index e5d888fc4b8c9..bf82eb7970b55 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 49f249175a767..a696921789baf 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 075b26999cf82..b69a8a4a78e95 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 2a56985bde7ba..d6c392f44c42f 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index ce468a02b7b9f..ce324b10707bb 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 797eb6c4832bd..e92985c2847d0 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 6ae9927e726b0..b9d2d41ad3c6f 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index e0829d8c5e48b..1b3c4b7c97006 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index e9a385345abf4..c6c249f893682 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 5ef686a17d7d0..adfde3eb57935 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 949bcb4eee582..e5b665a749de4 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 5158be010c9fd..b9857a22a4aa5 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 59cf9607d0607..d9d2d411504ec 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index e0067b586ef78..16067815b76ab 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 8203928488522..ee51fe5d501b1 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 56b6f935909cf..2621608fb4385 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index d4fa8ac8e7734..95e59f965436e 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 3cda7f136ae45..47b96c57a4d99 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 3b9bb80712aef..eb621d99fa2e3 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 5851e113231a4..de1c53c1af13d 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 70990e5669905..bd3799e8d44b0 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 5040352661740..bfae0d9378d37 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 67bc99fa66400..ebb26601a4699 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 4f0f67f6c1bd0..09baa5daf0aa1 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index f9fb31fd71123..fcaebe64cd996 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 06ee8559323b1..0fde59191c733 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 10f411fa7535a..ff0a3055cf443 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 87874a45ad8a5..3b4570f7c036b 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 9cc8d3e7222e6..a76df96a31831 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index e40978f69dbd0..c70c8ca947e65 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 3cc063ad12ba9..66e637f6c3f6a 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 67b6ef4c92378..ba2752d9b3405 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 38e1558e81c9e..26a690d05280a 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 8ddbeafefea4d..3260ae2e31a22 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index a2abf1613245a..441ed1b3c2600 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 2396a2d803f53..cd33f1d1f0502 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 1967e1a7a3e72..06e62aeef187e 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index eebbeb6a3591b..7151aacf2084d 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 3c687a4b4c14f..354e4d93b9370 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index a22e3dd83bf90..d5992e345f9b5 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index e85ecd38c73e7..d1e1d00ed8ed0 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 56cee1a6b264f..93528eb3bfbf1 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index cc8738bf3bba8..6ba05ca53fd8a 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 2569f870e981b..6275d70c17032 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 79c7170c9e16e..625810a6cc773 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 860c59b656496..cd3eda1a2203b 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index e69b2dfa3732a..2c9f352ccd8d4 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 358c8eccf725f..1e513b3ad0f7e 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 380657f84e9ee..dd9cb93ed4e4e 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 1a8d086d2112a..24945054119c8 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 54391475ff7a6..b3a148ab80f22 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index ab7bde06233e2..d492abb2efc12 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 68d2281aaed4b..a01955f4262bf 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index c38d8580810b1..a33d80bd4cd2f 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index fe87ab8b9957a..50a975d51e964 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index db68fcedcfeec..7a7927ec2f4ae 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 9fb7459e79644..03d3a1246b3ce 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index bc958109dc166..8c0d3ce762be5 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index afed170bdc182..d1722a8bf5614 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 0a2a92e9380be..6aefed8871217 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 7175a58926448..c1b894325cd54 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 78c2cb7328bf6..80533016c1c97 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 09e37f77abbaf..3258c848c7d36 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index f8e1ac5449558..61fbb56f1a171 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 7851f9ebd5f43..78e17c86e33d3 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 71d95485f0e0c..b2bbf6fdc00b0 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 234ff5f238a1b..a7e2be8113fac 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 0c320109cb282..572d41cd16fa3 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 571aca20b6bc2..3ab838c7557e4 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 0bb92aff8e6c6..613b02bd58925 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index e8aebf9dc5ee9..2e71f587b78f1 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index f4c4fe0f873c0..6354505d65347 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 72be5664f66b5..9c7b4486fe151 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 3e94ed7d2c36f..85e406eb02adb 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 412e3769a945d..a0c6fe024ffe7 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 101820fe72e2b..ece22c3ff7096 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index fdb084e6e8a06..91b1a139c1a9c 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index a680258482c5d..0f7cef5022e7c 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index cc76730569875..263994dcecb74 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 0e8d60e55d85c..c4721c56b46c4 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 690b62a59d23a..428a6a2fd3411 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 0e49ada53277b..1e3755ae194ca 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index ef5b1bdc74ad1..0c90d5d563dfe 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 9450cfe017a78..37eb518ebfb80 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index c0814cf748b70..a8348f70822d4 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 5347faf751b3d..10fae76d04b1e 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 145ebb771b650..7ff3186afb099 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 371cf829ef910..34569da5d7028 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 6d118b7d0fd0e..8e29bd95f0456 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index d93d46a651a70..9e17319c10337 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 64e80f2e1fcf4..cceee4bc2eb2e 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index fdee848035e37..78eda05c5b44f 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 4ac44b240e7ed..d197afe74668f 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 11b794f06cea0..abf0f29de3277 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index d2643b11052ca..52929cddf140f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 347d3b807c846..6a156288e0d69 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 9070910b11a1d..053dfb0beb5d5 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index f0b93c6cfcfad..86d5c3669cd80 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 94aed461ef2bd..cf2e35c77d2a7 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 3b3dedbbe5c82..125bda46f869c 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index db2962a213f6a..b950dc5c5fe54 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 7a459acfd17a1..eca63370271b2 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 679281e14f4bc..08f9cfb7699d9 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 49206e6f66018..771ef4c3664dd 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 248a916dcb633..bc9e95fce1374 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 35eba02d2850c..2d5f008c026bb 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 32010a03fd439..0638d5f8bf083 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 7fd665f44b66a..b1a1a3fc64da3 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index db671fccebd80..b03d18fec4ced 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 5182b9523c895..7c360b9333e54 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index da7c81fcf36ec..f18f2b1aead14 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index aded6b0382205..b7433466578b1 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index e24dcbb2b4068..2654598d4dcc9 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index c6318b785fced..91475331ebad8 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index db4a9ad91636b..79adceb20aeae 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index bdda86287180f..a5bb0c13967e9 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 3d98c7df1451b..c0b27f428d0b7 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 962e3a5686f96..1c193a552796d 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index c40e122c91d1f..b521420154f80 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 4ab343d342b19..f5eb308795aca 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index a5b9ff1195371..42a0beca9e7ea 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index c09d4b5019bba..2df8ddd83c85d 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 411b28aded9f0..b305a644f9271 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 18a6c2ee1cce6..f0c440f375743 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 9300446d93c07..82f90f66491df 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 982a29f1734fe..bccf44bcdb7d1 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 9286b1b2c1d38..1de00675c4406 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 9be439a29205c..a74f39651be04 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 90daba2542ea8..357f925da370f 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 42bb401753001..8752e5a774370 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index d7deacac8eaba..d939e7321db44 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index c27df5ad93cda..e914e2f8a891c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index fdc221035eb02..e193d62f4ee6b 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index a0bf47fbd4205..dc7bc516665c5 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 78745f607a386..bbd8f3cca0e03 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index a96fbc2ea13f8..ca8e953699da6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 2f8e2b9c537ee..3dfe50049855e 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 22b9cc2eb3db7..f20ef222d9e8e 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index d60fc82220f22..f1dfbd73ce704 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 9e0061218aa53..5bbf41024b26d 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index abb5b3ad08690..cd7b6bfac4e7c 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index e06043a7d114c..b12315abf9fc6 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index ebd10357fecf7..b7e0841ee703d 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 734185f7e0b00..9c495c4aa7743 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 343de86d179c0..7b767e626b7c9 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 73bb16188960e..ab4be4010f901 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index db86bf5efb581..43a7444c0c030 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index b74ccb611c937..020326e9a0c53 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 6a1ef62935ab5..72fb4fa068c7e 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 6601133e083d7..140c6462d2e78 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 3b43690f16346..229feacde942d 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 0521780fc4f33..691a582e60a70 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 691c6683c98eb..e4f3ab2faf109 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 7aa3822c1b482..1bfcb9c72707a 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index fd937a3285759..8910379b7ebe3 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 5cad73de99266..2f874f70f25c7 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index ff9c5a68d0cc0..0d8d08d774ef7 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 933096588e1d8..09f25106acbcf 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index c5bd216c14920..85eb0e283b7a7 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 42370f1ad51a5..65c18d1961171 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 1b2804875408f..c6acb5b0fbc56 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 0e03c797aeccf..5ec4f40c0dae5 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 9d756b3b67f13..ee036d3e7c04b 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 537ebca1c0782..7471c9772caa9 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 5f004f093e7a2..fffdeb850c4c8 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index b4ecf5f18cfba..4873a6d3d2afc 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 8437ff7ab50f0..3f7dbbba241a9 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index da9b825c2a89e..d183dd7fce455 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 61d37414d9ee7..0fbef0861888c 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index a3f32d72af37f..f942edc6beb32 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index f6c86ac164484..0b0b6996ac826 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 7488325f0e017..e8ff8b692628b 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 36baac3527cef..13c0aab065080 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index ab7c60253a2a4..b5d5c37c7c2d4 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 5f70812d77aca..a2e0b0c3660d1 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index dd74a2bd59fae..d9b70e1d9a354 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index d361b3e693ec0..e5a8151f36d80 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index da2928df7d6e0..3ceb6a607ee4f 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index a288fc3ff05ab..b6fa1bd6b9520 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 7ea34dfd044f8..28d4231ae4121 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 09f552ab7ff86..ede6a978b1972 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index f7f3067938aec..33f2325a9d42b 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index ab630ead2ad2e..f52aa4bd15337 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 078d7dd4533fd..8bd637653c617 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index f95eeb8fc20de..cb52176e574d2 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 905044ecd65a8..41f98b3aa5474 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index a1d41a4f28478..9de91bdd316fe 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index e951dda64a0f1..e881b7e1d8d1d 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index e6e152781b3ec..b3f7b8baeea0d 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 9503fb4b3d0a7..84762f6833dfc 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 23edd86672399..5297a1c4dfeab 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 525846f648a02..b48dac4d77ca7 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 9d3df81a67b2a..da2c95cf361b5 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 65b7d51fc81cf..50cc4890609aa 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index de71f6c7265b3..d308998e912cf 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 101d6360cf674..ee3015be49b3b 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index f4d60cf4f0eba..1ab87605dcb7b 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index a89aa8b61e4ec..b978b8ae17757 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index d642f2008f58a..43109981e1bd7 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 6951295f9c930..12f05e5363bc6 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 1882ec7f9732b..b44a921a532cb 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index d16b573cf821f..a9333634f6450 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index f9510448f8c6d..39bb856edb320 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 8c5b8086ed7d4..8614e0abbad77 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 937ada3e43bb1..fbfeb304d9bab 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 03bda040c0276..0941e34da7c6b 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 837a9ce42f252..268f95227b6b2 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 4b431397b6d5b..8710f593cbf06 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 9aa758c971d1c..b8aacfc863f72 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index f00b7c797983f..42d0c24ed78a8 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 0a34b3c0dfa28..b42450f650ec3 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 32751e60c3971..0604fc8cf5698 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 4d1529bd6c3e4..9791e2787c44a 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 4cf82d47174d3..ec5f484f4641c 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 7f7e3c11b5c03..541b2194d3f33 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index d884ad52a3ebe..0905d751b5fa1 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index e205515072523..b3fd83a428f3a 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 18e0b90a4d3f7..16370d522a392 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 9c432d5ebb04b..bdf37515d75f1 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 03e67cbb308b6..62acbd2c862fd 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 1609d1c636462..245f43ae0e848 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 2f6d2e6608817..f6a52d69a1e43 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 42a69bf7bc36e..38b3afeff9e02 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 930321e1fdd7c..5454be9ab6cd1 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index bcaedda27df2d..58b5bd6f2338b 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 24b46be19109c..f90f2436dcc85 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 29ebc2881a651..0238df60ca4da 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 83fedc7cf42d1..03c32627ef241 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 29838f1d2c21f..af604e573f216 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 5e564b8225047..52000bc18ca5e 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 100c1f9795e34..1cff837940b2b 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 9ec4c0b6cc24b..2a1f1ce82cb39 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 7542536a104e5..2d2db30e66054 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 4bae5ab65b32b..81c4149912c26 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-09-25 +date: 2022-09-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 0b97c50dd0cfc725520b8b9ec40d8139bb785f79 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 26 Sep 2022 09:07:25 +0200 Subject: [PATCH 003/172] [Lens] Add field filter to popover (#141582) * add field filter to popover * add test Co-authored-by: Stratoula Kalafateli --- .../lens/public/data_views_service/loader.ts | 1 + .../field_item.test.tsx | 43 +++++++++++++++++-- .../indexpattern_datasource/field_item.tsx | 19 +++++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts index d773f9c5ea509..9ffb99a54f403 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.ts +++ b/x-pack/plugins/lens/public/data_views_service/loader.ts @@ -42,6 +42,7 @@ export function convertDataViewIntoLensIndexPattern( displayName: field.displayName, type: field.type, aggregatable: field.aggregatable, + filterable: field.filterable, searchable: field.searchable, meta: dataView.metaFields.includes(field.name), esTypes: field.esTypes, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index 72624b6c4fa45..2404d53b8f02a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -21,7 +21,7 @@ import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import type { DataView } from '@kbn/data-views-plugin/common'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { loadFieldStats } from '@kbn/unified-field-list-plugin/public/services/field_stats'; import { FieldStats } from '@kbn/unified-field-list-plugin/public'; import { DOCUMENT_FIELD_NAME } from '../../common'; @@ -193,7 +193,11 @@ describe('IndexPattern Field Item', () => { await wrapper.update(); const popoverContent = wrapper.find(EuiPopover).prop('children'); act(() => { - mountWithIntl(popoverContent as ReactElement) + mountWithIntl( + + {popoverContent as ReactElement} + + ) .find('[data-test-subj="lnsFieldListPanelEdit"]') .first() .simulate('click'); @@ -215,12 +219,45 @@ describe('IndexPattern Field Item', () => { await wrapper.update(); const popoverContent = wrapper.find(EuiPopover).prop('children'); expect( - mountWithIntl(popoverContent as ReactElement) + mountWithIntl( + + {popoverContent as ReactElement} + + ) .find('[data-test-subj="lnsFieldListPanelEdit"]') .exists() ).toBeFalsy(); }); + it('should pass add filter callback and pass result to filter manager', async () => { + const field = { + name: 'test', + displayName: 'testLabel', + type: 'string', + aggregatable: true, + searchable: true, + filterable: true, + }; + + const editFieldSpy = jest.fn(); + const wrapper = mountWithIntl( + + ); + await clickField(wrapper, field.name); + await wrapper.update(); + const popoverContent = wrapper.find(EuiPopover).prop('children'); + const instance = mountWithIntl( + + {popoverContent as ReactElement} + + ); + const onAddFilter = instance.find(FieldStats).prop('onAddFilter'); + onAddFilter!(field as DataViewField, 'abc', '+'); + expect(mockedServices.data.query.filterManager.addFilters).toHaveBeenCalledWith([ + expect.objectContaining({ query: { match_phrase: { test: 'abc' } } }), + ]); + }); + it('should request field stats every time the button is clicked', async () => { let resolveFunction: (arg: unknown) => void; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index bae8db55a9d46..29f666a4ff6eb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -29,7 +29,8 @@ import { Filter, Query } from '@kbn/es-query'; import { DataViewField } from '@kbn/data-views-plugin/common'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { FieldStats } from '@kbn/unified-field-list-plugin/public'; +import { AddFieldFilterHandler, FieldStats } from '@kbn/unified-field-list-plugin/public'; +import { generateFilters } from '@kbn/data-plugin/public'; import { DragDrop, DragDropIdentifier } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { DOCUMENT_FIELD_NAME } from '../../common'; @@ -320,6 +321,21 @@ function FieldItemPopoverContents(props: FieldItemProps) { } = props; const services = useKibana().services; + const onAddFilter: AddFieldFilterHandler = useCallback( + (clickedField, values, operation) => { + const filterManager = services.data.query.filterManager; + const newFilters = generateFilters( + filterManager, + clickedField, + values, + operation, + indexPattern + ); + filterManager.addFilters(newFilters); + }, + [indexPattern, services.data.query.filterManager] + ); + const panelHeader = ( { From f576b1f4677e843cea421ef2808b9d05ea638f87 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 26 Sep 2022 11:24:52 +0300 Subject: [PATCH 004/172] [Lens][Agg based table]Navigate to lens for agg based table vis (#140791) * Removed console.log. * Other aggs to formula convertion added. * Filter ratio added. * Add XY Configuration * Added last_value convertion. * Updated ColumnWithMeta types. * Fixed for loop. * Added StaticValue convertion. * Added default agg convertion. * Add filters column * Refactored filter part. * Added meta to all the cols. * Fixed problem with columns. * Added date_histogram column, fixed layer configuration * Added terms support. * Refactored. * Refactored for consistancy. * Removed `getVisualizationSuggestionFromContext` fn. * Added references. * Fixed percentile and percentile ranks. * removed unused return statement. * Refactoring 'getEmptyLayersSuggestionsForVisualizeCharts' method * TimeScaleUnit fix. * Fixed references. * Fixed types. * Fixed terms column * Fixed count custom label. * Fix multi series * Fixed custom labels. * Fixed 'insertReferences' method * Revert "Fixed custom labels." This reverts commit 9bd61fbd272d38b89c89d3b1e011211b22f3fb91. * Fixed custom labels. * Fixed filter ratio and derivative metrics * fix last_value and series agg * Added CounterRate formula support. * Fix types * Replaced usage of timeseries. * Added window everywhere. * Add std_deviation and value_count, fix some issues * Fix interval for date_histogram and fix getting filter for metrics and difference * Fix tests for 'getDatasourceSuggestionsForVisualizeCharts' except test for 'format' * Refactored code. * Refactoring 'createNewLayerWithMetricAggregationFromVizEditor' * Removed not used code. * Removed unused import. * Fixed types. * Removed tests with type troubles. * Some code refactoring + formatter. * Removed not used comment. * Added formatter validation. * Added tests for getFormat. * Fix `getFormat` method * createColumn test added. * Added tests for `excludeMetaFromColumn` and `isColumnWithMeta`. * Added tests for counter_rate. * Added tests for cumulative_sum. * Updated cumulative_sum tests. * Added date_histogram tests. * Added filter ratio tests. * Filters test added. * createFormulaColumn test added. * convertMathToFormulaColumn test added. * Added one more case to check. * Added test for convertOtherAggsToFormulaColumn * added more tests for formula. * Added last_value tests. * Added tests for moving average. * Style fix. * Added test for convertMetricAggregationColumnWithoutSpecialParams * Add tests for metrics_helper. Update tests for filter ratio and counter rate formulas * Added tests for convertMetricAggregationToColumn * Added tests for computeParentPipelineColumns * Added tests for convertParentPipelineAggToColumns * Add tests for getParentPipelineSeriesFormula * Added tests for createParentPipelineAggregationColumn * Added tests for isPercentileRanksColumnWithMeta * Added tests for convertToPercentileRankParams * Update and add tests for getSiblingPipelineSeriesFormula * Fixed tests. * Added tests for convertToPercentileRankColumn. * Added tests for convertToPercentileRankColumns. * Added tests for percentile. * Added tests for static_value. * Add tests for isValidMetrics * Added std_deviation tests. * Added tests for terms. * Added tests. * Add tests for getLayers in xy configuration * Rename window to reducedTimeRange * Added test for getBucketsColumns * Fixed tests for parent_pipeline. * Added tests for getValidColumns * Added tests for getMetricsColumns * Removed empty new.index.ts of top_n. * Configuration refactored. * Added tests for getConfigurationForTimeseries and getConfigurationForTopN * Added tests for timeseries/index.ts * Added test for top_n. * Refactored getSuggestionFromConvertToLensContext at suggestion_helpers. * Fix format * Fix types * Remove unused code * Fix tests * Fix bucket columns test * Init comment. Add column and supported metric, update types for vis schemas * Added percentile. * Added percentile_rank. * Add metric column without special params * Added new type for AggParamsMapping. * Added cumulative_sum converter. * Replaced cumulative_sum with common parent_pipeline. * Add sibling pipeline agg and date histogram column converter * Added getColumnsFromVis method. * Fixed formulas nad added support of value_count. * Added navigateToLens functionality to visEditor table. * Fixed eslint errors. * Add convertBucketToColumns and some small updates * Fixed problems with converting. * Fixed Avg. * Added median. * Fixed percentile and percentile_ranks. * Add terms and filters column, fix problem with changing index pattern, refactor sibling pipelline aggs * Fixed std_dev. * update convert to lens for datatable * Fixed dateHistogram labels. * Fix terms converter * Fixed percentile/percentile_ranks/std_deviation. * Fixed sum. * Fixed cumulative_sum with value count. * Fix counter rate metric and remove unused comments * Added validation for *_bucket metrics. * Added timeshift, where it is possible. * Fixed type. * Add support of percentage column for datatable * Fixed formats. * Add trigger for agg based visualizations * Fix formats * Some refactoring * Added configuration. * Fixed suggestions. * Add dropEmptyRowsInDateHistogram param * excluded meta. * Refactored imports. * Fixed label. * Added last_value. * Provide collapseFn for bucket columns * Added percentile/percentile_rank labels fix. * Fix sibling aggs converter * Refactored convertToSchemaConfig * Fixed terms * Fixed problem with showing sub metrics on UI * Moved out convertToSchemaConfig. * Fixed types. * Converted IAggConfig to SchemaConfig. * Fixed pipeline and terms aggs. Added timeShift arg. * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Fixed tests * Added support of nested parent pipeline metrics. * Fixed formats. * Fixed cumulative sum by value count. * Fixed percentile and percentile ranks. * Fixed tests. * One more fix. * Fixed timeShift * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Fixed std_deviation labels. * Fixed percentage column and labels for sibling pipeline aggs * Fixed getDataSourceInfo method * added track if the rendered table is able to be converted to Lens * Added support of nested parent pipeline aggs. * Fixed unit tests * Fix column order * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Added shard_delay to SUPPORTED_AGGREGATIONS array and fixed limits * Fixed mock for xy/vislib unti tests * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Small refactoring of getColumnsFromVis. * Refactored for tests purpose. * Added tests for utils. * Added tests for isValidVis. * Added tests for getMetricsWithoutDuplicates * Added tests for sortColumns. * Added getColumnIds. * Added unit tests for table configuration * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Added tests for schemas. * Reduced bundle size. * Fixed annotations. * Removed export. * Fixed terms column * Update src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> * Added support of ranges and histograms * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Added supporting of split series + sibling pipeline aggs * Fix getBucketCollapseFn method * Fixed problem with invalid fields. * Update src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> * Fixed problem with removed index pattern. * Fixed types in timeseries supported metrics. * Fixed tests * Provided dataView to all the formula functions. * Added checking field type for formula * Fixed bundle size. Co-authored-by: Yaroslav Kuznietsov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> --- packages/kbn-optimizer/limits.yml | 2 +- .../data/common/search/aggs/buckets/index.ts | 1 + .../common/search/aggs/buckets/multi_terms.ts | 13 +- .../data/common/search/aggs/buckets/terms.ts | 23 +- .../common/search/aggs/metrics/bucket_avg.ts | 9 +- .../common/search/aggs/metrics/bucket_max.ts | 9 +- .../common/search/aggs/metrics/bucket_min.ts | 9 +- .../common/search/aggs/metrics/bucket_sum.ts | 9 +- .../search/aggs/metrics/cumulative_sum.ts | 13 +- .../common/search/aggs/metrics/derivative.ts | 13 +- .../search/aggs/metrics/filtered_metric.ts | 9 +- .../common/search/aggs/metrics/moving_avg.ts | 13 +- .../common/search/aggs/metrics/serial_diff.ts | 13 +- .../common/search/aggs/metrics/top_hit.ts | 16 +- .../common/search/aggs/metrics/top_metrics.ts | 19 +- src/plugins/data/common/search/aggs/types.ts | 67 +- .../heatmap/public/sample_vis.test.mocks.ts | 6 +- .../pie/public/sample_vis.test.mocks.ts | 4 +- src/plugins/vis_types/table/kibana.json | 1 + .../__snapshots__/table_vis_fn.test.ts.snap | 1 + .../configurations/index.test.ts | 76 +++ .../convert_to_lens/configurations/index.ts | 69 ++ .../public/convert_to_lens/index.test.ts | 111 ++++ .../table/public/convert_to_lens/index.ts | 104 +++ .../table/public/convert_to_lens/types.ts | 17 + src/plugins/vis_types/table/public/plugin.ts | 7 +- .../vis_types/table/public/services.ts | 8 + .../vis_types/table/public/table_vis_fn.ts | 2 + .../table/public/table_vis_renderer.tsx | 3 +- .../vis_types/table/public/table_vis_type.ts | 7 + src/plugins/vis_types/table/tsconfig.json | 1 + .../lib/configurations/xy/layers.ts | 50 +- .../convert_to_lens/lib/convert/column.ts | 2 +- .../convert_to_lens/lib/convert/types.ts | 2 +- .../datasource/get_datasource_info.test.ts | 11 +- .../lib/datasource/get_datasource_info.ts | 56 +- .../lib/metrics/supported_metrics.ts | 42 +- .../convert_to_lens/timeseries/index.test.ts | 2 +- .../convert_to_lens/timeseries/index.ts | 29 +- .../public/convert_to_lens/top_n/index.ts | 16 +- .../vis_types/timeseries/public/metrics_fn.ts | 3 + .../timeseries/public/metrics_type.ts | 16 +- .../public/timeseries_vis_renderer.tsx | 5 +- .../public/__snapshots__/to_ast.test.ts.snap | 2 +- .../__snapshots__/to_ast_pie.test.ts.snap | 2 +- .../xy/public/sample_vis.test.mocks.ts | 14 +- .../visualizations/common/constants.ts | 7 + .../common/convert_to_lens/constants.ts | 5 + .../common/convert_to_lens/index.ts | 1 + .../convert_to_lens/lib/buckets/index.ts | 126 ++++ .../convert_to_lens/lib/convert/column.ts | 35 + .../convert_to_lens/lib/convert/constants.ts | 24 + .../lib/convert/date_histogram.ts | 64 ++ .../convert_to_lens/lib/convert/filters.ts | 34 + .../convert_to_lens/lib/convert/formula.ts | 28 + .../convert_to_lens/lib/convert/index.ts | 22 + .../convert_to_lens/lib/convert/last_value.ts | 63 ++ .../convert_to_lens/lib/convert/metric.ts | 109 ++++ .../lib/convert/parent_pipeline.ts | 153 +++++ .../convert_to_lens/lib/convert/percentile.ts | 89 +++ .../lib/convert/percentile_rank.ts | 84 +++ .../convert_to_lens/lib/convert/range.ts | 78 +++ .../lib/convert/sibling_pipeline.ts | 36 ++ .../lib/convert/std_deviation.ts | 82 +++ .../lib/convert/supported_metrics.ts | 231 +++++++ .../convert_to_lens/lib/convert/terms.ts | 141 ++++ .../convert_to_lens/lib/convert/types.ts | 118 ++++ .../common/convert_to_lens/lib/index.ts | 11 + .../convert_to_lens/lib/metrics/formula.ts | 258 ++++++++ .../convert_to_lens/lib/metrics/index.ts | 11 + .../convert_to_lens/lib/metrics/metrics.ts | 127 ++++ .../lib/metrics/percentage_formula.ts | 38 ++ .../common/convert_to_lens/lib/utils.ts | 201 ++++++ .../common/convert_to_lens/types/columns.ts | 8 +- .../common/convert_to_lens/types/common.ts | 2 +- .../convert_to_lens/types/configurations.ts | 31 +- .../common/convert_to_lens/types/context.ts | 1 + .../common/convert_to_lens/types/params.ts | 35 +- .../common/convert_to_lens/utils.ts | 22 +- src/plugins/visualizations/common/index.ts | 1 + src/plugins/visualizations/common/types.ts | 32 +- .../visualizations/common/vis_schemas.ts | 51 ++ .../public/convert_to_lens/datasource.ts | 20 + .../public/convert_to_lens/index.ts | 10 + .../public/convert_to_lens/schemas.test.ts | 257 ++++++++ .../public/convert_to_lens/schemas.ts | 120 ++++ .../public/convert_to_lens/utils.test.ts | 610 ++++++++++++++++++ .../public/convert_to_lens/utils.ts | 123 ++++ .../embeddable/visualize_embeddable.tsx | 1 + src/plugins/visualizations/public/index.ts | 12 +- src/plugins/visualizations/public/plugin.ts | 3 +- .../visualizations/public/triggers/index.ts | 12 +- .../visualizations/public/vis_schemas.ts | 109 ++-- .../public/vis_types/base_vis_type.ts | 2 + .../visualizations/public/vis_types/types.ts | 21 +- .../components/visualize_top_nav.tsx | 6 +- .../utils/get_top_nav_config.tsx | 10 +- .../editor_frame/state_helpers.ts | 8 +- .../editor_frame/suggestion_helpers.ts | 3 +- .../operations/definitions/date_histogram.tsx | 4 +- .../operations/definitions/ranges/ranges.tsx | 5 +- .../operations/definitions/terms/index.tsx | 4 + x-pack/plugins/lens/public/plugin.ts | 11 +- .../visualize_agg_based_vis_actions.ts | 43 ++ .../datatable/visualization.tsx | 32 + 105 files changed, 4439 insertions(+), 253 deletions(-) create mode 100644 src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts create mode 100644 src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts create mode 100644 src/plugins/vis_types/table/public/convert_to_lens/index.test.ts create mode 100644 src/plugins/vis_types/table/public/convert_to_lens/index.ts create mode 100644 src/plugins/vis_types/table/public/convert_to_lens/types.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/constants.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/index.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/utils.ts create mode 100644 src/plugins/visualizations/common/vis_schemas.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/datasource.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/index.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/schemas.test.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/schemas.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/utils.test.ts create mode 100644 src/plugins/visualizations/public/convert_to_lens/utils.ts create mode 100644 x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b8de0b6daa3b5..c8daaf258deb6 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -71,7 +71,7 @@ pageLoadAssetSize: kibanaUsageCollection: 16463 kibanaUtils: 79713 kubernetesSecurity: 77234 - lens: 36000 + lens: 36500 licenseManagement: 41817 licensing: 29004 lists: 22900 diff --git a/src/plugins/data/common/search/aggs/buckets/index.ts b/src/plugins/data/common/search/aggs/buckets/index.ts index 9d7819157e189..000dcd5382b56 100644 --- a/src/plugins/data/common/search/aggs/buckets/index.ts +++ b/src/plugins/data/common/search/aggs/buckets/index.ts @@ -48,3 +48,4 @@ export * from './sampler_fn'; export * from './sampler'; export * from './diversified_sampler_fn'; export * from './diversified_sampler'; +export { SHARD_DELAY_AGG_NAME } from './shard_delay'; diff --git a/src/plugins/data/common/search/aggs/buckets/multi_terms.ts b/src/plugins/data/common/search/aggs/buckets/multi_terms.ts index 8dbde951ba183..a064f19390515 100644 --- a/src/plugins/data/common/search/aggs/buckets/multi_terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/multi_terms.ts @@ -13,7 +13,7 @@ import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterMultiTerms } from './create_filter/multi_terms'; import { aggMultiTermsFnName } from './multi_terms_fn'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; import { MultiFieldKey } from './multi_field_key'; import { @@ -26,10 +26,9 @@ const termsTitle = i18n.translate('data.search.aggs.buckets.multiTermsTitle', { defaultMessage: 'Multi-Terms', }); -export interface AggParamsMultiTerms extends BaseAggParams { +interface CommonAggParamsMultiTerms extends BaseAggParams { fields: string[]; orderBy: string; - orderAgg?: AggConfigSerialized; order?: 'asc' | 'desc'; size?: number; shardSize?: number; @@ -38,6 +37,14 @@ export interface AggParamsMultiTerms extends BaseAggParams { separatorLabel?: string; } +export interface AggParamsMultiTermsSerialized extends CommonAggParamsMultiTerms { + orderAgg?: AggConfigSerialized; +} + +export interface AggParamsMultiTerms extends CommonAggParamsMultiTerms { + orderAgg?: IAggConfig; +} + export const getMultiTermsBucketAgg = () => { const keyCaches = new WeakMap(); return new BucketAggType({ diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index fdee9dfdb042f..8314d2cd532de 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -17,7 +17,7 @@ import { migrateIncludeExcludeFormat, } from './migrate_include_exclude_format'; import { aggTermsFnName } from './terms_fn'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; import { KBN_FIELD_TYPES } from '../../..'; @@ -33,11 +33,9 @@ const termsTitle = i18n.translate('data.search.aggs.buckets.termsTitle', { defaultMessage: 'Terms', }); -export interface AggParamsTerms extends BaseAggParams { +export interface CommonAggParamsTerms extends BaseAggParams { field: string; orderBy: string; - orderAgg?: AggConfigSerialized; - order?: 'asc' | 'desc'; size?: number; shardSize?: number; missingBucket?: boolean; @@ -45,12 +43,25 @@ export interface AggParamsTerms extends BaseAggParams { otherBucket?: boolean; otherBucketLabel?: string; // advanced - exclude?: string[] | number[]; - include?: string[] | number[]; + exclude?: string[] | string | number[]; + include?: string[] | string | number[]; includeIsRegex?: boolean; excludeIsRegex?: boolean; } +export interface AggParamsTermsSerialized extends CommonAggParamsTerms { + orderAgg?: AggConfigSerialized; + order?: 'asc' | 'desc'; +} + +export interface AggParamsTerms extends CommonAggParamsTerms { + orderAgg?: IAggConfig; + order?: { + value: 'asc' | 'desc'; + text: string; + }; +} + export const getTermsBucketAgg = () => new BucketAggType({ name: BUCKET_TYPES.TERMS, diff --git a/src/plugins/data/common/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/common/search/aggs/metrics/bucket_avg.ts index c38d3fcfb0413..01ca65e43f131 100644 --- a/src/plugins/data/common/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/common/search/aggs/metrics/bucket_avg.ts @@ -13,13 +13,18 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsBucketAvg extends BaseAggParams { +export interface AggParamsBucketAvgSerialized extends BaseAggParams { customMetric?: AggConfigSerialized; customBucket?: AggConfigSerialized; } +export interface AggParamsBucketAvg extends BaseAggParams { + customMetric?: IAggConfig; + customBucket?: IAggConfig; +} + const overallAverageLabel = i18n.translate('data.search.aggs.metrics.overallAverageLabel', { defaultMessage: 'overall average', }); diff --git a/src/plugins/data/common/search/aggs/metrics/bucket_max.ts b/src/plugins/data/common/search/aggs/metrics/bucket_max.ts index 28b3990bd62c6..162109e49f424 100644 --- a/src/plugins/data/common/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/common/search/aggs/metrics/bucket_max.ts @@ -12,13 +12,18 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsBucketMax extends BaseAggParams { +export interface AggParamsBucketMaxSerialized extends BaseAggParams { customMetric?: AggConfigSerialized; customBucket?: AggConfigSerialized; } +export interface AggParamsBucketMax extends BaseAggParams { + customMetric?: IAggConfig; + customBucket?: IAggConfig; +} + const overallMaxLabel = i18n.translate('data.search.aggs.metrics.overallMaxLabel', { defaultMessage: 'overall max', }); diff --git a/src/plugins/data/common/search/aggs/metrics/bucket_min.ts b/src/plugins/data/common/search/aggs/metrics/bucket_min.ts index 8fa5724dfddaf..8aa9d1d0d3799 100644 --- a/src/plugins/data/common/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/common/search/aggs/metrics/bucket_min.ts @@ -12,13 +12,18 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsBucketMin extends BaseAggParams { +export interface AggParamsBucketMinSerialized extends BaseAggParams { customMetric?: AggConfigSerialized; customBucket?: AggConfigSerialized; } +export interface AggParamsBucketMin extends BaseAggParams { + customMetric?: IAggConfig; + customBucket?: IAggConfig; +} + const overallMinLabel = i18n.translate('data.search.aggs.metrics.overallMinLabel', { defaultMessage: 'overall min', }); diff --git a/src/plugins/data/common/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/common/search/aggs/metrics/bucket_sum.ts index f249ef1ce0861..0ff737841b358 100644 --- a/src/plugins/data/common/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/common/search/aggs/metrics/bucket_sum.ts @@ -12,13 +12,18 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsBucketSum extends BaseAggParams { +export interface AggParamsBucketSumSerialized extends BaseAggParams { customMetric?: AggConfigSerialized; customBucket?: AggConfigSerialized; } +export interface AggParamsBucketSum extends BaseAggParams { + customMetric?: IAggConfig; + customBucket?: IAggConfig; +} + const overallSumLabel = i18n.translate('data.search.aggs.metrics.overallSumLabel', { defaultMessage: 'overall sum', }); diff --git a/src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts index 496380dff4d1b..f74ac5663581a 100644 --- a/src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts @@ -12,14 +12,21 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsCumulativeSum extends BaseAggParams { +export interface CommonAggParamsCumulativeSum extends BaseAggParams { buckets_path?: string; - customMetric?: AggConfigSerialized; metricAgg?: string; } +export interface AggParamsCumulativeSumSerialized extends CommonAggParamsCumulativeSum { + customMetric?: AggConfigSerialized; +} + +export interface AggParamsCumulativeSum extends CommonAggParamsCumulativeSum { + customMetric?: IAggConfig; +} + const cumulativeSumLabel = i18n.translate('data.search.aggs.metrics.cumulativeSumLabel', { defaultMessage: 'cumulative sum', }); diff --git a/src/plugins/data/common/search/aggs/metrics/derivative.ts b/src/plugins/data/common/search/aggs/metrics/derivative.ts index 2c6f3bd048bb9..e245b12a7d723 100644 --- a/src/plugins/data/common/search/aggs/metrics/derivative.ts +++ b/src/plugins/data/common/search/aggs/metrics/derivative.ts @@ -12,14 +12,21 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsDerivative extends BaseAggParams { +export interface CommonAggParamsDerivative extends BaseAggParams { buckets_path?: string; - customMetric?: AggConfigSerialized; metricAgg?: string; } +export interface AggParamsDerivativeSerialized extends CommonAggParamsDerivative { + customMetric?: AggConfigSerialized; +} + +export interface AggParamsDerivative extends CommonAggParamsDerivative { + customMetric?: IAggConfig; +} + const derivativeLabel = i18n.translate('data.search.aggs.metrics.derivativeLabel', { defaultMessage: 'derivative', }); diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts index 22ae42b9130a5..abf882dd0466a 100644 --- a/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts +++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts @@ -13,14 +13,19 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; import { aggFilteredMetricFnName } from './filtered_metric_fn'; -export interface AggParamsFilteredMetric extends BaseAggParams { +export interface AggParamsFilteredMetricSerialized extends BaseAggParams { customMetric?: AggConfigSerialized; customBucket?: AggConfigSerialized; } +export interface AggParamsFilteredMetric extends BaseAggParams { + customMetric?: IAggConfig; + customBucket?: IAggConfig; +} + const filteredMetricLabel = i18n.translate('data.search.aggs.metrics.filteredMetricLabel', { defaultMessage: 'filtered', }); diff --git a/src/plugins/data/common/search/aggs/metrics/moving_avg.ts b/src/plugins/data/common/search/aggs/metrics/moving_avg.ts index 29eb2d0ba75d3..942f813cbeac8 100644 --- a/src/plugins/data/common/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/common/search/aggs/metrics/moving_avg.ts @@ -12,16 +12,23 @@ import { aggMovingAvgFnName } from './moving_avg_fn'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsMovingAvg extends BaseAggParams { +export interface CommonAggParamsMovingAvg extends BaseAggParams { buckets_path?: string; window?: number; script?: string; - customMetric?: AggConfigSerialized; metricAgg?: string; } +export interface AggParamsMovingAvgSerialized extends CommonAggParamsMovingAvg { + customMetric?: AggConfigSerialized; +} + +export interface AggParamsMovingAvg extends CommonAggParamsMovingAvg { + customMetric?: IAggConfig; +} + const movingAvgTitle = i18n.translate('data.search.aggs.metrics.movingAvgTitle', { defaultMessage: 'Moving Avg', }); diff --git a/src/plugins/data/common/search/aggs/metrics/serial_diff.ts b/src/plugins/data/common/search/aggs/metrics/serial_diff.ts index e3e62f56326e6..e40680adff930 100644 --- a/src/plugins/data/common/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/common/search/aggs/metrics/serial_diff.ts @@ -12,14 +12,21 @@ import { aggSerialDiffFnName } from './serial_diff_fn'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -import { AggConfigSerialized, BaseAggParams } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfig } from '../types'; -export interface AggParamsSerialDiff extends BaseAggParams { +export interface CommonAggParamsSerialDiff extends BaseAggParams { buckets_path?: string; - customMetric?: AggConfigSerialized; metricAgg?: string; } +export interface AggParamsSerialDiffSerialized extends CommonAggParamsSerialDiff { + customMetric?: AggConfigSerialized; +} + +export interface AggParamsSerialDiff extends CommonAggParamsSerialDiff { + customMetric?: IAggConfig; +} + const serialDiffTitle = i18n.translate('data.search.aggs.metrics.serialDiffTitle', { defaultMessage: 'Serial Diff', }); diff --git a/src/plugins/data/common/search/aggs/metrics/top_hit.ts b/src/plugins/data/common/search/aggs/metrics/top_hit.ts index fee74bade2aa1..367e16c15a2f1 100644 --- a/src/plugins/data/common/search/aggs/metrics/top_hit.ts +++ b/src/plugins/data/common/search/aggs/metrics/top_hit.ts @@ -8,18 +8,30 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { DataViewField } from '@kbn/data-views-plugin/common'; import { aggTopHitFnName } from './top_hit_fn'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { flattenHit, KBN_FIELD_TYPES } from '../../..'; import { BaseAggParams } from '../types'; -export interface AggParamsTopHit extends BaseAggParams { +export interface BaseAggParamsTopHit extends BaseAggParams { field: string; aggregate: 'min' | 'max' | 'sum' | 'average' | 'concat'; - sortField?: string; size?: number; +} + +export interface AggParamsTopHitSerialized extends BaseAggParamsTopHit { sortOrder?: 'desc' | 'asc'; + sortField?: string; +} + +export interface AggParamsTopHit extends BaseAggParamsTopHit { + sortOrder?: { + value: 'desc' | 'asc'; + text: string; + }; + sortField?: DataViewField; } const isNumericFieldSelected = (agg: IMetricAggConfig) => { diff --git a/src/plugins/data/common/search/aggs/metrics/top_metrics.ts b/src/plugins/data/common/search/aggs/metrics/top_metrics.ts index df90186bbd746..354e0c6207d50 100644 --- a/src/plugins/data/common/search/aggs/metrics/top_metrics.ts +++ b/src/plugins/data/common/search/aggs/metrics/top_metrics.ts @@ -12,16 +12,27 @@ import { i18n } from '@kbn/i18n'; import { aggTopMetricsFnName } from './top_metrics_fn'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../..'; +import { DataViewField, KBN_FIELD_TYPES } from '../../..'; import { BaseAggParams } from '../types'; -export interface AggParamsTopMetrics extends BaseAggParams { +export interface BaseAggParamsTopMetrics extends BaseAggParams { field: string; - sortField?: string; - sortOrder?: 'desc' | 'asc'; size?: number; } +export interface AggParamsTopMetricsSerialized extends BaseAggParamsTopMetrics { + sortOrder?: 'desc' | 'asc'; + sortField?: string; +} + +export interface AggParamsTopMetrics extends BaseAggParamsTopMetrics { + sortOrder?: { + value: 'desc' | 'asc'; + text: string; + }; + sortField?: DataViewField; +} + export const getTopMetricsMetricAgg = () => { return new MetricAggType({ name: METRIC_TYPES.TOP_METRICS, diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index 16fac531fad94..bc35e46d8da7a 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -37,13 +37,18 @@ import { aggMovingAvg, AggParamsAvg, AggParamsBucketAvg, + AggParamsBucketAvgSerialized, AggParamsBucketMax, + AggParamsBucketMaxSerialized, AggParamsBucketMin, + AggParamsBucketMinSerialized, AggParamsBucketSum, + AggParamsBucketSumSerialized, AggParamsFilteredMetric, AggParamsCardinality, AggParamsValueCount, AggParamsCumulativeSum, + AggParamsCumulativeSumSerialized, AggParamsDateHistogram, AggParamsDateRange, AggParamsDerivative, @@ -69,9 +74,13 @@ import { AggParamsStdDeviation, AggParamsSum, AggParamsTerms, + AggParamsTermsSerialized, AggParamsMultiTerms, + AggParamsMultiTermsSerialized, AggParamsRareTerms, AggParamsTopHit, + AggParamsTopMetrics, + AggParamsTopMetricsSerialized, aggPercentileRanks, aggPercentiles, aggRange, @@ -94,13 +103,17 @@ import { aggSinglePercentile, aggSinglePercentileRank, AggConfigsOptions, + AggParamsCount, + AggParamsDerivativeSerialized, + AggParamsFilteredMetricSerialized, + AggParamsMovingAvgSerialized, + AggParamsSerialDiffSerialized, + AggParamsTopHitSerialized, } from '.'; import { AggParamsSampler } from './buckets/sampler'; import { AggParamsDiversifiedSampler } from './buckets/diversified_sampler'; import { AggParamsSignificantText } from './buckets/significant_text'; -import { AggParamsTopMetrics } from './metrics/top_metrics'; import { aggTopMetrics } from './metrics/top_metrics_fn'; -import { AggParamsCount } from './metrics'; export type { IAggConfig, AggConfigSerialized } from './agg_config'; export type { CreateAggConfigParams, IAggConfigs, AggConfigsOptions } from './agg_configs'; @@ -146,8 +159,8 @@ export interface AggExpressionType { } /** @internal */ -export type AggExpressionFunctionArgs = - AggParamsMapping[Name] & Pick; +export type AggExpressionFunctionArgs = + SerializedAggParamsMapping[Name] & Pick; /** * A global list of the param interfaces for each agg type. @@ -156,6 +169,51 @@ export type AggExpressionFunctionArgs = * * @internal */ +interface SerializedAggParamsMapping { + [BUCKET_TYPES.RANGE]: AggParamsRange; + [BUCKET_TYPES.IP_RANGE]: AggParamsIpRange; + [BUCKET_TYPES.DATE_RANGE]: AggParamsDateRange; + [BUCKET_TYPES.FILTER]: AggParamsFilter; + [BUCKET_TYPES.FILTERS]: AggParamsFilters; + [BUCKET_TYPES.SIGNIFICANT_TERMS]: AggParamsSignificantTerms; + [BUCKET_TYPES.SIGNIFICANT_TEXT]: AggParamsSignificantText; + [BUCKET_TYPES.GEOTILE_GRID]: AggParamsGeoTile; + [BUCKET_TYPES.GEOHASH_GRID]: AggParamsGeoHash; + [BUCKET_TYPES.HISTOGRAM]: AggParamsHistogram; + [BUCKET_TYPES.DATE_HISTOGRAM]: AggParamsDateHistogram; + [BUCKET_TYPES.TERMS]: AggParamsTermsSerialized; + [BUCKET_TYPES.MULTI_TERMS]: AggParamsMultiTermsSerialized; + [BUCKET_TYPES.RARE_TERMS]: AggParamsRareTerms; + [BUCKET_TYPES.SAMPLER]: AggParamsSampler; + [BUCKET_TYPES.DIVERSIFIED_SAMPLER]: AggParamsDiversifiedSampler; + [METRIC_TYPES.AVG]: AggParamsAvg; + [METRIC_TYPES.CARDINALITY]: AggParamsCardinality; + [METRIC_TYPES.COUNT]: AggParamsCount; + [METRIC_TYPES.VALUE_COUNT]: AggParamsValueCount; + [METRIC_TYPES.GEO_BOUNDS]: AggParamsGeoBounds; + [METRIC_TYPES.GEO_CENTROID]: AggParamsGeoCentroid; + [METRIC_TYPES.MAX]: AggParamsMax; + [METRIC_TYPES.MEDIAN]: AggParamsMedian; + [METRIC_TYPES.SINGLE_PERCENTILE]: AggParamsSinglePercentile; + [METRIC_TYPES.SINGLE_PERCENTILE_RANK]: AggParamsSinglePercentileRank; + [METRIC_TYPES.MIN]: AggParamsMin; + [METRIC_TYPES.STD_DEV]: AggParamsStdDeviation; + [METRIC_TYPES.SUM]: AggParamsSum; + [METRIC_TYPES.AVG_BUCKET]: AggParamsBucketAvgSerialized; + [METRIC_TYPES.MAX_BUCKET]: AggParamsBucketMaxSerialized; + [METRIC_TYPES.MIN_BUCKET]: AggParamsBucketMinSerialized; + [METRIC_TYPES.SUM_BUCKET]: AggParamsBucketSumSerialized; + [METRIC_TYPES.FILTERED_METRIC]: AggParamsFilteredMetricSerialized; + [METRIC_TYPES.CUMULATIVE_SUM]: AggParamsCumulativeSumSerialized; + [METRIC_TYPES.DERIVATIVE]: AggParamsDerivativeSerialized; + [METRIC_TYPES.MOVING_FN]: AggParamsMovingAvgSerialized; + [METRIC_TYPES.PERCENTILE_RANKS]: AggParamsPercentileRanks; + [METRIC_TYPES.PERCENTILES]: AggParamsPercentiles; + [METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiffSerialized; + [METRIC_TYPES.TOP_HITS]: AggParamsTopHitSerialized; + [METRIC_TYPES.TOP_METRICS]: AggParamsTopMetricsSerialized; +} + export interface AggParamsMapping { [BUCKET_TYPES.RANGE]: AggParamsRange; [BUCKET_TYPES.IP_RANGE]: AggParamsIpRange; @@ -200,7 +258,6 @@ export interface AggParamsMapping { [METRIC_TYPES.TOP_HITS]: AggParamsTopHit; [METRIC_TYPES.TOP_METRICS]: AggParamsTopMetrics; } - /** * A global list of the expression function definitions for each agg type function. */ diff --git a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts index 04eac5c64de29..55a8aaa218837 100644 --- a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts @@ -1717,7 +1717,7 @@ export const sampleAreaVis = { { id: '1', enabled: true, - type: 'sum', + type: { name: 'sum' }, params: { field: 'total_quantity', }, @@ -1736,7 +1736,7 @@ export const sampleAreaVis = { { id: '2', enabled: true, - type: 'date_histogram', + type: { name: 'date_histogram' }, params: { field: 'order_date', timeRange: { @@ -1759,7 +1759,7 @@ export const sampleAreaVis = { { id: '3', enabled: true, - type: 'terms', + type: { name: 'terms' }, params: { field: 'category.keyword', orderBy: '1', diff --git a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts index e71bb7250dd1a..3525b7b6fbc05 100644 --- a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts @@ -1308,7 +1308,7 @@ export const samplePieVis = { { id: '1', enabled: true, - type: 'count', + type: { name: 'count' }, params: {}, schema: 'metric', toSerializedFieldFormat: () => ({ @@ -1318,7 +1318,7 @@ export const samplePieVis = { { id: '2', enabled: true, - type: 'terms', + type: { name: 'terms' }, params: { field: 'Carrier', orderBy: '1', diff --git a/src/plugins/vis_types/table/kibana.json b/src/plugins/vis_types/table/kibana.json index 0d5d6066643ad..d0ab6489ae61e 100644 --- a/src/plugins/vis_types/table/kibana.json +++ b/src/plugins/vis_types/table/kibana.json @@ -7,6 +7,7 @@ "expressions", "visualizations", "fieldFormats", + "dataViews", "usageCollection" ], "requiredBundles": [ diff --git a/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap b/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap index 1a2badbd26634..a2032429c385e 100644 --- a/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap +++ b/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap @@ -25,6 +25,7 @@ Object { "as": "table_vis", "type": "render", "value": Object { + "canNavigateToLens": undefined, "visConfig": Object { "autoFitRowToContent": false, "buckets": Array [], diff --git a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..4393ec86c2271 --- /dev/null +++ b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggTypes } from '../../../common'; +import { getConfiguration } from '.'; + +const params = { + perPage: 20, + percentageCol: 'Count', + showLabel: false, + showMetricsAtAllLevels: true, + showPartialRows: true, + showTotal: true, + showToolbar: false, + totalFunc: AggTypes.SUM, +}; + +describe('getConfiguration', () => { + test('should return correct configuration', () => { + expect( + getConfiguration('test1', params, { + metrics: ['metric-1'], + buckets: ['bucket-1'], + columnsWithoutReferenced: [ + { + columnId: 'metric-1', + operationType: 'count', + isBucketed: false, + isSplit: false, + sourceField: 'document', + params: {}, + dataType: 'number', + }, + { + columnId: 'bucket-1', + operationType: 'date_histogram', + isBucketed: true, + isSplit: false, + sourceField: 'date-field', + dataType: 'date', + params: { + interval: '1h', + }, + }, + ], + bucketCollapseFn: { 'bucket-1': 'sum' }, + }) + ).toEqual({ + columns: [ + { + alignment: 'left', + columnId: 'metric-1', + summaryRow: 'sum', + }, + { + alignment: 'left', + collapseFn: 'sum', + columnId: 'bucket-1', + }, + ], + headerRowHeight: 'single', + rowHeight: 'single', + layerId: 'test1', + layerType: 'data', + paging: { + enabled: true, + size: 20, + }, + }); + }); +}); diff --git a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..d98cb917b40ac --- /dev/null +++ b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, PagingState, TableVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { TableVisParams } from '../../../common'; + +const getColumns = ( + params: TableVisParams, + metrics: string[], + buckets: string[], + columns: Column[], + bucketCollapseFn?: Record +) => { + const { showTotal, totalFunc } = params; + return columns.map(({ columnId }) => ({ + columnId, + alignment: 'left' as const, + ...(showTotal && metrics.includes(columnId) ? { summaryRow: totalFunc } : {}), + ...(bucketCollapseFn && bucketCollapseFn[columnId] + ? { collapseFn: bucketCollapseFn[columnId] } + : {}), + })); +}; + +const getPagination = ({ perPage }: TableVisParams): PagingState => { + return { + enabled: perPage !== '', + size: perPage !== '' ? perPage : 0, + }; +}; + +const getRowHeight = ( + params: TableVisParams +): Pick => { + const { autoFitRowToContent } = params; + return { + rowHeight: autoFitRowToContent ? 'auto' : 'single', + headerRowHeight: autoFitRowToContent ? 'auto' : 'single', + }; +}; + +export const getConfiguration = ( + layerId: string, + params: TableVisParams, + { + metrics, + buckets, + columnsWithoutReferenced, + bucketCollapseFn, + }: { + metrics: string[]; + buckets: string[]; + columnsWithoutReferenced: Column[]; + bucketCollapseFn?: Record; + } +): TableVisConfiguration => { + return { + layerId, + layerType: 'data', + columns: getColumns(params, metrics, buckets, columnsWithoutReferenced, bucketCollapseFn), + paging: getPagination(params), + ...getRowHeight(params), + }; +}; diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..5c1ad0578be11 --- /dev/null +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { convertToLens } from '.'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetPercentageColumnFormulaColumn = jest.fn(); +const mockGetVisSchemas = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + getPercentageColumnFormulaColumn: jest.fn(() => mockGetPercentageColumnFormulaColumn()), + }), + getVisSchemas: jest.fn(() => mockGetVisSchemas()), + getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +const vis = { + isHierarchical: () => false, + type: {}, + params: { + perPage: 20, + percentageCol: 'Count', + showLabel: false, + showMetricsAtAllLevels: true, + showPartialRows: true, + showTotal: true, + showToolbar: false, + totalFunc: 'sum', + }, + data: {}, +} as any; + +const timefilter = { + getAbsoluteTime: () => {}, +} as any; + +describe('convertToLens', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if can not build percentage column', async () => { + mockGetColumnsFromVis.mockReturnValue({ + buckets: ['2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }); + mockGetVisSchemas.mockReturnValue({ + metric: [{ label: 'Count', aggId: 'agg-1' }], + }); + mockGetPercentageColumnFormulaColumn.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockGetPercentageColumnFormulaColumn).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return correct state for valid vis', async () => { + mockGetColumnsFromVis.mockReturnValue({ + buckets: ['2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }); + mockGetVisSchemas.mockReturnValue({ + metric: [{ label: 'Count', aggId: 'agg-1' }], + }); + mockGetPercentageColumnFormulaColumn.mockReturnValue({ columnId: 'percentage-column-1' }); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockGetPercentageColumnFormulaColumn).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsDatatable'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '2' }, { columnId: 'percentage-column-1' }, { columnId: '1' }], + }) + ); + }); +}); diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..e236c36e82a10 --- /dev/null +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { Column, ColumnWithMeta, SchemaConfig } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getVisSchemas, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getConfiguration } from './configurations'; +import { ConvertTableToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertTableToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const { getColumnsFromVis, getPercentageColumnFormulaColumn } = await convertToLensModule; + const result = getColumnsFromVis( + vis, + timefilter, + dataView, + { + buckets: ['bucket'], + splits: ['split_row', 'split_column'], + }, + { dropEmptyRowsInDateHistogram: true } + ); + + if (result === null) { + return null; + } + + if (vis.params.percentageCol) { + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + const metricAgg = visSchemas.metric.find((m) => m.label === vis.params.percentageCol); + if (!metricAgg) { + return null; + } + const percentageColumn = getPercentageColumnFormulaColumn({ + agg: metricAgg as SchemaConfig, + dataView, + aggs: visSchemas.metric as Array>, + }); + if (!percentageColumn) { + return null; + } + result.columns.splice( + result.columnsWithoutReferenced.findIndex((c) => c.meta.aggId === metricAgg.aggId) + 1, + 0, + percentageColumn + ); + result.columnsWithoutReferenced.push(percentageColumn); + } + + const layerId = uuid(); + const indexPatternId = dataView.id!; + return { + type: 'lnsDatatable', + layers: [ + { + indexPatternId, + layerId, + columns: result.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration: getConfiguration(layerId, vis.params, result), + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/table/public/convert_to_lens/types.ts b/src/plugins/vis_types/table/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..12aacce959c14 --- /dev/null +++ b/src/plugins/vis_types/table/public/convert_to_lens/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, TableVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { TableVisParams } from '../../common'; + +export type ConvertTableToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/table/public/plugin.ts b/src/plugins/vis_types/table/public/plugin.ts index 9e246448eb044..8e5769e2ef86a 100644 --- a/src/plugins/vis_types/table/public/plugin.ts +++ b/src/plugins/vis_types/table/public/plugin.ts @@ -12,7 +12,8 @@ import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { setFormatService } from './services'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { setDataViewsStart, setFormatService } from './services'; import { registerTableVis } from './register_vis'; /** @internal */ @@ -24,6 +25,7 @@ export interface TablePluginSetupDependencies { /** @internal */ export interface TablePluginStartDependencies { fieldFormats: FieldFormatsStart; + dataViews: DataViewsPublicPluginStart; usageCollection: UsageCollectionStart; } @@ -35,7 +37,8 @@ export class TableVisPlugin registerTableVis(core, deps); } - public start(core: CoreStart, { fieldFormats }: TablePluginStartDependencies) { + public start(core: CoreStart, { fieldFormats, dataViews }: TablePluginStartDependencies) { setFormatService(fieldFormats); + setDataViewsStart(dataViews); } } diff --git a/src/plugins/vis_types/table/public/services.ts b/src/plugins/vis_types/table/public/services.ts index edc6969e389ed..d6789deb70aaf 100644 --- a/src/plugins/vis_types/table/public/services.ts +++ b/src/plugins/vis_types/table/public/services.ts @@ -8,6 +8,14 @@ import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; export const [getFormatService, setFormatService] = createGetterSetter('FieldFormats'); + +export const [getUsageCollectionStart, setUsageCollectionStart] = + createGetterSetter('UsageCollection', false); + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/table/public/table_vis_fn.ts b/src/plugins/vis_types/table/public/table_vis_fn.ts index 77996423d2c43..43bdec2b10de9 100644 --- a/src/plugins/vis_types/table/public/table_vis_fn.ts +++ b/src/plugins/vis_types/table/public/table_vis_fn.ts @@ -17,6 +17,7 @@ export interface TableVisRenderValue { visData: TableVisData; visType: typeof VIS_TYPE_TABLE; visConfig: TableVisConfig; + canNavigateToLens: boolean; } export type TableExpressionFunctionDefinition = ExpressionFunctionDefinition< @@ -177,6 +178,7 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({ ...args, title: (handlers.variables.embeddableTitle as string) ?? args.title, }, + canNavigateToLens: handlers.variables.canNavigateToLens as boolean, }, }; }, diff --git a/src/plugins/vis_types/table/public/table_vis_renderer.tsx b/src/plugins/vis_types/table/public/table_vis_renderer.tsx index 783cf49d06311..ab40c5f567c2d 100644 --- a/src/plugins/vis_types/table/public/table_vis_renderer.tsx +++ b/src/plugins/vis_types/table/public/table_vis_renderer.tsx @@ -39,7 +39,7 @@ export const getTableVisRenderer: ( ) => ExpressionRenderDefinition = (core, usageCollection) => ({ name: 'table_vis', reuseDomNode: true, - render: async (domNode, { visData, visConfig }, handlers) => { + render: async (domNode, { visData, visConfig, canNavigateToLens }, handlers) => { handlers.onDestroy(() => { unmountComponentAtNode(domNode); }); @@ -55,6 +55,7 @@ export const getTableVisRenderer: ( const counterEvents = [ `render_${visualizationType}_table`, !visData.table ? `render_${visualizationType}_table_split` : undefined, + canNavigateToLens ? `render_${visualizationType}_table_convertable` : undefined, ].filter(Boolean) as string[]; usageCollection.reportUiCounter(containerType, METRIC_TYPE.COUNT, counterEvents); diff --git a/src/plugins/vis_types/table/public/table_vis_type.ts b/src/plugins/vis_types/table/public/table_vis_type.ts index 8bd20fb6a0c81..27479d23b99de 100644 --- a/src/plugins/vis_types/table/public/table_vis_type.ts +++ b/src/plugins/vis_types/table/public/table_vis_type.ts @@ -13,6 +13,7 @@ import { VIS_EVENT_TO_TRIGGER, VisTypeDefinition } from '@kbn/visualizations-plu import { TableVisParams, VIS_TYPE_TABLE } from '../common'; import { TableOptions } from './components/table_vis_options_lazy'; import { toExpressionAst } from './to_ast'; +import { convertToLens } from './convert_to_lens'; export const tableVisTypeDefinition: VisTypeDefinition = { name: VIS_TYPE_TABLE, @@ -102,4 +103,10 @@ export const tableVisTypeDefinition: VisTypeDefinition = { hasPartialRows: (vis) => vis.params.showPartialRows, hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }; diff --git a/src/plugins/vis_types/table/tsconfig.json b/src/plugins/vis_types/table/tsconfig.json index 6df0a83853142..892c5691c8f04 100644 --- a/src/plugins/vis_types/table/tsconfig.json +++ b/src/plugins/vis_types/table/tsconfig.json @@ -17,6 +17,7 @@ { "path": "../../data/tsconfig.json" }, { "path": "../../visualizations/tsconfig.json" }, { "path": "../../share/tsconfig.json" }, + { "path": "../../data_views/tsconfig.json" }, { "path": "../../expressions/tsconfig.json" }, { "path": "../../kibana_utils/tsconfig.json" }, { "path": "../../kibana_react/tsconfig.json" }, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts index 064471e9fcfaf..6815e278e0f30 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts @@ -69,7 +69,7 @@ export const getLayers = async ( dataSourceLayers: Record, model: Panel, dataViews: DataViewsPublicPluginStart -): Promise => { +): Promise => { const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => { const series = model.series[parseInt(key, 10)]; const { metrics, seriesAgg } = getSeriesAgg(series.metrics); @@ -136,30 +136,34 @@ export const getLayers = async ( (a) => typeof a.index_pattern === 'object' && 'id' in a.index_pattern && a.index_pattern.id ); - const annotationsLayers: Array = await Promise.all( - Object.entries(annotationsByIndexPattern).map(async ([indexPatternId, annotations]) => { - const convertedAnnotations: EventAnnotationConfig[] = []; - const { indexPattern } = (await fetchIndexPattern({ id: indexPatternId }, dataViews)) || {}; + try { + const annotationsLayers: Array = await Promise.all( + Object.entries(annotationsByIndexPattern).map(async ([indexPatternId, annotations]) => { + const convertedAnnotations: EventAnnotationConfig[] = []; + const { indexPattern } = (await fetchIndexPattern({ id: indexPatternId }, dataViews)) || {}; - if (indexPattern) { - annotations.forEach((a: Annotation) => { - const lensAnnotation = convertAnnotation(a, indexPattern); - if (lensAnnotation) { - convertedAnnotations.push(lensAnnotation); - } - }); - return { - layerId: v4(), - layerType: 'annotations', - ignoreGlobalFilters: true, - annotations: convertedAnnotations, - indexPatternId, - }; - } - }) - ); + if (indexPattern) { + annotations.forEach((a: Annotation) => { + const lensAnnotation = convertAnnotation(a, indexPattern); + if (lensAnnotation) { + convertedAnnotations.push(lensAnnotation); + } + }); + return { + layerId: v4(), + layerType: 'annotations', + ignoreGlobalFilters: true, + annotations: convertedAnnotations, + indexPatternId, + }; + } + }) + ); - return nonAnnotationsLayers.concat(...annotationsLayers.filter(nonNullable)); + return nonAnnotationsLayers.concat(...annotationsLayers.filter(nonNullable)); + } catch (e) { + return null; + } }; const convertAnnotation = ( diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts index 3bcd219f22676..bd0a9d572f192 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts @@ -11,7 +11,7 @@ import { BaseColumn, Operation, DataType, - ColumnWithMeta as GenericColumnWithMeta, + GenericColumnWithMeta, FormatParams, } from '@kbn/visualizations-plugin/common/convert_to_lens'; import uuid from 'uuid'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts index 2b8e831d3f5c3..d21291dc2622a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts @@ -10,7 +10,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { Layer as BaseLayer, Column as BaseColumn, - ColumnWithMeta as GenericColumnWithMeta, + GenericColumnWithMeta, PercentileColumn as BasePercentileColumn, PercentileRanksColumn as BasePercentileRanksColumn, FiltersColumn, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.test.ts index 5033d1470147d..975acfbbbcbb6 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.test.ts @@ -33,7 +33,7 @@ describe('getDataSourceInfo', () => { }); test('should return the default dataview if model_indexpattern is string', async () => { - const { indexPatternId, timeField } = await getDataSourceInfo( + const datasourceInfo = await getDataSourceInfo( 'test', undefined, false, @@ -41,12 +41,13 @@ describe('getDataSourceInfo', () => { undefined, dataViews ); + const { indexPatternId, timeField } = datasourceInfo!; expect(indexPatternId).toBe('12345'); expect(timeField).toBe('@timestamp'); }); test('should return the correct dataview if model_indexpattern is object', async () => { - const { indexPatternId, timeField } = await getDataSourceInfo( + const datasourceInfo = await getDataSourceInfo( { id: 'dataview-1-id' }, 'timeField-1', false, @@ -54,12 +55,14 @@ describe('getDataSourceInfo', () => { undefined, dataViews ); + const { indexPatternId, timeField } = datasourceInfo!; + expect(indexPatternId).toBe('dataview-1-id'); expect(timeField).toBe('timeField-1'); }); test('should fetch the correct data if overwritten dataview is provided', async () => { - const { indexPatternId, timeField } = await getDataSourceInfo( + const datasourceInfo = await getDataSourceInfo( { id: 'dataview-1-id' }, 'timeField-1', true, @@ -67,6 +70,8 @@ describe('getDataSourceInfo', () => { undefined, dataViews ); + const { indexPatternId, timeField } = datasourceInfo!; + expect(indexPatternId).toBe('test2'); expect(timeField).toBe('timeField2'); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.ts index 8f7fbd3670f4e..615e1595c8529 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/datasource/get_datasource_info.ts @@ -20,36 +20,40 @@ export const getDataSourceInfo = async ( seriesTimeField: string | undefined, dataViews: DataViewsPublicPluginStart ) => { - let indexPatternId = - modelIndexPattern && !isStringTypeIndexPattern(modelIndexPattern) ? modelIndexPattern.id : ''; + try { + let indexPatternId = + modelIndexPattern && !isStringTypeIndexPattern(modelIndexPattern) ? modelIndexPattern.id : ''; - let timeField = modelTimeField; - let indexPattern: DataView | null | undefined; - // handle override index pattern - if (isOverwritten) { - const fetchedIndexPattern = await fetchIndexPattern(overwrittenIndexPattern, dataViews); - indexPattern = fetchedIndexPattern.indexPattern; + let timeField = modelTimeField; + let indexPattern: DataView | null | undefined; + // handle override index pattern + if (isOverwritten) { + const fetchedIndexPattern = await fetchIndexPattern(overwrittenIndexPattern, dataViews); + indexPattern = fetchedIndexPattern.indexPattern; - if (indexPattern) { - indexPatternId = indexPattern.id ?? ''; - timeField = seriesTimeField ?? indexPattern.timeFieldName; + if (indexPattern) { + indexPatternId = indexPattern.id ?? ''; + timeField = seriesTimeField ?? indexPattern.timeFieldName; + } } - } - if (!indexPatternId) { - indexPattern = await dataViews.getDefault(); - indexPatternId = indexPattern?.id ?? ''; - timeField = indexPattern?.timeFieldName; - } else { - indexPattern = await dataViews.get(indexPatternId); - if (!timeField) { - timeField = indexPattern.timeFieldName; + if (!indexPatternId) { + indexPattern = await dataViews.getDefault(); + indexPatternId = indexPattern?.id ?? ''; + timeField = indexPattern?.timeFieldName; + } else { + indexPattern = await dataViews.get(indexPatternId); + if (!timeField) { + timeField = indexPattern.timeFieldName; + } } - } - return { - indexPatternId, - timeField, - indexPattern, - }; + return { + indexPatternId, + timeField, + indexPattern, + }; + } catch (e) { + return null; + } }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index 736037a3eb67c..933e7e344b7fd 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -22,8 +22,8 @@ interface AggWithFormula { export type AggOptions = { isFullReference: boolean; isFieldRequired: boolean; - supportedPanelTypes: PANEL_TYPES[]; - supportedTimeRangeModes: TIME_RANGE_DATA_MODES[]; + supportedPanelTypes: readonly PANEL_TYPES[]; + supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[]; } & (T extends Exclude ? Agg : AggWithFormula); // list of supported TSVB aggregation types in Lens @@ -62,8 +62,8 @@ export type SupportedMetrics = LocalSupportedMetrics & { [Key in UnsupportedSupportedMetrics]?: null; }; -const supportedPanelTypes: PANEL_TYPES[] = [PANEL_TYPES.TIMESERIES, PANEL_TYPES.TOP_N]; -const supportedTimeRangeModes: TIME_RANGE_DATA_MODES[] = [ +const supportedPanelTypes: readonly PANEL_TYPES[] = [PANEL_TYPES.TIMESERIES, PANEL_TYPES.TOP_N]; +const supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[] = [ TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, TIME_RANGE_DATA_MODES.LAST_VALUE, ]; @@ -94,29 +94,29 @@ export const SUPPORTED_METRICS: SupportedMetrics = { name: 'counter_rate', isFullReference: true, isFieldRequired: true, - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, supportedTimeRangeModes, }, moving_average: { name: 'moving_average', isFullReference: true, isFieldRequired: true, - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, derivative: { name: 'differences', isFullReference: true, isFieldRequired: true, - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, cumulative_sum: { name: 'cumulative_sum', isFullReference: true, isFieldRequired: true, - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, avg_bucket: { name: 'formula', @@ -124,8 +124,8 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_average', - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, max_bucket: { name: 'formula', @@ -133,8 +133,8 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_max', - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, min_bucket: { name: 'formula', @@ -142,8 +142,8 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_min', - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, sum_bucket: { name: 'formula', @@ -151,8 +151,8 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_sum', - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, max: { name: 'max', @@ -220,8 +220,8 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'pick_max', - supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as PANEL_TYPES[], - supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as TIME_RANGE_DATA_MODES[], + supportedPanelTypes: [PANEL_TYPES.TIMESERIES] as const, + supportedTimeRangeModes: [TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE] as const, }, static: { name: 'static_value', diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts index a6460e2917ba9..50aa1a6c6f7f4 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts @@ -70,7 +70,7 @@ describe('convertToLens', () => { mockConvertToDateHistogramColumn.mockReturnValue({}); mockGetMetricsColumns.mockReturnValue([{}]); mockGetBucketsColumns.mockReturnValue([{}]); - mockGetConfigurationForTimeseries.mockReturnValue({}); + mockGetConfigurationForTimeseries.mockReturnValue({ layers: [] }); }); afterEach(() => { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index 25cd1fa962d0d..4610310c1b378 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -7,7 +7,11 @@ */ import { parseTimeShift } from '@kbn/data-plugin/common'; -import { Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { + getIndexPatternIds, + isAnnotationsLayer, + Layer, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; import uuid from 'uuid'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { Panel } from '../../../common/types'; @@ -57,7 +61,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel return null; } - const { indexPatternId, indexPattern, timeField } = await getDataSourceInfo( + const datasourceInfo = await getDataSourceInfo( model.index_pattern, model.time_field, Boolean(series.override_index_pattern), @@ -65,7 +69,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel series.series_time_field, dataViews ); + if (!datasourceInfo) { + return null; + } + const { indexPatternId, indexPattern, timeField } = datasourceInfo; if (!timeField) { return null; } @@ -98,10 +106,23 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel } const configLayers = await getLayers(extendedLayers, model, dataViews); + if (configLayers === null) { + return null; + } + + const configuration = getConfiguration(model, configLayers); + const layers = Object.values(excludeMetaFromLayers(extendedLayers)); + const annotationIndexPatterns = configuration.layers.reduce((acc, layer) => { + if (isAnnotationsLayer(layer)) { + return [...acc, layer.indexPatternId]; + } + return acc; + }, []); return { type: 'lnsXY', - layers: Object.values(excludeMetaFromLayers(extendedLayers)), - configuration: getConfiguration(model, configLayers), + layers, + configuration, + indexPatternIds: [...getIndexPatternIds(layers), ...annotationIndexPatterns], }; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 2622fadf5e753..630a9e5cf4c97 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { parseTimeShift } from '@kbn/data-plugin/common'; -import { Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { getIndexPatternIds, Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { PANEL_TYPES } from '../../../common/enums'; import { getDataViewsStart } from '../../services'; import { getDataSourceInfo } from '../lib/datasource'; @@ -48,7 +48,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR return null; } - const { indexPatternId, indexPattern } = await getDataSourceInfo( + const datasourceInfo = await getDataSourceInfo( model.index_pattern, model.time_field, Boolean(series.override_index_pattern), @@ -57,6 +57,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR dataViews ); + if (!datasourceInfo) { + return null; + } + + const { indexPatternId, indexPattern } = datasourceInfo; const reducedTimeRange = getReducedTimeRange(model, series, timeRange); // handle multiple metrics @@ -80,10 +85,15 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR } const configLayers = await getLayers(extendedLayers, model, dataViews); + if (configLayers === null) { + return null; + } + const layers = Object.values(excludeMetaFromLayers(extendedLayers)); return { type: 'lnsXY', - layers: Object.values(excludeMetaFromLayers(extendedLayers)), + layers, configuration: getConfiguration(model, configLayers), + indexPatternIds: getIndexPatternIds(layers), }; }; diff --git a/src/plugins/vis_types/timeseries/public/metrics_fn.ts b/src/plugins/vis_types/timeseries/public/metrics_fn.ts index 9562e19f3f0ec..e6e2814b6e08b 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_fn.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_fn.ts @@ -27,6 +27,7 @@ export interface TimeseriesRenderValue { visParams: TimeseriesVisParams; syncColors: boolean; syncTooltips: boolean; + canNavigateToLens?: boolean; } export type TimeseriesExpressionFunctionDefinition = ExpressionFunctionDefinition< @@ -65,6 +66,7 @@ export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({ getExecutionContext, inspectorAdapters, abortSignal: expressionAbortSignal, + variables, } ) { const visParams: TimeseriesVisParams = JSON.parse(args.params); @@ -90,6 +92,7 @@ export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({ visData: response, syncColors, syncTooltips, + canNavigateToLens: variables.canNavigateToLens as boolean, }, }; }, diff --git a/src/plugins/vis_types/timeseries/public/metrics_type.ts b/src/plugins/vis_types/timeseries/public/metrics_type.ts index 012bdd35115f2..2e18e9c0af48e 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_type.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_type.ts @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; -import { TimeRange } from '@kbn/data-plugin/common'; import { Vis, VIS_EVENT_TO_TRIGGER, @@ -169,8 +168,19 @@ export const metricsVisDefinition: VisTypeDefinition< } return []; }, - navigateToLens: async (params?: VisParams, timeRange?: TimeRange) => - params ? await convertTSVBtoLensConfiguration(params as Panel, timeRange) : null, + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean( + vis?.params + ? await convertTSVBtoLensConfiguration(vis.params as Panel, timeFilter?.getAbsoluteTime()) + : null + ), + }; + }, + navigateToLens: async (vis, timeFilter) => + vis?.params + ? await convertTSVBtoLensConfiguration(vis?.params as Panel, timeFilter?.getAbsoluteTime()) + : null, inspectorAdapters: () => ({ requests: new RequestAdapter(), diff --git a/src/plugins/vis_types/timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_types/timeseries/public/timeseries_vis_renderer.tsx index 60b651f2df21b..4d64e46356c0d 100644 --- a/src/plugins/vis_types/timeseries/public/timeseries_vis_renderer.tsx +++ b/src/plugins/vis_types/timeseries/public/timeseries_vis_renderer.tsx @@ -62,12 +62,9 @@ export const getTimeseriesVisRenderer: (deps: { handlers.onDestroy(() => { unmountComponentAtNode(domNode); }); - const { visParams: model, visData, syncColors, syncTooltips } = config; + const { visParams: model, visData, syncColors, syncTooltips, canNavigateToLens } = config; const showNoResult = !checkIfDataExists(visData, model); - const { convertTSVBtoLensConfiguration } = await import('./convert_to_lens'); - const canNavigateToLens = await convertTSVBtoLensConfiguration(model); - const renderComplete = () => { const usageCollection = getUsageCollectionStart(); const containerType = extractContainerType(handlers.getExecutionContext()); diff --git a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_types/vislib/public/__snapshots__/to_ast.test.ts.snap index 6d20088dbff32..a3566ff97e799 100644 --- a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_types/vislib/public/__snapshots__/to_ast.test.ts.snap @@ -8,7 +8,7 @@ Object { "area", ], "visConfig": Array [ - "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"circlesRadius\\":5,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"legendSize\\":\\"small\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"truncateLegend\\":true,\\"maxLegendLines\\":1,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"circlesRadius\\":5,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"legendSize\\":\\"small\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"truncateLegend\\":true,\\"maxLegendLines\\":1,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{\\"date\\":true,\\"intervalESUnit\\":\\"h\\",\\"intervalESValue\\":\\"1\\",\\"interval\\":3600000,\\"format\\":{},\\"bounds\\":{}},\\"aggType\\":\\"date_histogram\\",\\"aggId\\":\\"2\\",\\"aggParams\\":{\\"field\\":\\"order_date\\",\\"timeRange\\":{},\\"useNormalizedEsInterval\\":true,\\"scaleMetricValues\\":false,\\"interval\\":\\"auto\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{},\\"aggType\\":\\"sum\\",\\"aggId\\":\\"1\\",\\"aggParams\\":{\\"field\\":\\"total_quantity\\"}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{},\\"aggType\\":\\"terms\\",\\"aggId\\":\\"3\\",\\"aggParams\\":{\\"field\\":\\"category.keyword\\",\\"orderBy\\":\\"1\\",\\"order\\":\\"desc\\",\\"size\\":5,\\"otherBucket\\":false,\\"otherBucketLabel\\":\\"Other\\",\\"missingBucket\\":false,\\"missingBucketLabel\\":\\"Missing\\"}}]}}", ], }, "getArgument": [Function], diff --git a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap b/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap index 80e52d95be5c9..cf9e9aad3a0c4 100644 --- a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap +++ b/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap @@ -5,7 +5,7 @@ Object { "addArgument": [Function], "arguments": Object { "visConfig": Array [ - "{\\"type\\":\\"pie\\",\\"addTooltip\\":true,\\"legendDisplay\\":\\"show\\",\\"legendPosition\\":\\"right\\",\\"legendSize\\":\\"large\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "{\\"type\\":\\"pie\\",\\"addTooltip\\":true,\\"legendDisplay\\":\\"show\\",\\"legendPosition\\":\\"right\\",\\"legendSize\\":\\"large\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{},\\"aggType\\":\\"count\\",\\"aggId\\":\\"1\\",\\"aggParams\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{},\\"aggType\\":\\"terms\\",\\"aggId\\":\\"2\\",\\"aggParams\\":{\\"field\\":\\"Carrier\\",\\"orderBy\\":\\"1\\",\\"order\\":\\"desc\\",\\"size\\":5,\\"otherBucket\\":false,\\"otherBucketLabel\\":\\"Other\\",\\"missingBucket\\":false,\\"missingBucketLabel\\":\\"Missing\\"}}]}}", ], }, "getArgument": [Function], diff --git a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts index d1e4af6b4b6c8..e55debd7c77ba 100644 --- a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts @@ -1838,7 +1838,7 @@ export const sampleAreaVis = { { id: '1', enabled: true, - type: 'sum', + type: { name: 'sum' }, params: { field: 'total_quantity', }, @@ -1857,7 +1857,7 @@ export const sampleAreaVis = { { id: '2', enabled: true, - type: 'date_histogram', + type: { name: 'date_histogram' }, params: { field: 'order_date', timeRange: { @@ -1872,6 +1872,14 @@ export const sampleAreaVis = { extended_bounds: {}, }, schema: 'segment', + buckets: { + getInterval: () => ({ esUnit: 'h', esValue: '1' }), + getScaledDateFormat: () => ({}), + getBounds: () => ({}), + setBounds: () => {}, + setInterval: () => {}, + }, + fieldIsTimeField: () => false, toSerializedFieldFormat: () => ({ id: 'date', params: { pattern: 'HH:mm:ss.SSS' }, @@ -1880,7 +1888,7 @@ export const sampleAreaVis = { { id: '3', enabled: true, - type: 'terms', + type: { name: 'terms' }, params: { field: 'category.keyword', orderBy: '1', diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts index ea695e6bdca02..5fd21e426da8d 100644 --- a/src/plugins/visualizations/common/constants.ts +++ b/src/plugins/visualizations/common/constants.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { METRIC_TYPES, BUCKET_TYPES } from '@kbn/data-plugin/common'; + export const VISUALIZE_ENABLE_LABS_SETTING = 'visualize:enableLabs'; export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; @@ -44,3 +46,8 @@ export const LegendSizeToPixels = { } as const; export const DEFAULT_LEGEND_SIZE = LegendSize.MEDIUM; + +export const SUPPORTED_AGGREGATIONS = [ + ...Object.values(METRIC_TYPES), + ...Object.values(BUCKET_TYPES), +] as const; diff --git a/src/plugins/visualizations/common/convert_to_lens/constants.ts b/src/plugins/visualizations/common/convert_to_lens/constants.ts index bd312129c9779..fa92b881033c7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/constants.ts +++ b/src/plugins/visualizations/common/convert_to_lens/constants.ts @@ -35,3 +35,8 @@ export const OperationsWithReferences = { } as const; export const Operations = { ...OperationsWithSourceField, ...OperationsWithReferences } as const; + +export const RANGE_MODES = { + Range: 'range', + Histogram: 'histogram', +} as const; diff --git a/src/plugins/visualizations/common/convert_to_lens/index.ts b/src/plugins/visualizations/common/convert_to_lens/index.ts index d336872cff6bb..16e1ad990e31b 100644 --- a/src/plugins/visualizations/common/convert_to_lens/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export type { AggBasedColumn } from './lib/convert/types'; export * from './types'; export * from './constants'; export * from './utils'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts new file mode 100644 index 0000000000000..0f929189f3369 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { convertToSchemaConfig } from '../../../vis_schemas'; +import { SchemaConfig } from '../../..'; +import { + AggBasedColumn, + CommonBucketConverterArgs, + convertToDateHistogramColumn, + convertToFiltersColumn, + convertToTermsColumn, + convertToRangeColumn, +} from '../convert'; +import { getFieldNameFromField, getLabel, isSchemaConfig } from '../utils'; + +export type BucketAggs = + | BUCKET_TYPES.TERMS + | BUCKET_TYPES.DATE_HISTOGRAM + | BUCKET_TYPES.FILTERS + | BUCKET_TYPES.RANGE + | BUCKET_TYPES.HISTOGRAM; +const SUPPORTED_BUCKETS: string[] = [ + BUCKET_TYPES.TERMS, + BUCKET_TYPES.DATE_HISTOGRAM, + BUCKET_TYPES.FILTERS, + BUCKET_TYPES.RANGE, + BUCKET_TYPES.HISTOGRAM, +]; + +const isSupportedBucketAgg = (agg: SchemaConfig): agg is SchemaConfig => { + return SUPPORTED_BUCKETS.includes(agg.aggType); +}; + +export const getBucketColumns = ( + { agg, dataView, metricColumns, aggs }: CommonBucketConverterArgs, + { + label, + isSplit = false, + dropEmptyRowsInDateHistogram = false, + }: { label: string; isSplit: boolean; dropEmptyRowsInDateHistogram: boolean } +) => { + if (!agg.aggParams) { + return null; + } + switch (agg.aggType) { + case BUCKET_TYPES.DATE_HISTOGRAM: + return convertToDateHistogramColumn( + agg.aggId ?? '', + agg.aggParams, + dataView, + isSplit, + dropEmptyRowsInDateHistogram + ); + case BUCKET_TYPES.FILTERS: + return convertToFiltersColumn(agg.aggId ?? '', agg.aggParams, isSplit); + case BUCKET_TYPES.RANGE: + case BUCKET_TYPES.HISTOGRAM: + return convertToRangeColumn(agg.aggId ?? '', agg.aggParams, label, dataView, isSplit); + case BUCKET_TYPES.TERMS: + const fieldName = getFieldNameFromField(agg.aggParams.field); + if (!fieldName) { + return null; + } + const field = dataView.getFieldByName(fieldName); + + if (!field) { + return null; + } + if (field.type !== 'date') { + return convertToTermsColumn( + agg.aggId ?? '', + { agg, dataView, metricColumns, aggs }, + label, + isSplit + ); + } else { + return convertToDateHistogramColumn( + agg.aggId ?? '', + { + field: fieldName, + }, + dataView, + isSplit, + dropEmptyRowsInDateHistogram + ); + } + } + + return null; +}; + +export const convertBucketToColumns = ( + { + agg, + dataView, + metricColumns, + aggs, + }: { + agg: SchemaConfig | IAggConfig; + dataView: DataView; + metricColumns: AggBasedColumn[]; + aggs: Array>; + }, + isSplit: boolean = false, + dropEmptyRowsInDateHistogram: boolean = false +) => { + const currentAgg = isSchemaConfig(agg) ? agg : convertToSchemaConfig(agg); + if (!currentAgg.aggParams || !isSupportedBucketAgg(currentAgg)) { + return null; + } + return getBucketColumns( + { agg: currentAgg, dataView, metricColumns, aggs }, + { + label: getLabel(currentAgg), + isSplit, + dropEmptyRowsInDateHistogram, + } + ); +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts new file mode 100644 index 0000000000000..b1ac46dd36a20 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import { DataType, FormatParams } from '../../types'; +import { SchemaConfig } from '../../../types'; +import { AggId, ExtraColumnFields, GeneralColumnWithMeta } from './types'; +import { getLabel } from '../utils'; + +export const createAggregationId = (agg: SchemaConfig): AggId => `${agg.aggId}`; + +export const getFormat = (): FormatParams => { + return {}; +}; + +export const createColumn = ( + agg: SchemaConfig, + field?: DataViewField, + { isBucketed = false, isSplit = false, reducedTimeRange }: ExtraColumnFields = {} +): GeneralColumnWithMeta => ({ + columnId: uuid(), + dataType: (field?.type as DataType) ?? undefined, + label: getLabel(agg), + isBucketed, + isSplit, + reducedTimeRange, + timeShift: agg.aggParams?.timeShift, + meta: { aggId: createAggregationId(agg) }, +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/constants.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/constants.ts new file mode 100644 index 0000000000000..b44a2e2f243e4 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/constants.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; + +export const PARENT_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.CUMULATIVE_SUM, + METRIC_TYPES.DERIVATIVE, + METRIC_TYPES.MOVING_FN, +]; + +export const SIBLING_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.AVG_BUCKET, + METRIC_TYPES.MAX_BUCKET, + METRIC_TYPES.MIN_BUCKET, + METRIC_TYPES.SUM_BUCKET, +]; + +export const PIPELINE_AGGS = [...PARENT_PIPELINE_AGGS, ...SIBLING_PIPELINE_AGGS]; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.ts new file mode 100644 index 0000000000000..5a00ebdd90aa3 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggParamsDateHistogram } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import uuid from 'uuid'; +import { DataType, DateHistogramParams } from '../../types'; +import { getFieldNameFromField } from '../utils'; +import { DateHistogramColumn } from './types'; + +export const getLabel = (aggParams: AggParamsDateHistogram, fieldName: string) => { + return aggParams && 'customLabel' in aggParams ? aggParams.customLabel ?? fieldName : fieldName; +}; + +export const convertToDateHistogramParams = ( + aggParams: AggParamsDateHistogram, + dropEmptyRowsInDateHistogram: boolean +): DateHistogramParams => { + return { + interval: aggParams.interval ?? 'auto', + dropPartials: aggParams.drop_partials, + includeEmptyRows: !dropEmptyRowsInDateHistogram, + }; +}; + +export const convertToDateHistogramColumn = ( + aggId: string, + aggParams: AggParamsDateHistogram, + dataView: DataView, + isSplit: boolean, + dropEmptyRowsInDateHistogram: boolean +): DateHistogramColumn | null => { + const dateFieldName = getFieldNameFromField(aggParams.field); + + if (!dateFieldName) { + return null; + } + + const dateField = dataView.getFieldByName(dateFieldName); + if (!dateField) { + return null; + } + + const params = convertToDateHistogramParams(aggParams, dropEmptyRowsInDateHistogram); + const label = getLabel(aggParams, dateFieldName); + + return { + columnId: uuid(), + label, + operationType: 'date_histogram', + dataType: dateField.type as DataType, + isBucketed: true, + isSplit, + sourceField: dateField.name, + params, + timeShift: aggParams.timeShift, + meta: { aggId }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.ts new file mode 100644 index 0000000000000..416efdc53a72d --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggParamsFilters } from '@kbn/data-plugin/common'; +import uuid from 'uuid'; +import { FiltersColumn } from './types'; + +export const convertToFiltersColumn = ( + aggId: string, + aggParams: AggParamsFilters, + isSplit: boolean = false +): FiltersColumn | null => { + if (!aggParams.filters?.length) { + return null; + } + + return { + columnId: uuid(), + operationType: 'filters', + dataType: 'string', + isBucketed: true, + isSplit, + params: { + filters: aggParams.filters ?? [], + }, + timeShift: aggParams.timeShift, + meta: { aggId }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts new file mode 100644 index 0000000000000..0ad2a4072e19d --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SchemaConfig } from '../../..'; +import { FormulaParams } from '../../types'; +import { createAggregationId, createColumn, getFormat } from './column'; +import { FormulaColumn } from './types'; + +const convertToFormulaParams = (formula: string): FormulaParams => ({ + formula, +}); + +export const createFormulaColumn = (formula: string, agg: SchemaConfig): FormulaColumn | null => { + const params = convertToFormulaParams(formula); + return { + operationType: 'formula', + ...createColumn(agg), + references: [], + params: { ...params, ...getFormat() }, + timeShift: agg.aggParams?.timeShift, + meta: { aggId: createAggregationId(agg) }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts new file mode 100644 index 0000000000000..b77ea6b97209a --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './column'; +export * from './date_histogram'; +export * from './filters'; +export * from './formula'; +export * from './metric'; +export * from './parent_pipeline'; +export * from './percentile_rank'; +export * from './percentile'; +export * from './sibling_pipeline'; +export * from './std_deviation'; +export * from './terms'; +export * from './types'; +export * from './last_value'; +export * from './range'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts new file mode 100644 index 0000000000000..3162cf14e71c3 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig } from '../../..'; +import { LastValueParams } from '../../types'; +import { isFieldValid } from '../../utils'; +import { getFieldNameFromField } from '../utils'; +import { createColumn, getFormat } from './column'; +import { SUPPORTED_METRICS } from './supported_metrics'; +import { CommonColumnConverterArgs, LastValueColumn } from './types'; + +const convertToLastValueParams = ( + agg: SchemaConfig +): LastValueParams => { + return { + sortField: agg.aggParams!.sortField!.name, + showArrayValues: agg.aggType === METRIC_TYPES.TOP_HITS, + }; +}; + +export const convertToLastValueColumn = ( + { agg, dataView }: CommonColumnConverterArgs, + reducedTimeRange?: string +): LastValueColumn | null => { + const { aggParams } = agg; + + if ( + (aggParams?.size && Number(aggParams?.size) !== 1) || + aggParams?.sortOrder?.value !== 'desc' + ) { + return null; + } + + const fieldName = getFieldNameFromField(agg.aggParams!.field); + if (!fieldName) { + return null; + } + + const field = dataView.getFieldByName(fieldName); + if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + return null; + } + + if (!agg.aggParams?.sortField) { + return null; + } + + return { + operationType: 'last_value', + sourceField: field.name ?? 'document', + ...createColumn(agg, field, { reducedTimeRange }), + params: { + ...convertToLastValueParams(agg), + ...getFormat(), + }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts new file mode 100644 index 0000000000000..f46adea9538dc --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { Operations } from '../../constants'; +import { createColumn, getFormat } from './column'; +import { SupportedMetric } from './supported_metrics'; +import { CommonColumnConverterArgs, MetricsWithField } from './types'; +import { SchemaConfig } from '../../../types'; +import { + AvgColumn, + CardinalityColumn, + CountColumn, + MaxColumn, + MedianColumn, + MinColumn, + SumColumn, +} from './types'; +import { getFieldNameFromField } from '../utils'; +import { isFieldValid } from '../../utils'; + +type MetricAggregationWithoutParams = + | typeof Operations.AVERAGE + | typeof Operations.COUNT + | typeof Operations.UNIQUE_COUNT + | typeof Operations.COUNTER_RATE + | typeof Operations.MAX + | typeof Operations.MEDIAN + | typeof Operations.MIN + | typeof Operations.SUM; + +const SUPPORTED_METRICS_AGGS_WITHOUT_PARAMS: MetricAggregationWithoutParams[] = [ + Operations.AVERAGE, + Operations.COUNT, + Operations.UNIQUE_COUNT, + Operations.MAX, + Operations.MIN, + Operations.MEDIAN, + Operations.SUM, +]; + +export type MetricAggregationColumnWithoutSpecialParams = + | AvgColumn + | CountColumn + | CardinalityColumn + | MaxColumn + | MedianColumn + | MinColumn + | SumColumn; + +export type MetricsWithoutSpecialParams = + | METRIC_TYPES.AVG + | METRIC_TYPES.COUNT + | METRIC_TYPES.MAX + | METRIC_TYPES.MIN + | METRIC_TYPES.SUM + | METRIC_TYPES.MEDIAN + | METRIC_TYPES.CARDINALITY + | METRIC_TYPES.VALUE_COUNT; + +const isSupportedAggregationWithoutParams = ( + agg: string +): agg is MetricAggregationWithoutParams => { + return (SUPPORTED_METRICS_AGGS_WITHOUT_PARAMS as string[]).includes(agg); +}; + +export const isMetricWithField = ( + agg: CommonColumnConverterArgs['agg'] +): agg is SchemaConfig => { + return agg.aggType !== METRIC_TYPES.COUNT; +}; + +export const convertMetricAggregationColumnWithoutSpecialParams = ( + aggregation: SupportedMetric, + { agg, dataView }: CommonColumnConverterArgs, + reducedTimeRange?: string +): MetricAggregationColumnWithoutSpecialParams | null => { + if (!isSupportedAggregationWithoutParams(aggregation.name)) { + return null; + } + + let sourceField; + + if (isMetricWithField(agg)) { + sourceField = getFieldNameFromField(agg.aggParams?.field) ?? 'document'; + } else { + sourceField = 'document'; + } + + const field = dataView.getFieldByName(sourceField); + if (!isFieldValid(field, aggregation)) { + return null; + } + + return { + operationType: aggregation.name, + sourceField, + ...createColumn(agg, field, { + reducedTimeRange, + }), + params: { ...getFormat() }, + timeShift: agg.aggParams?.timeShift, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts new file mode 100644 index 0000000000000..c1fd75ae19265 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig } from '../../..'; +import { MovingAverageParams } from '../../types'; +import { convertMetricToColumns, getFormulaForPipelineAgg } from '../metrics'; +import { createColumn } from './column'; +import { createFormulaColumn } from './formula'; +import { + convertMetricAggregationColumnWithoutSpecialParams, + MetricAggregationColumnWithoutSpecialParams, +} from './metric'; +import { SUPPORTED_METRICS } from './supported_metrics'; +import { + MovingAverageColumn, + DerivativeColumn, + CumulativeSumColumn, + FormulaColumn, + ExtendedColumnConverterArgs, + OtherParentPipelineAggs, + AggBasedColumn, +} from './types'; +import { PIPELINE_AGGS, SIBLING_PIPELINE_AGGS } from './constants'; +import { getMetricFromParentPipelineAgg } from '../utils'; + +export type ParentPipelineAggColumn = MovingAverageColumn | DerivativeColumn | CumulativeSumColumn; + +export const convertToMovingAverageParams = ( + agg: SchemaConfig +): MovingAverageParams => ({ + window: agg.aggParams!.window ?? 0, +}); + +export const convertToOtherParentPipelineAggColumns = ( + { agg, dataView, aggs }: ExtendedColumnConverterArgs, + reducedTimeRange?: string +): FormulaColumn | [ParentPipelineAggColumn, AggBasedColumn] | null => { + const { aggType } = agg; + const op = SUPPORTED_METRICS[aggType]; + if (!op) { + return null; + } + + const metric = getMetricFromParentPipelineAgg(agg, aggs); + if (!metric) { + return null; + } + + const subAgg = SUPPORTED_METRICS[metric.aggType]; + + if (!subAgg) { + return null; + } + + if (SIBLING_PIPELINE_AGGS.includes(metric.aggType)) { + return null; + } + + if (PIPELINE_AGGS.includes(metric.aggType)) { + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + if (!formula) { + return null; + } + + return createFormulaColumn(formula, agg); + } + + const subMetric = convertMetricToColumns(metric, dataView, aggs); + + if (subMetric === null) { + return null; + } + + return [ + { + operationType: op.name, + references: [subMetric[0].columnId], + ...createColumn(agg), + params: {}, + timeShift: agg.aggParams?.timeShift, + } as ParentPipelineAggColumn, + subMetric[0], + ]; +}; + +export const convertToCumulativeSumAggColumn = ( + { agg, dataView, aggs }: ExtendedColumnConverterArgs, + reducedTimeRange?: string +): + | FormulaColumn + | [ParentPipelineAggColumn, MetricAggregationColumnWithoutSpecialParams] + | null => { + const { aggParams, aggType } = agg; + if (!aggParams) { + return null; + } + const metric = getMetricFromParentPipelineAgg(agg, aggs); + if (!metric) { + return null; + } + + const subAgg = SUPPORTED_METRICS[metric.aggType]; + + if (!subAgg) { + return null; + } + + if (SIBLING_PIPELINE_AGGS.includes(metric.aggType)) { + return null; + } + + if (metric.aggType === METRIC_TYPES.COUNT || subAgg.name === 'sum') { + // create column for sum or count + const subMetric = convertMetricAggregationColumnWithoutSpecialParams( + subAgg, + { agg: metric as SchemaConfig, dataView }, + reducedTimeRange + ); + if (subMetric === null) { + return null; + } + + const op = SUPPORTED_METRICS[aggType]; + if (!op) { + return null; + } + + return [ + { + operationType: op.name, + references: [subMetric?.columnId], + ...createColumn(agg), + params: {}, + timeShift: agg.aggParams?.timeShift, + } as ParentPipelineAggColumn, + + subMetric, + ]; + } else { + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + if (!formula) { + return null; + } + + return createFormulaColumn(formula, agg); + } +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts new file mode 100644 index 0000000000000..de9d4e088b636 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig } from '../../..'; +import { isFieldValid, PercentileParams } from '../..'; +import { getFieldNameFromField, getLabelForPercentile } from '../utils'; +import { createColumn, getFormat } from './column'; +import { PercentileColumn, CommonColumnConverterArgs } from './types'; +import { SUPPORTED_METRICS } from './supported_metrics'; + +export const convertToPercentileParams = (percentile: number): PercentileParams => ({ + percentile, +}); + +const isSinglePercentile = ( + agg: SchemaConfig +): agg is SchemaConfig => { + if (agg.aggType === METRIC_TYPES.SINGLE_PERCENTILE) { + return true; + } + return false; +}; + +const getPercent = ( + agg: SchemaConfig +) => { + if (isSinglePercentile(agg)) { + return agg.aggParams?.percentile; + } + const { aggParams, aggId } = agg; + if (!aggParams || !aggId) { + return null; + } + + const { percents } = aggParams; + + const [, percentStr] = aggId.split('.'); + + const percent = Number(percentStr); + if (!percents || !percents.length || percentStr === '' || isNaN(percent)) { + return null; + } + return percent; +}; + +export const convertToPercentileColumn = ( + { + agg, + dataView, + }: CommonColumnConverterArgs, + { index, reducedTimeRange }: { index?: number; reducedTimeRange?: string } = {} +): PercentileColumn | null => { + const { aggParams, aggId } = agg; + if (!aggParams || !aggId) { + return null; + } + const percent = getPercent(agg); + if (percent === null || percent === undefined) { + return null; + } + + const params = convertToPercentileParams(percent); + + const fieldName = getFieldNameFromField(agg?.aggParams?.field); + + if (!fieldName) { + return null; + } + + const field = dataView.getFieldByName(fieldName); + if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + return null; + } + + return { + operationType: 'percentile', + sourceField: field.name, + ...createColumn(agg, field, { reducedTimeRange }), + params: { ...params, ...getFormat() }, + label: getLabelForPercentile(agg), + timeShift: agg.aggParams?.timeShift, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts new file mode 100644 index 0000000000000..5124a26543552 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig } from '../../..'; +import { isFieldValid, PercentileRanksParams } from '../..'; +import { getFieldNameFromField, getLabelForPercentile } from '../utils'; +import { createColumn, getFormat } from './column'; +import { PercentileRanksColumn, CommonColumnConverterArgs } from './types'; +import { SUPPORTED_METRICS } from './supported_metrics'; + +export const convertToPercentileRankParams = (value: number): PercentileRanksParams => ({ + value: Number(value), +}); + +const isSinglePercentileRank = ( + agg: SchemaConfig +): agg is SchemaConfig => { + if (agg.aggType === METRIC_TYPES.SINGLE_PERCENTILE_RANK) { + return true; + } + return false; +}; + +const getPercent = ( + agg: SchemaConfig +) => { + if (isSinglePercentileRank(agg)) { + return agg.aggParams?.value; + } + const { aggParams, aggId } = agg; + if (!aggParams || !aggId) { + return null; + } + const { values } = aggParams; + const [, percentStr] = aggId.split('.'); + + const percent = Number(percentStr); + + if (!values || !values.length || percentStr === '' || isNaN(percent)) { + return null; + } + return percent; +}; + +export const convertToPercentileRankColumn = ( + { + agg, + dataView, + }: CommonColumnConverterArgs, + { index, reducedTimeRange }: { index?: number; reducedTimeRange?: string } = {} +): PercentileRanksColumn | null => { + const { aggParams } = agg; + const percent = getPercent(agg); + + if (percent === null || percent === undefined) { + return null; + } + + const params = convertToPercentileRankParams(percent); + const fieldName = getFieldNameFromField(aggParams!.field); + if (!fieldName) { + return null; + } + + const field = dataView.getFieldByName(fieldName); + if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + return null; + } + + return { + operationType: 'percentile_rank', + sourceField: field.name, + ...createColumn(agg, field, { reducedTimeRange }), + params: { ...params, ...getFormat() }, + label: getLabelForPercentile(agg), + timeShift: aggParams?.timeShift, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts new file mode 100644 index 0000000000000..6a9f96fd5ad1e --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggParamsRange, AggParamsHistogram } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import uuid from 'uuid'; +import { RANGE_MODES } from '../../constants'; +import { DataType, RangeParams } from '../../types'; +import { getFieldNameFromField } from '../utils'; +import { RangeColumn } from './types'; + +const isHistogramAggParams = ( + aggParams: AggParamsRange | AggParamsHistogram +): aggParams is AggParamsHistogram => { + return aggParams && 'interval' in aggParams; +}; + +export const convertToRangeParams = ( + aggParams: AggParamsRange | AggParamsHistogram +): RangeParams => { + if (isHistogramAggParams(aggParams)) { + return { + type: RANGE_MODES.Histogram, + maxBars: aggParams.maxBars ?? 'auto', + ranges: [], + }; + } else { + return { + type: RANGE_MODES.Range, + maxBars: 'auto', + ranges: + aggParams.ranges?.map((range) => ({ + label: range.label, + from: range.from ?? null, + to: range.to ?? null, + })) ?? [], + }; + } +}; + +export const convertToRangeColumn = ( + aggId: string, + aggParams: AggParamsRange | AggParamsHistogram, + label: string, + dataView: DataView, + isSplit: boolean = false +): RangeColumn | null => { + const fieldName = getFieldNameFromField(aggParams.field); + + if (!fieldName) { + return null; + } + + const field = dataView.getFieldByName(fieldName); + if (!field) { + return null; + } + + const params = convertToRangeParams(aggParams); + + return { + columnId: uuid(), + label, + operationType: 'range', + dataType: field.type as DataType, + isBucketed: true, + isSplit, + sourceField: field.name, + params, + timeShift: aggParams.timeShift, + meta: { aggId }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts new file mode 100644 index 0000000000000..03e1d955dd045 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { convertMetricToColumns } from '../metrics'; +import { AggBasedColumn, ExtendedColumnConverterArgs, SiblingPipelineMetric } from './types'; +import { convertToSchemaConfig } from '../../../vis_schemas'; + +export const convertToSiblingPipelineColumns = ( + columnConverterArgs: ExtendedColumnConverterArgs +): AggBasedColumn | null => { + const { aggParams, label } = columnConverterArgs.agg; + if (!aggParams) { + return null; + } + + if (!aggParams.customMetric) { + return null; + } + + const customMetricColumn = convertMetricToColumns( + { ...convertToSchemaConfig(aggParams.customMetric), label }, + columnConverterArgs.dataView, + columnConverterArgs.aggs + ); + + if (!customMetricColumn) { + return null; + } + + return customMetricColumn[0]; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts new file mode 100644 index 0000000000000..f2c218d429bdf --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { isFieldValid } from '../../utils'; +import { addTimeRangeToFormula } from '../metrics/formula'; +import { getFieldNameFromField } from '../utils'; +import { createFormulaColumn } from './formula'; +import { getFormulaFromMetric, SUPPORTED_METRICS } from './supported_metrics'; +import { CommonColumnConverterArgs, FormulaColumn } from './types'; + +const STD_LOWER = 'std_lower'; +const STD_UPPER = 'std_upper'; +const STD_MODES = [STD_LOWER, STD_UPPER]; + +const getFormulaForStdDevLowerBound = (field: string, reducedTimeRange?: string) => { + const aggFormula = getFormulaFromMetric(SUPPORTED_METRICS.std_dev); + + return `average(${field}${addTimeRangeToFormula( + reducedTimeRange + )}) - ${2} * ${aggFormula}(${field}${addTimeRangeToFormula(reducedTimeRange)})`; +}; + +const getFormulaForStdDevUpperBound = (field: string, reducedTimeRange?: string) => { + const aggFormula = getFormulaFromMetric(SUPPORTED_METRICS.std_dev); + + return `average(${field}${addTimeRangeToFormula( + reducedTimeRange + )}) + ${2} * ${aggFormula}(${field}${addTimeRangeToFormula(reducedTimeRange)})`; +}; + +export const getStdDeviationFormula = ( + aggId: string, + fieldName: string, + reducedTimeRange?: string +) => { + const [, mode] = aggId.split('.'); + if (!STD_MODES.includes(mode)) { + return null; + } + + return mode === STD_LOWER + ? getFormulaForStdDevLowerBound(fieldName, reducedTimeRange) + : getFormulaForStdDevUpperBound(fieldName, reducedTimeRange); +}; + +export const convertToStdDeviationFormulaColumns = ( + { agg, dataView }: CommonColumnConverterArgs, + reducedTimeRange?: string +) => { + const { aggId } = agg; + if (!aggId) { + return null; + } + + const fieldName = getFieldNameFromField(agg.aggParams?.field); + + if (!fieldName) { + return null; + } + const field = dataView.getFieldByName(fieldName); + if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + return null; + } + + const formula = getStdDeviationFormula(aggId, field.displayName, reducedTimeRange); + + if (!formula) { + return null; + } + + const formulaColumn: FormulaColumn | null = createFormulaColumn(formula, agg); + if (!formulaColumn) { + return null; + } + return { ...formulaColumn, label: agg.label }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts new file mode 100644 index 0000000000000..17a8ccf26c369 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { Operation } from '../../types'; +import { Operations } from '../../constants'; + +interface Agg { + isFormula?: false; +} +interface AggWithFormula { + isFormula: true; + formula: string; +} + +export type AggOptions = { + isFullReference: boolean; + isFieldRequired: boolean; + supportedDataTypes: readonly string[]; +} & (T extends Exclude ? Agg : AggWithFormula); + +// list of supported TSVB aggregation types in Lens +// some of them are supported on the quick functions tab and some of them +// are supported with formulas + +export type Metric = { name: T } & AggOptions; +interface LocalSupportedMetrics { + [METRIC_TYPES.AVG]: Metric; + [METRIC_TYPES.CARDINALITY]: Metric; + [METRIC_TYPES.MEDIAN]: Metric; + [METRIC_TYPES.COUNT]: Metric; + [METRIC_TYPES.DERIVATIVE]: Metric; + [METRIC_TYPES.CUMULATIVE_SUM]: Metric; + [METRIC_TYPES.AVG_BUCKET]: Metric; + [METRIC_TYPES.MAX_BUCKET]: Metric; + [METRIC_TYPES.MIN_BUCKET]: Metric; + [METRIC_TYPES.SUM_BUCKET]: Metric; + [METRIC_TYPES.MAX]: Metric; + [METRIC_TYPES.MIN]: Metric; + [METRIC_TYPES.SUM]: Metric; + [METRIC_TYPES.VALUE_COUNT]: Metric; + [METRIC_TYPES.STD_DEV]: Metric; + [METRIC_TYPES.PERCENTILES]: Metric; + [METRIC_TYPES.SINGLE_PERCENTILE]: Metric; + [METRIC_TYPES.PERCENTILE_RANKS]: Metric; + [METRIC_TYPES.SINGLE_PERCENTILE_RANK]: Metric; + [METRIC_TYPES.TOP_HITS]: Metric; + [METRIC_TYPES.TOP_METRICS]: Metric; + [METRIC_TYPES.MOVING_FN]: Metric; +} + +type UnsupportedSupportedMetrics = Exclude< + METRIC_TYPES | BUCKET_TYPES, + keyof LocalSupportedMetrics +>; +export type SupportedMetrics = LocalSupportedMetrics & { + [Key in UnsupportedSupportedMetrics]?: null; +}; + +const supportedDataTypesWithDate = ['number', 'date', 'histogram'] as const; +const supportedDataTypes = ['number', 'histogram'] as const; +const extendedSupportedDataTypes = [ + 'string', + 'boolean', + 'number', + 'number_range', + 'ip', + 'ip_range', + 'date', + 'date_range', + 'murmur3', +] as const; + +export const SUPPORTED_METRICS: SupportedMetrics = { + avg: { + name: 'average', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: ['number'], + }, + cardinality: { + name: 'unique_count', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: extendedSupportedDataTypes, + }, + count: { + name: 'count', + isFullReference: false, + isFieldRequired: false, + supportedDataTypes: [], + }, + moving_avg: { + name: 'moving_average', + isFullReference: true, + isFieldRequired: true, + supportedDataTypes: ['number'], + }, + derivative: { + name: 'differences', + isFullReference: true, + isFieldRequired: true, + supportedDataTypes: ['number'], + }, + cumulative_sum: { + name: 'cumulative_sum', + isFullReference: true, + isFieldRequired: true, + supportedDataTypes: ['number'], + }, + avg_bucket: { + name: 'formula', + isFullReference: true, + isFieldRequired: true, + isFormula: true, + formula: 'overall_average', + supportedDataTypes: ['number'], + }, + max_bucket: { + name: 'formula', + isFullReference: true, + isFieldRequired: true, + isFormula: true, + formula: 'overall_max', + supportedDataTypes: ['number'], + }, + min_bucket: { + name: 'formula', + isFullReference: true, + isFieldRequired: true, + isFormula: true, + formula: 'overall_min', + supportedDataTypes: ['number'], + }, + sum_bucket: { + name: 'formula', + isFullReference: true, + isFieldRequired: true, + isFormula: true, + formula: 'overall_sum', + supportedDataTypes: ['number'], + }, + max: { + name: 'max', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: supportedDataTypesWithDate, + }, + min: { + name: 'min', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: supportedDataTypesWithDate, + }, + percentiles: { + name: 'percentile', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + single_percentile: { + name: 'percentile', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + percentile_ranks: { + name: 'percentile_rank', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + single_percentile_rank: { + name: 'percentile_rank', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + sum: { + name: 'sum', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + top_hits: { + name: 'last_value', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: extendedSupportedDataTypes, + }, + top_metrics: { + name: 'last_value', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: extendedSupportedDataTypes, + }, + value_count: { + name: 'count', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes: extendedSupportedDataTypes, + }, + std_dev: { + name: 'standard_deviation', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, + median: { + name: 'median', + isFullReference: false, + isFieldRequired: true, + supportedDataTypes, + }, +} as const; + +type SupportedMetricsKeys = keyof LocalSupportedMetrics; + +export type SupportedMetric = typeof SUPPORTED_METRICS[SupportedMetricsKeys]; + +export const getFormulaFromMetric = (metric: SupportedMetric) => { + if (metric.isFormula) { + return metric.formula; + } + return metric.name; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts new file mode 100644 index 0000000000000..0a50390ec469e --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BUCKET_TYPES } from '@kbn/data-plugin/common'; +import uuid from 'uuid'; +import { DataType, TermsParams } from '../../types'; +import { getFieldNameFromField, isColumnWithMeta } from '../utils'; +import { convertToSchemaConfig } from '../../../vis_schemas'; +import { convertMetricToColumns } from '../metrics'; +import { CommonBucketConverterArgs, TermsColumn } from './types'; + +interface OrderByWithAgg { + orderAgg?: TermsParams['orderAgg']; + orderBy: TermsParams['orderBy']; +} + +const getOrderByWithAgg = ({ + agg, + dataView, + aggs, + metricColumns, +}: CommonBucketConverterArgs): OrderByWithAgg | null => { + if (!agg.aggParams) { + return null; + } + + if (agg.aggParams.orderBy === '_key') { + return { orderBy: { type: 'alphabetical' } }; + } + + if (agg.aggParams.orderBy === 'custom') { + if (!agg.aggParams.orderAgg) { + return null; + } + const orderMetricColumn = convertMetricToColumns( + convertToSchemaConfig(agg.aggParams.orderAgg), + dataView, + aggs + ); + if (!orderMetricColumn) { + return null; + } + return { + orderBy: { type: 'custom' }, + orderAgg: orderMetricColumn[0], + }; + } + + const orderAgg = metricColumns.find((column) => { + if (isColumnWithMeta(column)) { + return column.meta.aggId === agg.aggParams?.orderBy; + } + return false; + }); + + if (!orderAgg) { + return null; + } + + return { + orderBy: { type: 'column', columnId: orderAgg.columnId }, + orderAgg, + }; +}; + +export const convertToTermsParams = ({ + agg, + dataView, + aggs, + metricColumns, +}: CommonBucketConverterArgs): TermsParams | null => { + if (!agg.aggParams) { + return null; + } + + const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns }); + if (orderByWithAgg === null) { + return null; + } + + return { + size: agg.aggParams.size ?? 10, + include: agg.aggParams.include + ? Array.isArray(agg.aggParams.include) + ? agg.aggParams.include + : [agg.aggParams.include] + : [], + includeIsRegex: agg.aggParams.includeIsRegex, + exclude: agg.aggParams.exclude + ? Array.isArray(agg.aggParams.exclude) + ? agg.aggParams.exclude + : [agg.aggParams.exclude] + : [], + excludeIsRegex: agg.aggParams.excludeIsRegex, + otherBucket: agg.aggParams.otherBucket, + orderDirection: agg.aggParams.order?.value ?? 'desc', + parentFormat: { id: 'terms' }, + missingBucket: agg.aggParams.missingBucket, + ...orderByWithAgg, + }; +}; + +export const convertToTermsColumn = ( + aggId: string, + { agg, dataView, aggs, metricColumns }: CommonBucketConverterArgs, + label: string, + isSplit: boolean +): TermsColumn | null => { + if (!agg.aggParams?.field) { + return null; + } + const sourceField = getFieldNameFromField(agg.aggParams?.field) ?? 'document'; + const field = dataView.getFieldByName(sourceField); + + if (!field) { + return null; + } + + const params = convertToTermsParams({ agg, dataView, aggs, metricColumns }); + if (!params) { + return null; + } + + return { + columnId: uuid(), + operationType: 'terms', + label, + dataType: (field.type as DataType) ?? undefined, + sourceField: field.name, + isBucketed: true, + isSplit, + params: { ...params }, + timeShift: agg.aggParams?.timeShift, + meta: { aggId }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts new file mode 100644 index 0000000000000..3dfaee67a61e0 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { SchemaConfig, SupportedAggregation } from '../../../types'; +import { + Operation, + BaseColumn as GenericBaseColumn, + Column as BaseColumn, + GenericColumnWithMeta, + PercentileColumn as BasePercentileColumn, + PercentileRanksColumn as BasePercentileRanksColumn, + FormulaColumn as BaseFormulaColumn, + LastValueColumn as BaseLastValueColumn, + AvgColumn as BaseAvgColumn, + CountColumn as BaseCountColumn, + CardinalityColumn as BaseCardinalityColumn, + MaxColumn as BaseMaxColumn, + MedianColumn as BaseMedianColumn, + MinColumn as BaseMinColumn, + SumColumn as BaseSumColumn, + CumulativeSumColumn as BaseCumulativeSumColumn, + MovingAverageColumn as BaseMovingAverageColumn, + DerivativeColumn as BaseDerivativeColumn, + DateHistogramColumn as BaseDateHistogramColumn, + TermsColumn as BaseTermsColumn, + FiltersColumn as BaseFiltersColumn, + RangeColumn as BaseRangeColumn, +} from '../../types'; + +export type MetricsWithField = Exclude< + METRIC_TYPES, + | METRIC_TYPES.FILTERED_METRIC + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.SUM_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.SERIAL_DIFF + | METRIC_TYPES.COUNT +>; + +export type OtherParentPipelineAggs = METRIC_TYPES.DERIVATIVE | METRIC_TYPES.MOVING_FN; + +export type ParentPipelineMetric = METRIC_TYPES.CUMULATIVE_SUM | OtherParentPipelineAggs; + +export type SiblingPipelineMetric = + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.SUM_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.MAX_BUCKET; + +export type BucketColumn = DateHistogramColumn | TermsColumn | FiltersColumn; +export interface CommonColumnConverterArgs< + Agg extends SupportedAggregation = SupportedAggregation +> { + agg: SchemaConfig; + dataView: DataView; +} + +export interface ExtendedColumnConverterArgs< + Agg extends SupportedAggregation = SupportedAggregation +> extends CommonColumnConverterArgs { + aggs: Array>; +} + +export interface CommonBucketConverterArgs< + Agg extends SupportedAggregation = SupportedAggregation +> { + agg: SchemaConfig; + dataView: DataView; + metricColumns: AggBasedColumn[]; + aggs: Array>; +} + +export type AggId = `${string}`; + +export interface Meta { + aggId: AggId; +} + +export type GeneralColumn = Omit, 'operationType' | 'params'>; +export type GeneralColumnWithMeta = GenericColumnWithMeta; +export interface ExtraColumnFields { + isBucketed?: boolean; + isSplit?: boolean; + reducedTimeRange?: string; +} + +export type PercentileColumn = GenericColumnWithMeta; +export type PercentileRanksColumn = GenericColumnWithMeta; + +export type AggBasedColumn = GenericColumnWithMeta | BucketColumn; + +export type FormulaColumn = GenericColumnWithMeta; +export type LastValueColumn = GenericColumnWithMeta; +export type AvgColumn = GenericColumnWithMeta; +export type CountColumn = GenericColumnWithMeta; +export type CardinalityColumn = GenericColumnWithMeta; +export type MaxColumn = GenericColumnWithMeta; +export type MedianColumn = GenericColumnWithMeta; +export type MinColumn = GenericColumnWithMeta; +export type SumColumn = GenericColumnWithMeta; +export type CumulativeSumColumn = GenericColumnWithMeta; +export type MovingAverageColumn = GenericColumnWithMeta; +export type DerivativeColumn = GenericColumnWithMeta; +export type DateHistogramColumn = GenericColumnWithMeta; +export type TermsColumn = GenericColumnWithMeta; +export type FiltersColumn = GenericColumnWithMeta; +export type RangeColumn = GenericColumnWithMeta; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/index.ts new file mode 100644 index 0000000000000..083450c8ff5d1 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './buckets'; +export * from './metrics'; +export * from './convert'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts new file mode 100644 index 0000000000000..276ac54e2fc3d --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts @@ -0,0 +1,258 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataView, DataViewField, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { isFieldValid, SchemaConfig } from '../../..'; +import { Operations } from '../../constants'; +import { isMetricWithField, getStdDeviationFormula, ExtendedColumnConverterArgs } from '../convert'; +import { getFormulaFromMetric, SUPPORTED_METRICS } from '../convert/supported_metrics'; +import { + getFieldNameFromField, + getMetricFromParentPipelineAgg, + isPercentileAgg, + isPercentileRankAgg, + isPipeline, + isStdDevAgg, +} from '../utils'; +import { SIBLING_PIPELINE_AGGS } from '../convert/constants'; + +type PipelineAggs = SchemaConfig< + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.SUM_BUCKET +>; + +type MetricAggsWithoutParams = SchemaConfig< + | METRIC_TYPES.AVG + | METRIC_TYPES.MAX + | METRIC_TYPES.MIN + | METRIC_TYPES.SUM + | METRIC_TYPES.CARDINALITY + | METRIC_TYPES.COUNT +>; + +export const addTimeRangeToFormula = (reducedTimeRange?: string) => { + return reducedTimeRange ? `, reducedTimeRange='${reducedTimeRange}'` : ''; +}; + +const PARENT_PIPELINE_OPS: string[] = [ + Operations.CUMULATIVE_SUM, + Operations.DIFFERENCES, + Operations.MOVING_AVERAGE, +]; + +const METRIC_OPS_WITHOUT_PARAMS: string[] = [ + Operations.AVERAGE, + Operations.MAX, + Operations.MIN, + Operations.SUM, + Operations.UNIQUE_COUNT, + Operations.COUNT, +]; + +const isDataViewField = (field: string | DataViewField): field is DataViewField => { + if (field && typeof field === 'object') { + return true; + } + return false; +}; + +const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { + const aggregation = SUPPORTED_METRICS[agg.aggType]; + if (!aggregation) { + return false; + } + if (isMetricWithField(agg)) { + if (!agg.aggParams?.field) { + return false; + } + const sourceField = getFieldNameFromField(agg.aggParams?.field); + const field = dataView.getFieldByName(sourceField!); + if (!isFieldValid(field, aggregation)) { + return false; + } + } + + return true; +}; + +const getFormulaForAggsWithoutParams = ( + agg: SchemaConfig, + dataView: DataView, + selector: string | undefined, + reducedTimeRange?: string +) => { + const op = SUPPORTED_METRICS[agg.aggType]; + if (!isValidAgg(agg, dataView) || !op) { + return null; + } + + const formula = getFormulaFromMetric(op); + return `${formula}(${selector ?? ''}${addTimeRangeToFormula(reducedTimeRange)})`; +}; + +const getFormulaForPercentileRanks = ( + agg: SchemaConfig, + dataView: DataView, + selector: string | undefined, + reducedTimeRange?: string +) => { + const value = Number(agg.aggId?.split('.')[1]); + const op = SUPPORTED_METRICS[agg.aggType]; + if (!isValidAgg(agg, dataView) || !op) { + return null; + } + + const formula = getFormulaFromMetric(op); + return `${formula}(${selector}, value=${value}${addTimeRangeToFormula(reducedTimeRange)})`; +}; + +const getFormulaForPercentile = ( + agg: SchemaConfig, + dataView: DataView, + selector: string, + reducedTimeRange?: string +) => { + const percentile = Number(agg.aggId?.split('.')[1]); + const op = SUPPORTED_METRICS[agg.aggType]; + if (!isValidAgg(agg, dataView) || !op) { + return null; + } + + const formula = getFormulaFromMetric(op); + return `${formula}(${selector}, percentile=${percentile}${addTimeRangeToFormula( + reducedTimeRange + )})`; +}; + +const getFormulaForSubMetric = ({ + agg, + dataView, + aggs, +}: ExtendedColumnConverterArgs): string | null => { + const op = SUPPORTED_METRICS[agg.aggType]; + if (!op) { + return null; + } + + if ( + PARENT_PIPELINE_OPS.includes(op.name) || + SIBLING_PIPELINE_AGGS.includes(agg.aggType as METRIC_TYPES) + ) { + return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView }); + } + + if (METRIC_OPS_WITHOUT_PARAMS.includes(op.name)) { + const metricAgg = agg as MetricAggsWithoutParams; + return getFormulaForAggsWithoutParams( + metricAgg, + dataView, + metricAgg.aggParams && 'field' in metricAgg.aggParams + ? isDataViewField(metricAgg.aggParams.field) + ? metricAgg.aggParams?.field.displayName + : metricAgg.aggParams?.field + : undefined + ); + } + + if (op.name === Operations.PERCENTILE_RANK) { + const percentileRanksAgg = agg as SchemaConfig; + + return getFormulaForPercentileRanks( + percentileRanksAgg, + dataView, + percentileRanksAgg.aggParams?.field + ); + } + + return null; +}; + +export const getFormulaForPipelineAgg = ({ + agg, + dataView, + aggs, +}: ExtendedColumnConverterArgs< + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.SUM_BUCKET +>) => { + const { aggType } = agg; + const supportedAgg = SUPPORTED_METRICS[aggType]; + if (!supportedAgg) { + return null; + } + + const metricAgg = getMetricFromParentPipelineAgg(agg, aggs); + if (!metricAgg) { + return null; + } + + const subFormula = getFormulaForSubMetric({ + agg: metricAgg, + aggs, + dataView, + }); + if (subFormula === null) { + return null; + } + + if (PARENT_PIPELINE_OPS.includes(supportedAgg.name)) { + const formula = getFormulaFromMetric(supportedAgg); + return `${formula}(${subFormula})`; + } + + return subFormula; +}; + +export const getFormulaForAgg = ({ + agg, + aggs, + dataView, +}: ExtendedColumnConverterArgs) => { + if (isPipeline(agg)) { + return getFormulaForPipelineAgg({ agg, aggs, dataView }); + } + + if (isPercentileAgg(agg)) { + return getFormulaForPercentile( + agg, + dataView, + getFieldNameFromField(agg.aggParams?.field) ?? '' + ); + } + + if (isPercentileRankAgg(agg)) { + return getFormulaForPercentileRanks( + agg, + dataView, + getFieldNameFromField(agg.aggParams?.field) ?? '' + ); + } + + if (isStdDevAgg(agg) && agg.aggId) { + if (!isValidAgg(agg, dataView)) { + return null; + } + return getStdDeviationFormula(agg.aggId, getFieldNameFromField(agg.aggParams?.field) ?? ''); + } + + return getFormulaForAggsWithoutParams( + agg, + dataView, + isMetricWithField(agg) ? getFieldNameFromField(agg.aggParams?.field) ?? '' : '' + ); +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts new file mode 100644 index 0000000000000..c34e328332339 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getFormulaForPipelineAgg } from './formula'; +export { convertMetricToColumns } from './metrics'; +export { getPercentageColumnFormulaColumn } from './percentage_formula'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts new file mode 100644 index 0000000000000..d97f81c4c8240 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { SchemaConfig } from '../../..'; +import { + convertMetricAggregationColumnWithoutSpecialParams, + convertToOtherParentPipelineAggColumns, + convertToPercentileColumn, + convertToPercentileRankColumn, + convertToSiblingPipelineColumns, + convertToStdDeviationFormulaColumns, + convertToLastValueColumn, + convertToCumulativeSumAggColumn, + AggBasedColumn, +} from '../convert'; +import { SUPPORTED_METRICS } from '../convert/supported_metrics'; +import { getValidColumns } from '../utils'; + +export const convertMetricToColumns = ( + agg: SchemaConfig, + dataView: DataView, + aggs: Array> +): AggBasedColumn[] | null => { + const supportedAgg = SUPPORTED_METRICS[agg.aggType]; + if (!supportedAgg) { + return null; + } + + switch (agg.aggType) { + case METRIC_TYPES.AVG: + case METRIC_TYPES.MIN: + case METRIC_TYPES.MAX: + case METRIC_TYPES.SUM: + case METRIC_TYPES.COUNT: + case METRIC_TYPES.CARDINALITY: + case METRIC_TYPES.VALUE_COUNT: + case METRIC_TYPES.MEDIAN: { + const columns = convertMetricAggregationColumnWithoutSpecialParams(supportedAgg, { + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.STD_DEV: { + const columns = convertToStdDeviationFormulaColumns({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.PERCENTILES: { + const columns = convertToPercentileColumn({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.SINGLE_PERCENTILE: { + const columns = convertToPercentileColumn({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.PERCENTILE_RANKS: { + const columns = convertToPercentileRankColumn({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.SINGLE_PERCENTILE_RANK: { + const columns = convertToPercentileRankColumn({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.TOP_HITS: + case METRIC_TYPES.TOP_METRICS: { + const columns = convertToLastValueColumn({ + agg, + dataView, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.CUMULATIVE_SUM: { + const columns = convertToCumulativeSumAggColumn({ + agg, + dataView, + aggs, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.DERIVATIVE: + case METRIC_TYPES.MOVING_FN: { + const columns = convertToOtherParentPipelineAggColumns({ + agg, + dataView, + aggs, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.SUM_BUCKET: + case METRIC_TYPES.MIN_BUCKET: + case METRIC_TYPES.MAX_BUCKET: + case METRIC_TYPES.AVG_BUCKET: { + const columns = convertToSiblingPipelineColumns({ + agg, + dataView, + aggs, + }); + return getValidColumns(columns); + } + case METRIC_TYPES.SERIAL_DIFF: + return null; + default: + return null; + } +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts new file mode 100644 index 0000000000000..773851a770db4 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { createFormulaColumn, ExtendedColumnConverterArgs, FormulaColumn } from '../convert'; +import { getFormulaForAgg } from './formula'; + +export const getPercentageColumnFormulaColumn = ({ + agg, + aggs, + dataView, +}: ExtendedColumnConverterArgs): FormulaColumn | null => { + const metricFormula = getFormulaForAgg({ agg, aggs, dataView }); + if (!metricFormula) { + return null; + } + const formula = `(${metricFormula}) / overall_sum(${metricFormula})`; + + const formulaColumn = createFormulaColumn(formula, agg); + + if (!formulaColumn) { + return null; + } + + return { + ...formulaColumn, + params: { + ...formulaColumn.params, + format: { id: 'percent' }, + }, + label: `${formulaColumn?.label} percentages`, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts new file mode 100644 index 0000000000000..39920e525b791 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isEqual, omit } from 'lodash'; +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { DataViewFieldBase } from '@kbn/es-query'; +import { SchemaConfig } from '../../types'; +import { + AggBasedColumn, + MetricsWithoutSpecialParams, + ParentPipelineMetric, + SiblingPipelineMetric, +} from './convert'; +import { ColumnWithMeta } from '../types'; +import { convertToSchemaConfig } from '../../vis_schemas'; + +type UnwrapArray = T extends Array ? P : T; + +export const getLabel = (agg: SchemaConfig) => { + return agg.aggParams && 'customLabel' in agg.aggParams + ? agg.aggParams.customLabel ?? agg.label + : agg.label; +}; + +export const getLabelForPercentile = (agg: SchemaConfig) => { + return agg.aggParams && 'customLabel' in agg.aggParams && agg.aggParams.customLabel !== '' + ? agg.label + : ''; +}; + +export const getValidColumns = ( + columns: Array | AggBasedColumn | null | undefined +) => { + if (columns && Array.isArray(columns)) { + const nonNullColumns = columns.filter( + (c): c is Exclude, null> => c !== null + ); + + if (nonNullColumns.length !== columns.length) { + return null; + } + + return nonNullColumns; + } + + return columns ? [columns] : null; +}; + +export const getFieldNameFromField = ( + field: DataViewField | DataViewFieldBase | string | undefined +) => { + if (!field) { + return null; + } + + if (typeof field === 'string') { + return field; + } + + return field.name; +}; + +export const isSchemaConfig = (agg: SchemaConfig | IAggConfig): agg is SchemaConfig => { + if ((agg as SchemaConfig).aggType) { + return true; + } + return false; +}; + +export const isColumnWithMeta = ( + column: AggBasedColumn | ColumnWithMeta +): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +const SIBBLING_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.AVG_BUCKET, + METRIC_TYPES.SUM_BUCKET, + METRIC_TYPES.MAX_BUCKET, + METRIC_TYPES.MIN_BUCKET, +]; + +const PARENT_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.CUMULATIVE_SUM, + METRIC_TYPES.DERIVATIVE, + METRIC_TYPES.MOVING_FN, +]; + +const AGGS_WITHOUT_SPECIAL_RARAMS: string[] = [ + METRIC_TYPES.AVG, + METRIC_TYPES.COUNT, + METRIC_TYPES.MAX, + METRIC_TYPES.MIN, + METRIC_TYPES.SUM, + METRIC_TYPES.MEDIAN, + METRIC_TYPES.CARDINALITY, + METRIC_TYPES.VALUE_COUNT, +]; + +const PIPELINE_AGGS: string[] = [...SIBBLING_PIPELINE_AGGS, ...PARENT_PIPELINE_AGGS]; + +export const isSiblingPipeline = ( + metric: SchemaConfig +): metric is SchemaConfig => { + return SIBBLING_PIPELINE_AGGS.includes(metric.aggType); +}; + +export const isPipeline = ( + metric: SchemaConfig +): metric is SchemaConfig< + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.SUM_BUCKET +> => { + return PIPELINE_AGGS.includes(metric.aggType); +}; + +export const isMetricAggWithoutParams = ( + metric: SchemaConfig +): metric is SchemaConfig => { + return AGGS_WITHOUT_SPECIAL_RARAMS.includes(metric.aggType); +}; + +export const isPercentileAgg = ( + metric: SchemaConfig +): metric is SchemaConfig => { + return metric.aggType === METRIC_TYPES.PERCENTILES; +}; + +export const isPercentileRankAgg = ( + metric: SchemaConfig +): metric is SchemaConfig => { + return metric.aggType === METRIC_TYPES.PERCENTILE_RANKS; +}; + +export const isStdDevAgg = (metric: SchemaConfig): metric is SchemaConfig => { + return metric.aggType === METRIC_TYPES.STD_DEV; +}; + +export const getCutomBucketsFromSiblingAggs = (metrics: SchemaConfig[]) => { + return metrics.reduce((acc, metric) => { + if ( + isSiblingPipeline(metric) && + metric.aggParams?.customBucket && + acc.every( + (bucket) => + !isEqual( + omit(metric.aggParams?.customBucket?.serialize(), ['id']), + omit(bucket.serialize(), ['id']) + ) + ) + ) { + acc.push(metric.aggParams.customBucket); + } + + return acc; + }, []); +}; + +export const getMetricFromParentPipelineAgg = ( + agg: SchemaConfig, + aggs: Array> +): SchemaConfig | null => { + if (!agg.aggParams) { + return null; + } + + if (isSiblingPipeline(agg)) { + if (agg.aggParams.customMetric) { + return convertToSchemaConfig(agg.aggParams.customMetric); + } + return null; + } + + const { customMetric, metricAgg } = agg.aggParams; + if (!customMetric && metricAgg === 'custom') { + return null; + } + + let metric; + if (!customMetric) { + metric = aggs.find(({ aggId }) => aggId === metricAgg); + } else { + metric = convertToSchemaConfig(customMetric); + } + + return metric as SchemaConfig; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/columns.ts b/src/plugins/visualizations/common/convert_to_lens/types/columns.ts index 6817e216e762d..735fe3f33b527 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/columns.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/columns.ts @@ -109,5 +109,9 @@ export type AnyColumnWithReferences = export type Column = AnyColumnWithReferences | AnyColumnWithSourceField | FiltersColumn; -export type ColumnWithMeta = Col & - (Meta extends undefined ? undefined : { meta: Meta }); +export type GenericColumnWithMeta< + Col extends Column | {}, + Meta extends {} | unknown = undefined +> = Col & (Meta extends undefined ? undefined : { meta: Meta }); + +export type ColumnWithMeta = GenericColumnWithMeta; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/common.ts b/src/plugins/visualizations/common/convert_to_lens/types/common.ts index 974326834ce2a..d792467e298d0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/common.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/common.ts @@ -29,7 +29,7 @@ export interface FilterQuery { export interface Filter { input: FilterQuery; - label: string; + label?: string; } export interface Range { diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 4702ebae80826..8c348fec4c2fa 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -170,4 +170,33 @@ export interface XYConfiguration { valuesInLegend?: boolean; } -export type Configuration = XYConfiguration; +export interface SortingState { + columnId: string | undefined; + direction: 'asc' | 'desc' | 'none'; +} + +export interface PagingState { + size: number; + enabled: boolean; +} + +export interface ColumnState { + columnId: string; + summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'; + alignment?: 'left' | 'right' | 'center'; + collapseFn?: string; +} + +export interface TableVisConfiguration { + columns: ColumnState[]; + layerId: string; + layerType: 'data'; + sorting?: SortingState; + rowHeight?: 'auto' | 'single' | 'custom'; + headerRowHeight?: 'auto' | 'single' | 'custom'; + rowHeightLines?: number; + headerRowHeightLines?: number; + paging?: PagingState; +} + +export type Configuration = XYConfiguration | TableVisConfiguration; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/context.ts b/src/plugins/visualizations/common/convert_to_lens/types/context.ts index 037d1c8c96a1c..c894f95596983 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/context.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/context.ts @@ -20,4 +20,5 @@ export interface NavigateToLensContext layers: Layer[]; type: string; configuration: T; + indexPatternIds: string[]; } diff --git a/src/plugins/visualizations/common/convert_to_lens/types/params.ts b/src/plugins/visualizations/common/convert_to_lens/types/params.ts index 6fc49d30fe02e..d66822921fb19 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/params.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/params.ts @@ -6,9 +6,13 @@ * Side Public License, v 1. */ +import { $Values } from '@kbn/utility-types'; +import { RANGE_MODES } from '../constants'; import { Column } from './columns'; import { Filter, NumberValueFormat } from './common'; +export type RangeMode = $Values; + export interface FormatParams { format?: NumberValueFormat; } @@ -17,21 +21,6 @@ export interface FiltersParams { filters: Filter[]; } -export interface RangeParams extends FormatParams { - type: 'histogram' | 'range'; - maxBars: 'auto' | number; - ranges: Range[]; - includeEmptyRows?: boolean; - parentFormat?: { - id: string; - params?: { - id?: string; - template?: string; - replaceInfinity?: boolean; - }; - }; -} - export interface TermsParams extends FormatParams { size: number; include?: string[] | number[]; @@ -58,6 +47,22 @@ export interface DateHistogramParams { dropPartials?: boolean; } +interface Range { + from: number | null; + to: number | null; + label?: string; +} +export interface RangeParams extends FormatParams { + type: RangeMode; + maxBars: 'auto' | number; + ranges: Range[]; + includeEmptyRows?: boolean; + parentFormat?: { + id: string; + params?: { id?: string; template?: string; replaceInfinity?: boolean }; + }; +} + export type MinParams = FormatParams; export type MaxParams = FormatParams; export type AvgParams = FormatParams; diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 1beba70bc2a1b..3536282d830d3 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -6,8 +6,28 @@ * Side Public License, v 1. */ -import { XYAnnotationsLayerConfig, XYLayerConfig } from './types'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { SupportedMetric } from './lib/convert/supported_metrics'; +import { Layer, XYAnnotationsLayerConfig, XYLayerConfig } from './types'; export const isAnnotationsLayer = ( layer: Pick ): layer is XYAnnotationsLayerConfig => layer.layerType === 'annotations'; + +export const getIndexPatternIds = (layers: Layer[]) => + layers.map(({ indexPatternId }) => indexPatternId); + +export const isFieldValid = ( + field: DataViewField | undefined, + aggregation: SupportedMetric +): field is DataViewField => { + if (!field && aggregation.isFieldRequired) { + return false; + } + + if (field && (!field.aggregatable || !aggregation.supportedDataTypes.includes(field.type))) { + return false; + } + + return true; +}; diff --git a/src/plugins/visualizations/common/index.ts b/src/plugins/visualizations/common/index.ts index dde60809ea593..90ea3f6dca0a1 100644 --- a/src/plugins/visualizations/common/index.ts +++ b/src/plugins/visualizations/common/index.ts @@ -14,5 +14,6 @@ export * from './types'; export * from './utils'; export * from './expression_functions'; export * from './convert_to_lens'; +export { convertToSchemaConfig } from './vis_schemas'; export { LegendSize, LegendSizeToPixels, DEFAULT_LEGEND_SIZE } from './constants'; diff --git a/src/plugins/visualizations/common/types.ts b/src/plugins/visualizations/common/types.ts index 8aa03470b2094..12407f0966cd7 100644 --- a/src/plugins/visualizations/common/types.ts +++ b/src/plugins/visualizations/common/types.ts @@ -7,7 +7,14 @@ */ import type { SerializableRecord } from '@kbn/utility-types'; -import type { AggConfigSerialized, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { + AggParamsMapping, + AggConfigSerialized, + SerializedSearchSourceFields, + METRIC_TYPES, + BUCKET_TYPES, +} from '@kbn/data-plugin/common'; +import type { SerializedFieldFormat } from '@kbn/field-formats-plugin/common'; import type { SavedObjectAttributes } from '@kbn/core/types'; export interface VisParams { @@ -49,3 +56,26 @@ export interface SerializedVis { uiState?: any; data: SerializedVisData; } +interface SchemaConfigParams { + precision?: number; + useGeocentroid?: boolean; +} + +export type SupportedAggregation = METRIC_TYPES | BUCKET_TYPES; + +type SchemasByAggs = { + [Agg in Aggs]: GenericSchemaConfig; +}[Aggs]; + +export interface GenericSchemaConfig { + accessor: number; + label: string; + format: SerializedFieldFormat; + params: SchemaConfigParams; + aggType: Agg; + aggId?: string; + aggParams?: AggParamsMapping[Agg]; +} + +export type SchemaConfig = + SchemasByAggs; diff --git a/src/plugins/visualizations/common/vis_schemas.ts b/src/plugins/visualizations/common/vis_schemas.ts new file mode 100644 index 0000000000000..cdccc7902e08d --- /dev/null +++ b/src/plugins/visualizations/common/vis_schemas.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig, SupportedAggregation } from './types'; + +interface SchemaConfigParams { + precision?: number; + useGeocentroid?: boolean; +} + +export function convertToSchemaConfig(agg: IAggConfig): SchemaConfig { + const aggType = agg.type.name as SupportedAggregation; + const hasSubAgg = [ + 'derivative', + 'moving_avg', + 'serial_diff', + 'cumulative_sum', + 'sum_bucket', + 'avg_bucket', + 'min_bucket', + 'max_bucket', + ].includes(aggType); + + const formatAgg = hasSubAgg + ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg) + : agg; + + const params: SchemaConfigParams = {}; + + if (aggType === 'geohash_grid') { + params.precision = agg.params.precision; + params.useGeocentroid = agg.params.useGeocentroid; + } + + const label = agg.makeLabel && agg.makeLabel(); + return { + accessor: 0, + format: formatAgg.toSerializedFieldFormat(), + params, + label, + aggType, + aggId: agg.id, + aggParams: agg.params, + } as SchemaConfig; +} diff --git a/src/plugins/visualizations/public/convert_to_lens/datasource.ts b/src/plugins/visualizations/public/convert_to_lens/datasource.ts new file mode 100644 index 0000000000000..502632651170d --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/datasource.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const getDataViewByIndexPatternId = async ( + indexPatternId: string | undefined, + dataViews: DataViewsPublicPluginStart +) => { + try { + return indexPatternId ? await dataViews.get(indexPatternId) : await dataViews.getDefault(); + } catch (err) { + return null; + } +}; diff --git a/src/plugins/visualizations/public/convert_to_lens/index.ts b/src/plugins/visualizations/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..9cd2ba2768ad7 --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getColumnsFromVis } from './schemas'; +export { getPercentageColumnFormulaColumn } from '../../common/convert_to_lens/lib'; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts new file mode 100644 index 0000000000000..dccd579d95dba --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -0,0 +1,257 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + AggConfig, + AggConfigOptions, + AggConfigs, + AggConfigsOptions, + GetConfigFn, +} from '@kbn/data-plugin/common'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import type { Vis } from '../vis'; +import { getColumnsFromVis } from './schemas'; + +const mockConvertMetricToColumns = jest.fn(); +const mockConvertBucketToColumns = jest.fn(); +const mockGetCutomBucketsFromSiblingAggs = jest.fn(); +const mockGetVisSchemas = jest.fn(); + +const mockGetBucketCollapseFn = jest.fn(); +const mockGetBucketColumns = jest.fn(); +const mockGetColumnIds = jest.fn(); +const mockGetColumnsWithoutReferenced = jest.fn(); +const mockGetMetricsWithoutDuplicates = jest.fn(); +const mockIsValidVis = jest.fn(); +const mockSortColumns = jest.fn(); + +jest.mock('../../common/convert_to_lens/lib/metrics', () => ({ + convertMetricToColumns: jest.fn(() => mockConvertMetricToColumns()), +})); + +jest.mock('../../common/convert_to_lens/lib/buckets', () => ({ + convertBucketToColumns: jest.fn(() => mockConvertBucketToColumns()), +})); + +jest.mock('../../common/convert_to_lens/lib/utils', () => ({ + getCutomBucketsFromSiblingAggs: jest.fn(() => mockGetCutomBucketsFromSiblingAggs()), +})); + +jest.mock('../vis_schemas', () => ({ + getVisSchemas: jest.fn(() => mockGetVisSchemas()), +})); + +jest.mock('./utils', () => ({ + getBucketCollapseFn: jest.fn(() => mockGetBucketCollapseFn()), + getBucketColumns: jest.fn(() => mockGetBucketColumns()), + getColumnIds: jest.fn(() => mockGetColumnIds()), + getColumnsWithoutReferenced: jest.fn(() => mockGetColumnsWithoutReferenced()), + getMetricsWithoutDuplicates: jest.fn(() => mockGetMetricsWithoutDuplicates()), + isValidVis: jest.fn(() => mockIsValidVis()), + sortColumns: jest.fn(() => mockSortColumns()), +})); + +describe('getColumnsFromVis', () => { + const dataServiceMock = dataPluginMock.createStartContract(); + const dataView = stubLogstashDataView; + const aggConfigs = new AggConfigs( + dataView, + [], + {} as AggConfigsOptions, + (() => ({})) as GetConfigFn + ); + const aggConfig = new AggConfig(aggConfigs, {} as AggConfigOptions); + + const vis = {} as Vis; + beforeEach(() => { + jest.clearAllMocks(); + mockGetVisSchemas.mockReturnValue({}); + mockIsValidVis.mockReturnValue(true); + }); + + test('should return null if vis is not valid', () => { + mockIsValidVis.mockReturnValue(false); + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(0); + }); + + test('should return null if multiple different sibling aggs was provided', () => { + const buckets: AggConfig[] = [aggConfig, aggConfig]; + mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(0); + }); + + test('should return null if one sibling agg was provided and it is not supported', () => { + const buckets: AggConfig[] = [aggConfig]; + mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); + mockConvertBucketToColumns.mockReturnValue(null); + mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertBucketToColumns).toBeCalledTimes(1); + expect(mockGetBucketColumns).toBeCalledTimes(0); + }); + + test('should return null if metrics are not supported', () => { + const buckets: AggConfig[] = [aggConfig]; + mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); + mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); + mockConvertMetricToColumns.mockReturnValue([null, {}]); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + expect(mockGetBucketColumns).toBeCalledTimes(0); + }); + + test('should return null if buckets are not supported', () => { + const buckets: AggConfig[] = [aggConfig]; + mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); + mockConvertBucketToColumns.mockReturnValue({}); + mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); + mockConvertMetricToColumns.mockReturnValue([{}]); + mockGetBucketColumns.mockReturnValue(null); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + expect(mockGetBucketColumns).toBeCalledTimes(1); + }); + + test('should return null if splits are not supported', () => { + const buckets: AggConfig[] = [aggConfig]; + mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); + mockConvertBucketToColumns.mockReturnValue({}); + mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); + mockConvertMetricToColumns.mockReturnValue([{}]); + mockGetBucketColumns.mockReturnValueOnce([{}]); + mockGetBucketColumns.mockReturnValueOnce(null); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toBeNull(); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + expect(mockGetBucketColumns).toBeCalledTimes(2); + expect(mockSortColumns).toBeCalledTimes(0); + }); + + test('should return columns', () => { + const buckets: AggConfig[] = [aggConfig]; + const bucketColumns = [ + { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: 'agg-id-1' }, + }, + ]; + const metrics = [ + { + sourceField: 'some-field', + columnId: 'col2', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'col-id-3' }, + }, + ]; + + const columnsWithoutReferenced = ['col2']; + const metricId = 'metric1'; + const bucketId = 'bucket1'; + const bucketCollapseFn = 'max'; + + mockGetCutomBucketsFromSiblingAggs.mockReturnValue([]); + mockGetMetricsWithoutDuplicates.mockReturnValue(metrics); + mockConvertMetricToColumns.mockReturnValue(metrics); + mockConvertBucketToColumns.mockReturnValue(bucketColumns); + mockGetBucketColumns.mockReturnValue(bucketColumns); + mockGetColumnsWithoutReferenced.mockReturnValue(columnsWithoutReferenced); + mockSortColumns.mockReturnValue([...metrics, ...buckets]); + mockGetColumnIds.mockReturnValueOnce([metricId]); + mockGetColumnIds.mockReturnValueOnce([bucketId]); + mockGetBucketCollapseFn.mockReturnValueOnce(bucketCollapseFn); + + const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { + splits: [], + buckets: [], + }); + + expect(result).toEqual({ + bucketCollapseFn, + buckets: [bucketId], + columns: [...metrics, ...buckets], + columnsWithoutReferenced, + metrics: [metricId], + }); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + expect(mockGetBucketColumns).toBeCalledTimes(2); + expect(mockSortColumns).toBeCalledTimes(1); + expect(mockGetColumnsWithoutReferenced).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts new file mode 100644 index 0000000000000..372e434ca8868 --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DataView } from '@kbn/data-views-plugin/common'; +import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; +import { AggBasedColumn, SchemaConfig } from '../../common'; +import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; +import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; +import { getCutomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; +import type { Vis } from '../types'; +import { getVisSchemas, Schemas } from '../vis_schemas'; +import { + getBucketCollapseFn, + getBucketColumns, + getColumnIds, + getColumnsWithoutReferenced, + getMetricsWithoutDuplicates, + isValidVis, + sortColumns, +} from './utils'; + +export const getColumnsFromVis = ( + vis: Vis, + timefilter: TimefilterContract, + dataView: DataView, + { splits, buckets }: { splits: Array; buckets: Array } = { + splits: [], + buckets: [], + }, + config?: { + dropEmptyRowsInDateHistogram?: boolean; + } +) => { + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + if (!isValidVis(visSchemas)) { + return null; + } + + const customBuckets = getCutomBucketsFromSiblingAggs(visSchemas.metric); + + // doesn't support sibbling pipeline aggs with different bucket aggs + if (customBuckets.length > 1) { + return null; + } + + const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); + const aggs = metricsWithoutDuplicates as Array>; + + const metricColumns = metricsWithoutDuplicates.flatMap((m) => + convertMetricToColumns(m, dataView, aggs) + ); + + if (metricColumns.includes(null)) { + return null; + } + const metrics = metricColumns as AggBasedColumn[]; + const customBucketColumns = []; + + if (customBuckets.length) { + const customBucketColumn = convertBucketToColumns( + { agg: customBuckets[0], dataView, metricColumns: metrics, aggs }, + false, + config?.dropEmptyRowsInDateHistogram + ); + if (!customBucketColumn) { + return null; + } + customBucketColumns.push(customBucketColumn); + } + + const bucketColumns = getBucketColumns( + visSchemas, + buckets, + dataView, + false, + metricColumns as AggBasedColumn[], + config?.dropEmptyRowsInDateHistogram + ); + if (!bucketColumns) { + return null; + } + + const splitBucketColumns = getBucketColumns( + visSchemas, + splits, + dataView, + true, + metricColumns as AggBasedColumn[], + config?.dropEmptyRowsInDateHistogram + ); + if (!splitBucketColumns) { + return null; + } + + const columns = sortColumns( + [...metrics, ...bucketColumns, ...splitBucketColumns, ...customBucketColumns], + visSchemas, + [...buckets, ...splits], + metricsWithoutDuplicates + ); + + const columnsWithoutReferenced = getColumnsWithoutReferenced(columns); + + return { + metrics: getColumnIds(metrics), + buckets: getColumnIds([...bucketColumns, ...splitBucketColumns, ...customBucketColumns]), + bucketCollapseFn: getBucketCollapseFn(visSchemas.metric, customBucketColumns), + columnsWithoutReferenced, + columns, + }; +}; diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts new file mode 100644 index 0000000000000..734a250a2972c --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -0,0 +1,610 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { + AggBasedColumn, + CounterRateColumn, + GenericColumnWithMeta, + SchemaConfig, + SupportedAggregation, +} from '../../common'; +import { + AvgColumn, + CountColumn, + MaxColumn, + DateHistogramColumn, + Meta, +} from '../../common/convert_to_lens/lib'; +import { + getBucketCollapseFn, + getBucketColumns, + getColumnIds, + getColumnsWithoutReferenced, + getMetricsWithoutDuplicates, + isReferenced, + isValidVis, + sortColumns, +} from './utils'; +import { Schemas } from '../vis_schemas'; + +const mockConvertBucketToColumns = jest.fn(); + +jest.mock('../../common/convert_to_lens/lib/buckets', () => ({ + convertBucketToColumns: jest.fn(() => mockConvertBucketToColumns()), +})); + +describe('isReferenced', () => { + const columnId = 'col1'; + const references = ['col0', columnId, '----']; + + test.each<[string, [string, string[]], boolean]>([ + ['false if referneces are empty', [columnId, []], false], + ['false if columnId is not in referneces', ['some new column', references], false], + ['true if columnId is in referneces', [columnId, references], true], + ])('should return %s', (_, input, expected) => { + expect(isReferenced(...input)).toBe(expected); + }); +}); + +describe('getColumnsWithoutReferenced', () => { + const column: AvgColumn = { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'some id' }, + }; + + const referencedColumn: CountColumn = { + sourceField: 'document', + columnId: 'col1', + operationType: 'count', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'some id' }, + }; + + const columnWithReference: GenericColumnWithMeta = { + references: [referencedColumn.columnId], + columnId: 'col2', + operationType: 'counter_rate', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'some id' }, + }; + + const columns: AggBasedColumn[] = [column, columnWithReference, referencedColumn]; + const columnsWithoutReferenced = [column, columnWithReference]; + test.each<[string, AggBasedColumn[], AggBasedColumn[]]>([ + ['filter out referenced column if contains', columns, columnsWithoutReferenced], + [ + 'return the same array, if no referenced columns', + columnsWithoutReferenced, + columnsWithoutReferenced, + ], + ])('should %s', (_, input, expected) => { + expect(getColumnsWithoutReferenced(input)).toEqual(expected); + }); +}); + +describe('getBucketCollapseFn', () => { + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + const metric2: SchemaConfig = { + ...metric1, + aggType: METRIC_TYPES.AVG_BUCKET, + }; + + const metric3: SchemaConfig = { + ...metric1, + aggType: METRIC_TYPES.MAX_BUCKET, + }; + + const metric4: SchemaConfig = { + ...metric1, + aggType: METRIC_TYPES.MIN_BUCKET, + }; + + const metric5: SchemaConfig = { + ...metric1, + aggType: METRIC_TYPES.SUM_BUCKET, + }; + + const customBucketColum: AggBasedColumn = { + columnId: 'bucket-1', + operationType: 'date_histogram', + sourceField: 'test', + isBucketed: true, + isSplit: false, + params: { + interval: '1h', + }, + dataType: 'date', + meta: { + aggId: '1', + }, + }; + + test.each< + [ + string, + [Array>, AggBasedColumn[]], + Record + ] + >([ + ['avg', [[metric1, metric2], [customBucketColum]], { [customBucketColum.columnId]: 'avg' }], + ['max', [[metric1, metric3], [customBucketColum]], { [customBucketColum.columnId]: 'max' }], + ['min', [[metric1, metric4], [customBucketColum]], { [customBucketColum.columnId]: 'min' }], + ['sum', [[metric1, metric5], [customBucketColum]], { [customBucketColum.columnId]: 'sum' }], + [ + 'undefined if no sibling pipeline agg is provided', + [[metric1], [customBucketColum]], + { [customBucketColum.columnId]: undefined }, + ], + ])('should return%s', (_, input, expected) => { + expect(getBucketCollapseFn(...input)).toEqual(expected); + }); +}); + +describe('getBucketColumns', () => { + const dataView = stubLogstashDataView; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should skip empty schemas and return empty array', () => { + const metricKey = 'metric'; + const bucketKey = 'group'; + const keys = [metricKey, bucketKey]; + + const visSchemas: Schemas = { + [metricKey]: [], + [bucketKey]: [], + }; + + expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([]); + expect(mockConvertBucketToColumns).toBeCalledTimes(0); + }); + + test('should return null if metric is invalid', () => { + const metricKey = 'metric'; + const bucketKey = 'group'; + const keys = [metricKey, bucketKey]; + + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1], + [bucketKey]: [], + }; + mockConvertBucketToColumns.mockReturnValueOnce(null); + + expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(mockConvertBucketToColumns).toBeCalledTimes(1); + }); + + test('should return null if no buckets are returned', () => { + const metricKey = 'metric'; + const bucketKey = 'group'; + const keys = [metricKey, bucketKey]; + + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1], + [bucketKey]: [], + }; + mockConvertBucketToColumns.mockReturnValueOnce([null]); + + expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(mockConvertBucketToColumns).toBeCalledTimes(1); + }); + test('should return columns', () => { + const metricKey = 'metric'; + const bucketKey = 'group'; + const keys = [metricKey, bucketKey]; + + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + const column: AvgColumn = { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'some id' }, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1], + [bucketKey]: [metric1], + }; + + const returnValue = [column]; + + mockConvertBucketToColumns.mockReturnValue(returnValue); + + expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([ + ...returnValue, + ...returnValue, + ]); + expect(mockConvertBucketToColumns).toBeCalledTimes(2); + }); +}); + +describe('isValidVis', () => { + const metricKey = 'metric'; + const bucketKey = 'group'; + + test("should return true, if metrics doesn't contain sibling aggs", () => { + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1], + [bucketKey]: [], + }; + + expect(isValidVis(visSchemas)).toBeTruthy(); + }); + + test('should return true, if metrics contain only one sibling agg', () => { + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1], + [bucketKey]: [], + }; + + expect(isValidVis(visSchemas)).toBeTruthy(); + }); + + test('should return true, if metrics contain multiple sibling aggs of the same type', () => { + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1, metric1], + [bucketKey]: [], + }; + + expect(isValidVis(visSchemas)).toBeTruthy(); + }); + + test('should return false, if metrics contain multiple sibling aggs with different types', () => { + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + }; + const metric2: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.SUM_BUCKET, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1, metric2], + [bucketKey]: [], + }; + + expect(isValidVis(visSchemas)).toBeFalsy(); + }); +}); + +describe('getMetricsWithoutDuplicates', () => { + const duplicatedAggId = 'some-agg-id'; + const baseMetric = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + const metric1: SchemaConfig = { + ...baseMetric, + aggType: METRIC_TYPES.AVG, + aggId: duplicatedAggId, + }; + + const metric2: SchemaConfig = { + ...baseMetric, + aggType: METRIC_TYPES.SUM, + aggId: 'some-other-id', + }; + + const metric3: SchemaConfig = { + ...baseMetric, + aggType: METRIC_TYPES.MAX, + aggId: duplicatedAggId, + }; + + test('should remove aggs with same aggIds', () => { + expect(getMetricsWithoutDuplicates([metric1, metric2, metric3])).toEqual([metric1, metric2]); + }); + + test('should return array if no duplicates', () => { + expect(getMetricsWithoutDuplicates([metric2, metric3])).toEqual([metric2, metric3]); + }); +}); + +describe('sortColumns', () => { + const aggId1 = '0_agg_id'; + const aggId2 = '1_agg_id'; + const aggId3 = '2_agg_id'; + const aggId4 = '3_agg_id'; + const aggId5 = '4_agg_id'; + + const column1: AvgColumn = { + sourceField: 'some-field', + columnId: 'col0', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: aggId1 }, + }; + + const column2: CountColumn = { + sourceField: 'document', + columnId: 'col1', + operationType: 'count', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: aggId2 }, + }; + + const column3: MaxColumn = { + sourceField: 'some-field', + columnId: 'col2', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: aggId3 }, + }; + + const column4: DateHistogramColumn = { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: aggId4 }, + }; + + const column5: DateHistogramColumn = { + sourceField: 'some-field', + columnId: 'col4', + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: aggId5 }, + }; + + const metricKey = 'metric'; + const bucketKey = 'group'; + + const baseMetric = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + const metric1: SchemaConfig = { + ...baseMetric, + accessor: 1, + aggType: METRIC_TYPES.AVG, + aggId: aggId1, + }; + const metric2: SchemaConfig = { + ...baseMetric, + accessor: 2, + aggType: METRIC_TYPES.COUNT, + aggId: aggId2, + }; + const metric3: SchemaConfig = { + ...baseMetric, + accessor: 3, + aggType: METRIC_TYPES.MAX, + aggId: aggId3, + }; + + const bucket1: SchemaConfig = { + ...baseMetric, + accessor: 4, + aggType: BUCKET_TYPES.DATE_HISTOGRAM, + aggId: aggId4, + }; + + const bucket2: SchemaConfig = { + ...baseMetric, + accessor: 5, + aggType: BUCKET_TYPES.DATE_HISTOGRAM, + aggId: aggId5, + }; + + const visSchemas: Schemas = { + [metricKey]: [metric1, metric2, metric3], + [bucketKey]: [bucket1, bucket2], + }; + const columns: AggBasedColumn[] = [column4, column3, column2, column5, column1]; + const metricsWithoutDuplicates: Array> = [ + metric1, + metric2, + metric3, + ]; + const keys = [bucketKey]; + + test('should remove aggs with same aggIds', () => { + expect(sortColumns(columns, visSchemas, keys, metricsWithoutDuplicates)).toEqual([ + column1, + column2, + column3, + column4, + column5, + ]); + }); +}); + +describe('getColumnIds', () => { + const colId1 = '0_agg_id'; + const colId2 = '1_agg_id'; + const colId3 = '2_agg_id'; + const colId4 = '3_agg_id'; + + const column1: AvgColumn = { + sourceField: 'some-field', + columnId: colId1, + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: colId1 }, + }; + + const column2: CountColumn = { + sourceField: 'document', + columnId: colId2, + operationType: 'count', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: colId2 }, + }; + + const column3: MaxColumn = { + sourceField: 'some-field', + columnId: colId3, + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: colId3 }, + }; + + const column4: DateHistogramColumn = { + sourceField: 'some-field', + columnId: colId4, + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: colId4 }, + }; + + test('return columnIds', () => { + expect(getColumnIds([column1, column2, column3, column4])).toEqual([ + colId1, + colId2, + colId3, + colId4, + ]); + }); +}); diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts new file mode 100644 index 0000000000000..a5337568b5568 --- /dev/null +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DataView } from '@kbn/data-views-plugin/common'; +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { AggBasedColumn, SchemaConfig, SupportedAggregation } from '../../common'; +import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; +import { isSiblingPipeline } from '../../common/convert_to_lens/lib/utils'; +import { Schemas } from '../vis_schemas'; + +export const isReferenced = (columnId: string, references: string[]) => + references.includes(columnId); + +export const getColumnsWithoutReferenced = (columns: AggBasedColumn[]) => { + const references = Object.values(columns).flatMap((col) => + 'references' in col ? col.references : [] + ); + return columns.filter(({ columnId }) => !isReferenced(columnId, references)); +}; + +export const getBucketCollapseFn = ( + metrics: Array>, + customBucketColumns: AggBasedColumn[] +) => { + const collapseFn = metrics.find((m) => isSiblingPipeline(m))?.aggType.split('_')[0]; + return customBucketColumns.length + ? { + [customBucketColumns[0].columnId]: collapseFn, + } + : {}; +}; + +export const getBucketColumns = ( + visSchemas: Schemas, + keys: Array, + dataView: DataView, + isSplit: boolean, + metricColumns: AggBasedColumn[], + dropEmptyRowsInDateHistogram: boolean = false +) => { + const columns: AggBasedColumn[] = []; + for (const key of keys) { + if (visSchemas[key] && visSchemas[key]?.length) { + const bucketColumns = visSchemas[key]?.flatMap((m) => + convertBucketToColumns( + { + agg: m, + dataView, + metricColumns, + aggs: visSchemas.metric as Array>, + }, + isSplit, + dropEmptyRowsInDateHistogram + ) + ); + if (!bucketColumns || bucketColumns.includes(null)) { + return null; + } + columns.push(...(bucketColumns as AggBasedColumn[])); + } + } + return columns; +}; + +export const isValidVis = (visSchemas: Schemas) => { + const { metric } = visSchemas; + const siblingPipelineAggs = metric.filter((m) => isSiblingPipeline(m)); + + if (!siblingPipelineAggs.length) { + return true; + } + + // doesn't support mixed sibling pipeline aggregations + if (siblingPipelineAggs.some((agg) => agg.aggType !== siblingPipelineAggs[0].aggType)) { + return false; + } + + return true; +}; + +export const getMetricsWithoutDuplicates = (metrics: Array>) => + metrics.reduce>>((acc, metric) => { + if (metric.aggId && !acc.some((m) => m.aggId === metric.aggId)) { + acc.push(metric); + } + return acc; + }, []); + +export const sortColumns = ( + columns: AggBasedColumn[], + visSchemas: Schemas, + bucketsAndSplitsKeys: Array, + metricsWithoutDuplicates: Array> +) => { + const aggOrderMap: Record = ['metric', ...bucketsAndSplitsKeys].reduce( + (acc, key) => { + return { + ...acc, + ...(key === 'metric' ? metricsWithoutDuplicates : visSchemas[key])?.reduce( + (newAcc, schema) => { + newAcc[schema.aggId] = schema.accessor; + return newAcc; + }, + {} + ), + }; + }, + {} + ); + + return columns.sort( + (a, b) => + Number(aggOrderMap[a.meta.aggId.split('-')[0]]) - + Number(aggOrderMap[b.meta.aggId.split('-')[0]]) + ); +}; + +export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index fcdcede24409e..26746f8d23200 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -569,6 +569,7 @@ export class VisualizeEmbeddable }, variables: { embeddableTitle: this.getTitle(), + ...(await this.vis.type.getExpressionVariables?.(this.vis, this.timefilter)), }, searchSessionId: this.input.searchSessionId, syncColors: this.input.syncColors, diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 39fc5e570a9cf..693bbd26a2e2f 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -30,7 +30,7 @@ export type VisualizeEmbeddableFactoryContract = PublicContract; export type { VisualizeInput } from './embeddable'; export type { VisualizeEmbeddable } from './embeddable'; -export type { SchemaConfig } from './vis_schemas'; +export type { SchemaConfig } from '../common/types'; export { updateOldState } from './legacy/vis_update_state'; export type { PersistedState } from './persisted_state'; export type { @@ -66,4 +66,12 @@ export { urlFor, getFullPath } from './utils/saved_visualize_utils'; export type { IEditorController, EditorRenderProps } from './visualize_app/types'; -export { VISUALIZE_EDITOR_TRIGGER, ACTION_CONVERT_TO_LENS } from './triggers'; +export { + VISUALIZE_EDITOR_TRIGGER, + AGG_BASED_VISUALIZATION_TRIGGER, + ACTION_CONVERT_TO_LENS, + ACTION_CONVERT_AGG_BASED_TO_LENS, +} from './triggers'; + +export const convertToLensModule = import('./convert_to_lens'); +export { getDataViewByIndexPatternId } from './convert_to_lens/datasource'; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 50245642e7fed..b7094f30b8b04 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -57,7 +57,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { TypesSetup, TypesStart } from './vis_types'; import type { VisualizeServices } from './visualize_app/types'; -import { visualizeEditorTrigger } from './triggers'; +import { aggBasedVisualizationTrigger, visualizeEditorTrigger } from './triggers'; import { createVisEditorsRegistry, VisEditorsRegistry } from './vis_editors_registry'; import { showNewVisModal } from './wizard'; import { VisualizeLocatorDefinition } from '../common/locator'; @@ -338,6 +338,7 @@ export class VisualizationsPlugin expressions.registerFunction(rangeExpressionFunction); expressions.registerFunction(visDimensionExpressionFunction); expressions.registerFunction(xyDimensionExpressionFunction); + uiActions.registerTrigger(aggBasedVisualizationTrigger); uiActions.registerTrigger(visualizeEditorTrigger); const embeddableFactory = new VisualizeEmbeddableFactory({ start }); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); diff --git a/src/plugins/visualizations/public/triggers/index.ts b/src/plugins/visualizations/public/triggers/index.ts index 2050185db15e4..c6eb548d7fab5 100644 --- a/src/plugins/visualizations/public/triggers/index.ts +++ b/src/plugins/visualizations/public/triggers/index.ts @@ -11,8 +11,16 @@ import { Trigger } from '@kbn/ui-actions-plugin/public'; export const VISUALIZE_EDITOR_TRIGGER = 'VISUALIZE_EDITOR_TRIGGER'; export const visualizeEditorTrigger: Trigger = { id: VISUALIZE_EDITOR_TRIGGER, - title: 'Convert legacy visualizations to Lens', - description: 'Triggered when user navigates from a legacy visualization to Lens.', + title: 'Convert TSVB to Lens', + description: 'Triggered when user navigates from a TSVB to Lens.', +}; + +export const AGG_BASED_VISUALIZATION_TRIGGER = 'AGG_BASED_VISUALIZATION_TRIGGER'; +export const aggBasedVisualizationTrigger: Trigger = { + id: AGG_BASED_VISUALIZATION_TRIGGER, + title: 'Convert legacy agg based visualizations to Lens', + description: 'Triggered when user navigates from a agg based visualization to Lens.', }; export const ACTION_CONVERT_TO_LENS = 'ACTION_CONVERT_TO_LENS'; +export const ACTION_CONVERT_AGG_BASED_TO_LENS = 'ACTION_CONVERT_AGG_BASED_TO_LENS'; diff --git a/src/plugins/visualizations/public/vis_schemas.ts b/src/plugins/visualizations/public/vis_schemas.ts index 795631b9b4f07..cd8bd9bc386a8 100644 --- a/src/plugins/visualizations/public/vis_schemas.ts +++ b/src/plugins/visualizations/public/vis_schemas.ts @@ -6,24 +6,29 @@ * Side Public License, v 1. */ -import type { SerializedFieldFormat } from '@kbn/field-formats-plugin/common'; -import { IAggConfig, search } from '@kbn/data-plugin/public'; - +import { + BUCKET_TYPES, + IAggConfig, + METRIC_TYPES, + SHARD_DELAY_AGG_NAME, +} from '@kbn/data-plugin/common'; +import { search } from '@kbn/data-plugin/public'; import { Vis, VisToExpressionAstParams } from './types'; +import { SchemaConfig } from '../common/types'; +import { convertToSchemaConfig } from '../common'; const { isDateHistogramBucketAggConfig } = search.aggs; -interface SchemaConfigParams { - precision?: number; - useGeocentroid?: boolean; -} +const SUPPORTED_AGGREGATIONS = [ + ...Object.values(METRIC_TYPES), + ...Object.values(BUCKET_TYPES), + SHARD_DELAY_AGG_NAME, +]; + +type SupportedAggregation = typeof SUPPORTED_AGGREGATIONS[number]; -export interface SchemaConfig { - accessor: number; - label: string; - format: SerializedFieldFormat; - params: SchemaConfigParams; - aggType: string; +function isSupportedAggType(name: string): name is SupportedAggregation { + return SUPPORTED_AGGREGATIONS.includes(name as SupportedAggregation); } export interface Schemas { @@ -41,54 +46,40 @@ export interface Schemas { [key: string]: any[] | undefined; } -export const getVisSchemas = ( - vis: Vis, +const updateDateHistogramParams = ( + agg: IAggConfig, { timeRange, timefilter }: VisToExpressionAstParams -): Schemas => { - const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => { - if (isDateHistogramBucketAggConfig(agg)) { - agg.params.timeRange = timeRange; - const bounds = - agg.params.timeRange && agg.fieldIsTimeField() - ? timefilter.calculateBounds(agg.params.timeRange) - : undefined; - agg.buckets.setBounds(bounds); - agg.buckets.setInterval(agg.params.interval); - } - - const hasSubAgg = [ - 'derivative', - 'moving_avg', - 'serial_diff', - 'cumulative_sum', - 'sum_bucket', - 'avg_bucket', - 'min_bucket', - 'max_bucket', - ].includes(agg.type.name); - - const formatAgg = hasSubAgg - ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg) - : agg; - - const params: SchemaConfigParams = {}; - - if (agg.type.name === 'geohash_grid') { - params.precision = agg.params.precision; - params.useGeocentroid = agg.params.useGeocentroid; - } +) => { + if (isDateHistogramBucketAggConfig(agg)) { + agg.params.timeRange = timeRange; + const bounds = + agg.params.timeRange && agg.fieldIsTimeField() + ? timefilter.calculateBounds(agg.params.timeRange) + : undefined; + agg.buckets.setBounds(bounds); + agg.buckets.setInterval(agg.params.interval); + } + return agg; +}; - const label = agg.makeLabel && agg.makeLabel(); +const createSchemaConfig = ( + agg: IAggConfig, + accessor: number, + params: VisToExpressionAstParams +): SchemaConfig => { + const aggType = agg.type.name; + if (!isSupportedAggType(aggType)) { + throw new Error(`Unsupported agg type: ${aggType}`); + } - return { - accessor, - format: formatAgg.toSerializedFieldFormat(), - params, - label, - aggType: agg.type.name, - }; - }; + const updatedAgg = updateDateHistogramParams(agg, params); + return { ...convertToSchemaConfig(updatedAgg), accessor }; +}; +export const getVisSchemas = ( + vis: Vis, + params: VisToExpressionAstParams +): Schemas => { let cnt = 0; const schemas: Schemas = { metric: [], @@ -121,11 +112,11 @@ export const getVisSchemas = ( schemas[schemaName] = []; } if (!isHierarchical || agg.type.type !== 'metrics') { - schemas[schemaName]!.push(createSchemaConfig(cnt++, agg)); + schemas[schemaName]!.push(createSchemaConfig(agg, cnt++, params)); } if (isHierarchical && (agg.type.type !== 'metrics' || metrics.length === responseAggs.length)) { metrics.forEach((metric: any) => { - const schemaConfig = createSchemaConfig(cnt++, metric); + const schemaConfig = createSchemaConfig(metric, cnt++, params); if (!skipMetrics) { schemas.metric.push(schemaConfig); } diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index 1b0875f636af4..2d351f18a3b47 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -29,6 +29,7 @@ export class BaseVisType { public readonly note; public readonly getSupportedTriggers; public readonly navigateToLens; + public readonly getExpressionVariables; public readonly icon; public readonly image; public readonly stage; @@ -62,6 +63,7 @@ export class BaseVisType { this.note = opts.note ?? ''; this.getSupportedTriggers = opts.getSupportedTriggers; this.navigateToLens = opts.navigateToLens; + this.getExpressionVariables = opts.getExpressionVariables; this.title = opts.title; this.icon = opts.icon; this.image = opts.image; diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts index 2eb011e91d66a..9689729f187fd 100644 --- a/src/plugins/visualizations/public/vis_types/types.ts +++ b/src/plugins/visualizations/public/vis_types/types.ts @@ -9,8 +9,12 @@ import type { IconType } from '@elastic/eui'; import type { ReactNode } from 'react'; import type { Adapters } from '@kbn/inspector-plugin/common'; -import { TimeRange } from '@kbn/data-plugin/common'; -import type { AggGroupNames, AggParam, AggGroupName } from '@kbn/data-plugin/public'; +import type { + AggGroupNames, + AggParam, + AggGroupName, + TimefilterContract, +} from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { Vis, VisEditorOptionsProps, VisParams, VisToExpressionAst } from '../types'; import { VisGroups } from './vis_groups_enum'; @@ -104,10 +108,19 @@ export interface VisTypeDefinition { * in order to be displayed in the Lens editor. */ readonly navigateToLens?: ( - params?: VisParams, - timeRange?: TimeRange + vis?: Vis, + timeFilter?: TimefilterContract ) => Promise | undefined; + /** + * If given, it will provide variables for expression params. + * Every visualization that wants to add variables for expression params should have this method. + */ + readonly getExpressionVariables?: ( + vis?: Vis, + timeFilter?: TimefilterContract + ) => Promise>; + /** * Some visualizations are created without SearchSource and may change the used indexes during the visualization configuration. * Using this method we can rewrite the standard mechanism for getting used indexes diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index 3ba6d035f31fb..500a5e5b34d41 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -100,14 +100,14 @@ const TopNav = ({ const asyncGetTriggerContext = async () => { if (vis.type.navigateToLens) { const triggerConfig = await vis.type.navigateToLens( - vis.params, - services.data.query.timefilter.timefilter.getAbsoluteTime() + vis, + services.data.query.timefilter.timefilter ); setEditInLensConfig(triggerConfig); } }; asyncGetTriggerContext(); - }, [services.data.query.timefilter.timefilter, vis.params, vis.type]); + }, [services.data.query.timefilter.timefilter, vis, vis.type, vis.params, vis.data.indexPattern]); const displayEditInLensItem = Boolean(vis.type.navigateToLens && editInLensConfig); const config = useMemo(() => { diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 68cb01d10c94d..36b92585f1096 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -40,7 +40,7 @@ import { VisualizeConstants } from '../../../common/constants'; import { getEditBreadcrumbs } from './breadcrumbs'; import { VISUALIZE_APP_LOCATOR, VisualizeLocatorParams } from '../../../common/locator'; import { getUiActions } from '../../services'; -import { VISUALIZE_EDITOR_TRIGGER } from '../../triggers'; +import { VISUALIZE_EDITOR_TRIGGER, AGG_BASED_VISUALIZATION_TRIGGER } from '../../triggers'; import { getVizEditorOriginatingAppUrl } from './utils'; import './visualize_navigation.scss'; @@ -311,7 +311,13 @@ export const getTopNavConfig = ( if (editInLensConfig) { hideLensBadge(); setNavigateToLens(true); - getUiActions().getTrigger(VISUALIZE_EDITOR_TRIGGER).exec(updatedWithMeta); + getUiActions() + .getTrigger( + visInstance.vis.type.group === 'aggbased' + ? AGG_BASED_VISUALIZATION_TRIGGER + : VISUALIZE_EDITOR_TRIGGER + ) + .exec(updatedWithMeta); } }, }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index c886c21cfa6b8..1f0f6884d9719 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -13,7 +13,6 @@ import { difference } from 'lodash'; import type { DataViewsContract, DataViewSpec } from '@kbn/data-views-plugin/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; -import { isAnnotationsLayer } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { Datasource, DatasourceLayers, @@ -51,12 +50,7 @@ function getIndexPatterns( const indexPatternIds = []; if (initialContext) { if ('isVisualizeAction' in initialContext) { - for (const { indexPatternId } of initialContext.layers) { - indexPatternIds.push(indexPatternId); - } - for (const l of initialContext.configuration.layers) { - if (isAnnotationsLayer(l)) indexPatternIds.push(l.indexPatternId); - } + indexPatternIds.push(...initialContext.indexPatternIds); } else { indexPatternIds.push(initialContext.dataViewSpec.id!); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index e362af10fd1f4..ec8eb224678b6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -199,7 +199,8 @@ export function getVisualizeFieldSuggestions({ const allSuggestions = suggestions.filter( (s) => s.visualizationId === visualizeTriggerFieldContext.type ); - return activeVisualization?.getSuggestionFromConvertToLensContext?.({ + const visualization = visualizationMap[visualizeTriggerFieldContext.type] || null; + return visualization?.getSuggestionFromConvertToLensContext?.({ suggestions: allSuggestions, context: visualizeTriggerFieldContext, }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 40b2f66b2b93d..52d7dff8d6ec7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -75,7 +75,7 @@ function getMultipleDateHistogramsErrorMessage(layer: IndexPatternLayer, columnI export const dateHistogramOperation: OperationDefinition< DateHistogramIndexPatternColumn, 'field', - { interval: string; dropPartials?: boolean } + { interval: string; dropPartials?: boolean; includeEmptyRows?: boolean } > = { type: 'date_histogram', displayName: i18n.translate('xpack.lens.indexPattern.dateHistogram', { @@ -116,7 +116,7 @@ export const dateHistogramOperation: OperationDefinition< scale: 'interval', params: { interval: columnParams?.interval ?? autoInterval, - includeEmptyRows: true, + includeEmptyRows: columnParams?.includeEmptyRows ?? true, dropPartials: Boolean(columnParams?.dropPartials), }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 3efbafcd572b0..aa84c727ae507 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -103,13 +103,14 @@ export const rangeOperation: OperationDefinition< defaultMessage: 'Missing field', }), buildColumn({ field }, columnParams) { + const type = columnParams?.type ?? MODES.Histogram; return { label: field.displayName, - dataType: 'number', // string for Range + dataType: type === MODES.Histogram ? 'number' : 'string', // string for Range operationType: 'range', sourceField: field.name, isBucketed: true, - scale: 'interval', // ordinal for Range + scale: type === MODES.Histogram ? 'interval' : 'ordinal', // ordinal for Range params: { includeEmptyRows: columnParams?.includeEmptyRows ?? true, type: columnParams?.type ?? MODES.Histogram, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 0427e40f24ac6..3f8496d31f678 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -240,6 +240,10 @@ export const termsOperation: OperationDefinition< otherBucket: (columnParams?.otherBucket ?? true) && !indexPattern.hasRestrictions, missingBucket: columnParams?.missingBucket ?? false, parentFormat: columnParams?.parentFormat ?? { id: 'terms' }, + include: columnParams?.include ?? [], + exclude: columnParams?.exclude ?? [], + includeIsRegex: columnParams?.includeIsRegex ?? false, + excludeIsRegex: columnParams?.excludeIsRegex ?? false, secondaryFields: columnParams?.secondaryFields, }, }; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 44307bad11b8e..5f55f4ae9c2e0 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -41,7 +41,10 @@ import { ACTION_VISUALIZE_FIELD, VISUALIZE_FIELD_TRIGGER, } from '@kbn/ui-actions-plugin/public'; -import { VISUALIZE_EDITOR_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { + VISUALIZE_EDITOR_TRIGGER, + AGG_BASED_VISUALIZATION_TRIGGER, +} from '@kbn/visualizations-plugin/public'; import { createStartServicesGetter } from '@kbn/kibana-utils-plugin/public'; import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; @@ -85,6 +88,7 @@ import { getLensAliasConfig } from './vis_type_alias'; import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_action'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import { visualizeTSVBAction } from './trigger_actions/visualize_tsvb_actions'; +import { visualizeAggBasedVisAction } from './trigger_actions/visualize_agg_based_vis_actions'; import type { LensEmbeddableInput } from './embeddable'; import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; @@ -480,6 +484,11 @@ export class LensPlugin { visualizeTSVBAction(core.application) ); + startDependencies.uiActions.addTriggerAction( + AGG_BASED_VISUALIZATION_TRIGGER, + visualizeAggBasedVisAction(core.application) + ); + startDependencies.uiActions.addTriggerAction( CONTEXT_MENU_TRIGGER, createOpenInDiscoverAction( diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts new file mode 100644 index 0000000000000..2d2b329a7ea67 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts @@ -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 { i18n } from '@kbn/i18n'; +import { createAction } from '@kbn/ui-actions-plugin/public'; +import { + ACTION_CONVERT_TO_LENS, + ACTION_CONVERT_AGG_BASED_TO_LENS, +} from '@kbn/visualizations-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import type { VisualizeEditorContext } from '../types'; + +export const visualizeAggBasedVisAction = (application: ApplicationStart) => + createAction<{ [key: string]: VisualizeEditorContext }>({ + type: ACTION_CONVERT_TO_LENS, + id: ACTION_CONVERT_AGG_BASED_TO_LENS, + getDisplayName: () => + i18n.translate('xpack.lens.visualizeAggBasedLegend', { + defaultMessage: 'Visualize agg based chart', + }), + isCompatible: async () => !!application.capabilities.visualize.show, + execute: async (context: { [key: string]: VisualizeEditorContext }) => { + const table = Object.values(context.layers); + const payload = { + ...context, + layers: table, + isVisualizeAction: true, + }; + application.navigateToApp('lens', { + state: { + type: ACTION_CONVERT_TO_LENS, + payload, + originatingApp: i18n.translate('xpack.lens.AggBasedLabel', { + defaultMessage: 'Aggregation based visualization', + }), + }, + }); + }, + }); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index 8cc30c65ed1a9..071fa328486c9 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -20,6 +20,7 @@ import type { Visualization, VisualizationSuggestion, DatasourceLayers, + Suggestion, } from '../../types'; import { TableDimensionEditor } from './components/dimension_editor'; import { TableDimensionEditorAdditionalSection } from './components/dimension_editor_addtional_section'; @@ -27,6 +28,7 @@ import { LayerType, layerTypes } from '../../../common'; import { getDefaultSummaryLabel, PagingState } from '../../../common/expressions'; import type { ColumnState, SortingState } from '../../../common/expressions'; import { DataTableToolbar } from './components/toolbar'; +import { IndexPatternLayer } from '../../indexpattern_datasource/types'; export interface DatatableVisualizationState { columns: ColumnState[]; @@ -40,6 +42,16 @@ export interface DatatableVisualizationState { paging?: PagingState; } +interface DatatableDatasourceState { + [prop: string]: unknown; + layers: IndexPatternLayer[]; +} + +export interface DatatableSuggestion extends Suggestion { + datasourceState: DatatableDatasourceState; + visualizationState: DatatableVisualizationState; +} + const visualizationLabel = i18n.translate('xpack.lens.datatable.label', { defaultMessage: 'Table', }); @@ -583,6 +595,26 @@ export const getDatatableVisualization = ({ return state; } }, + getSuggestionFromConvertToLensContext({ suggestions, context }) { + const allSuggestions = suggestions as DatatableSuggestion[]; + return { + ...allSuggestions[0], + datasourceState: { + ...allSuggestions[0].datasourceState, + layers: allSuggestions.reduce( + (acc, s) => ({ + ...acc, + ...s.datasourceState.layers, + }), + {} + ), + }, + visualizationState: { + ...allSuggestions[0].visualizationState, + ...context.configuration, + }, + }; + }, }); function getDataSourceAndSortedColumns( From 8937be2db431f24c7b768cfd7d378de114c025db Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 26 Sep 2022 10:30:03 +0200 Subject: [PATCH 005/172] [ML] Explain Log Rate Spikes: Fix analysis table row pinning. (#141455) - Fixes styling of hovered and pinned rows to use EUI provided variables. - The above was also done for the Log Pattern Analysis page to fix an issue with dark theme. - Fixes unpinning a row for field/value pairs. - Fixes pinning/unpinning for groups. --- .../document_count_chart.tsx | 2 +- .../document_count_content.tsx | 13 +- .../explain_log_rate_spikes_analysis.tsx | 33 ++--- .../explain_log_rate_spikes_app_state.tsx | 6 +- .../explain_log_rate_spikes_page.tsx | 51 ++++---- .../category_table/category_table.tsx | 12 +- .../log_categorization_page.tsx | 1 + .../spike_analysis_table.tsx | 64 ++++++---- .../spike_analysis_table_groups.tsx | 51 +++++--- .../spike_analysis_table_row_provider.tsx | 118 ++++++++++++++++++ x-pack/plugins/aiops/public/hooks/use_data.ts | 5 +- 11 files changed, 250 insertions(+), 106 deletions(-) create mode 100644 x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_row_provider.tsx diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx index 1989316b15ec1..ad317fbf222ec 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx @@ -120,7 +120,7 @@ export const DocumentCountChart: FC = ({ const overallSeriesNameWithSplit = i18n.translate( 'xpack.aiops.dataGrid.field.documentCountChartSplit.seriesLabel', { - defaultMessage: 'other document count', + defaultMessage: 'Other document count', } ); diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx index 1f179be0ba975..e262ad0348d98 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx @@ -5,13 +5,12 @@ * 2.0. */ -import React, { useEffect, useState, FC, useMemo } from 'react'; +import React, { useEffect, useState, FC } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { WindowParameters } from '@kbn/aiops-utils'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; import { DocumentCountChart, DocumentCountChartPoint } from '../document_count_chart'; import { TotalCountHeader } from '../total_count_header'; @@ -27,9 +26,9 @@ const clearSelectionLabel = i18n.translate( export interface DocumentCountContentProps { brushSelectionUpdateHandler: (d: WindowParameters) => void; clearSelectionHandler: () => void; - changePoint?: ChangePoint; documentCountStats?: DocumentCountStats; documentCountStatsSplit?: DocumentCountStats; + documentCountStatsSplitLabel?: string; totalCount: number; windowParameters?: WindowParameters; } @@ -37,9 +36,9 @@ export interface DocumentCountContentProps { export const DocumentCountContent: FC = ({ brushSelectionUpdateHandler, clearSelectionHandler, - changePoint, documentCountStats, documentCountStatsSplit, + documentCountStatsSplitLabel = '', totalCount, windowParameters, }) => { @@ -52,10 +51,6 @@ export const DocumentCountContent: FC = ({ const bucketTimestamps = Object.keys(documentCountStats?.buckets ?? {}).map((time) => +time); const timeRangeEarliest = Math.min(...bucketTimestamps); const timeRangeLatest = Math.max(...bucketTimestamps); - const chartPointsSplitLabel = useMemo( - () => `${changePoint?.fieldName}:${changePoint?.fieldValue}`, - [changePoint] - ); if ( documentCountStats === undefined || @@ -121,7 +116,7 @@ export const DocumentCountContent: FC = ({ timeRangeEarliest={timeRangeEarliest} timeRangeLatest={timeRangeLatest} interval={documentCountStats.interval} - chartPointsSplitLabel={chartPointsSplitLabel} + chartPointsSplitLabel={documentCountStatsSplitLabel} isBrushCleared={isBrushCleared} /> )} diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx index a0fb825ee4064..2425161615915 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx @@ -23,7 +23,6 @@ import { useFetchStream } from '@kbn/aiops-utils'; import type { WindowParameters } from '@kbn/aiops-utils'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -32,7 +31,7 @@ import type { ApiExplainLogRateSpikes } from '../../../common/api'; import { SpikeAnalysisGroupsTable } from '../spike_analysis_table'; import { SpikeAnalysisTable } from '../spike_analysis_table'; -import { GroupTableItem } from '../spike_analysis_table/spike_analysis_table_groups'; +import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider'; const groupResultsMessage = i18n.translate( 'xpack.aiops.spikeAnalysisTable.groupedSwitchLabel.groupResults', @@ -54,10 +53,6 @@ interface ExplainLogRateSpikesAnalysisProps { /** Window parameters for the analysis */ windowParameters: WindowParameters; searchQuery: Query['query']; - onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; - onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; - selectedChangePoint?: ChangePoint; - onSelectedGroup?: (group: GroupTableItem | null) => void; } export const ExplainLogRateSpikesAnalysis: FC = ({ @@ -66,14 +61,12 @@ export const ExplainLogRateSpikesAnalysis: FC latest, windowParameters, searchQuery, - onPinnedChangePoint, - onSelectedChangePoint, - selectedChangePoint, - onSelectedGroup, }) => { const { http } = useAiopsAppContext(); const basePath = http.basePath.get() ?? ''; + const { clearAllRowState } = useSpikeAnalysisTableRowContext(); + const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState< WindowParameters | undefined >(); @@ -81,6 +74,9 @@ export const ExplainLogRateSpikesAnalysis: FC const onSwitchToggle = (e: { target: { checked: React.SetStateAction } }) => { setGroupResults(e.target.checked); + + // When toggling the group switch, clear all row selections + clearAllRowState(); }; const { @@ -109,15 +105,9 @@ export const ExplainLogRateSpikesAnalysis: FC // Start handler clears possibly hovered or pinned // change points on analysis refresh. function startHandler() { - // Reset grouping to false when restarting the analysis. + // Reset grouping to false and clear all row selections when restarting the analysis. setGroupResults(false); - - if (onPinnedChangePoint) { - onPinnedChangePoint(null); - } - if (onSelectedChangePoint) { - onSelectedChangePoint(null); - } + clearAllRowState(); setCurrentAnalysisWindowParameters(windowParameters); start(); @@ -249,10 +239,6 @@ export const ExplainLogRateSpikesAnalysis: FC changePoints={data.changePoints} groupTableItems={groupTableItems} loading={isRunning} - onPinnedChangePoint={onPinnedChangePoint} - onSelectedChangePoint={onSelectedChangePoint} - selectedChangePoint={selectedChangePoint} - onSelectedGroup={onSelectedGroup} dataViewId={dataView.id} /> ) : null} @@ -260,9 +246,6 @@ export const ExplainLogRateSpikesAnalysis: FC ) : null} diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx index 9b9395721e535..34460240d0134 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx @@ -36,6 +36,8 @@ import { import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { SpikeAnalysisTableRowStateProvider } from '../spike_analysis_table/spike_analysis_table_row_provider'; + import { ExplainLogRateSpikesPage } from './explain_log_rate_spikes_page'; export interface ExplainLogRateSpikesAppStateProps { @@ -164,7 +166,9 @@ export const ExplainLogRateSpikesAppState: FC return ( - + + + ); diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index feff4f2e8211f..63ccb20e4e6b0 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState, FC } from 'react'; +import React, { useCallback, useEffect, useState, FC } from 'react'; import { EuiEmptyPrompt, EuiFlexGroup, @@ -19,6 +19,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { WindowParameters } from '@kbn/aiops-utils'; import type { ChangePoint } from '@kbn/ml-agg-utils'; @@ -38,10 +39,21 @@ import { SearchPanel } from '../search_panel'; import { restorableDefaults } from './explain_log_rate_spikes_app_state'; import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis'; import type { GroupTableItem } from '../spike_analysis_table/spike_analysis_table_groups'; +import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider'; // TODO port to `@emotion/react` once `useEuiBreakpoint` is available https://github.com/elastic/eui/pull/6057 import './explain_log_rate_spikes_page.scss'; +function getDocumentCountStatsSplitLabel(changePoint?: ChangePoint, group?: GroupTableItem) { + if (changePoint) { + return `${changePoint?.fieldName}:${changePoint?.fieldValue}`; + } else if (group) { + return i18n.translate('xpack.aiops.spikeAnalysisPage.documentCountStatsSplitGroupLabel', { + defaultMessage: 'Selected group', + }); + } +} + /** * ExplainLogRateSpikes props require a data view. */ @@ -58,6 +70,15 @@ export const ExplainLogRateSpikesPage: FC = ({ }) => { const { data: dataService } = useAiopsAppContext(); + const { + currentSelectedChangePoint, + currentSelectedGroup, + setPinnedChangePoint, + setPinnedGroup, + setSelectedChangePoint, + setSelectedGroup, + } = useSpikeAnalysisTableRowContext(); + const [aiopsListState, setAiopsListState] = usePageUrlState(AppStateKey, restorableDefaults); const [globalState, setGlobalState] = useUrlState('_g'); @@ -93,19 +114,6 @@ export const ExplainLogRateSpikesPage: FC = ({ [currentSavedSearch, aiopsListState, setAiopsListState] ); - const [pinnedChangePoint, setPinnedChangePoint] = useState(null); - const [selectedChangePoint, setSelectedChangePoint] = useState(null); - const [selectedGroup, setSelectedGroup] = useState(null); - - // If a row is pinned, still overrule with a potentially hovered row. - const currentSelectedChangePoint = useMemo(() => { - if (selectedChangePoint) { - return selectedChangePoint; - } else if (pinnedChangePoint) { - return pinnedChangePoint; - } - }, [pinnedChangePoint, selectedChangePoint]); - const { documentStats, timefilter, @@ -119,8 +127,7 @@ export const ExplainLogRateSpikesPage: FC = ({ aiopsListState, setGlobalState, currentSelectedChangePoint, - undefined, - selectedGroup + currentSelectedGroup ); const { totalCount, documentCountStats, documentCountStatsCompare } = documentStats; @@ -170,6 +177,7 @@ export const ExplainLogRateSpikesPage: FC = ({ function clearSelection() { setWindowParameters(undefined); setPinnedChangePoint(null); + setPinnedGroup(null); setSelectedChangePoint(null); setSelectedGroup(null); } @@ -230,12 +238,15 @@ export const ExplainLogRateSpikesPage: FC = ({ clearSelectionHandler={clearSelection} documentCountStats={documentCountStats} documentCountStatsSplit={ - currentSelectedChangePoint || selectedGroup + currentSelectedChangePoint || currentSelectedGroup ? documentCountStatsCompare : undefined } + documentCountStatsSplitLabel={getDocumentCountStatsSplitLabel( + currentSelectedChangePoint, + currentSelectedGroup + )} totalCount={totalCount} - changePoint={currentSelectedChangePoint} windowParameters={windowParameters} /> @@ -250,10 +261,6 @@ export const ExplainLogRateSpikesPage: FC = ({ latest={latest} windowParameters={windowParameters} searchQuery={searchQuery} - onPinnedChangePoint={setPinnedChangePoint} - onSelectedChangePoint={setSelectedChangePoint} - selectedChangePoint={currentSelectedChangePoint} - onSelectedGroup={setSelectedGroup} /> )} {windowParameters === undefined && ( diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx index 1aa613962ff9e..9c5862d34dbde 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { TimefilterContract } from '@kbn/data-plugin/public'; import { + useEuiBackgroundColor, EuiButton, EuiSpacer, EuiFlexGroup, @@ -62,6 +63,7 @@ export const CategoryTable: FC = ({ setSelectedCategory, }) => { const euiTheme = useEuiTheme(); + const primaryBackgroundColor = useEuiBackgroundColor('primary'); const { openInDiscoverWithFilter } = useDiscoverLinks(); const [selectedCategories, setSelectedCategories] = useState([]); const { onTableChange, pagination, sorting } = useTableState(categories ?? [], 'key'); @@ -193,22 +195,18 @@ export const CategoryTable: FC = ({ pinnedCategory.key === category.key ) { return { - backgroundColor: 'rgb(227,240,249,0.37)', + backgroundColor: primaryBackgroundColor, }; } - if ( - selectedCategory && - selectedCategory.key === category.key && - selectedCategory.key === category.key - ) { + if (selectedCategory && selectedCategory.key === category.key) { return { backgroundColor: euiTheme.euiColorLightestShade, }; } return { - backgroundColor: 'white', + backgroundColor: euiTheme.euiColorEmptyShade, }; }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 6bc90db0a7b71..845a4239f1104 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -116,6 +116,7 @@ export const LogCategorizationPage: FC = ({ aiopsListState, setGlobalState, undefined, + undefined, BAR_TARGET ); diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx index 35f3ba368be9a..3994bd82d9523 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx @@ -9,6 +9,7 @@ import React, { FC, useCallback, useMemo, useState } from 'react'; import { sortBy } from 'lodash'; import { + useEuiBackgroundColor, EuiBadge, EuiBasicTable, EuiBasicTableColumn, @@ -29,6 +30,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { MiniHistogram } from '../mini_histogram'; import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; +import { useSpikeAnalysisTableRowContext } from './spike_analysis_table_row_provider'; const NARROW_COLUMN_WIDTH = '120px'; const ACTIONS_COLUMN_WIDTH = '60px'; @@ -48,20 +50,18 @@ interface SpikeAnalysisTableProps { changePoints: ChangePoint[]; dataViewId?: string; loading: boolean; - onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; - onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; - selectedChangePoint?: ChangePoint; } export const SpikeAnalysisTable: FC = ({ changePoints, dataViewId, loading, - onPinnedChangePoint, - onSelectedChangePoint, - selectedChangePoint, }) => { const euiTheme = useEuiTheme(); + const primaryBackgroundColor = useEuiBackgroundColor('primary'); + + const { pinnedChangePoint, selectedChangePoint, setPinnedChangePoint, setSelectedChangePoint } = + useSpikeAnalysisTableRowContext(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -318,6 +318,32 @@ export const SpikeAnalysisTable: FC = ({ }; }, [pageIndex, pageSize, sortField, sortDirection, changePoints]); + const getRowStyle = (changePoint: ChangePoint) => { + if ( + pinnedChangePoint && + pinnedChangePoint.fieldName === changePoint.fieldName && + pinnedChangePoint.fieldValue === changePoint.fieldValue + ) { + return { + backgroundColor: primaryBackgroundColor, + }; + } + + if ( + selectedChangePoint && + selectedChangePoint.fieldName === changePoint.fieldName && + selectedChangePoint.fieldValue === changePoint.fieldValue + ) { + return { + backgroundColor: euiTheme.euiColorLightestShade, + }; + } + + return { + backgroundColor: euiTheme.euiColorEmptyShade, + }; + }; + // Don't pass on the `loading` state to the table itself because // it disables hovering events. Because the mini histograms take a while // to load, hovering would not update the main chart. Instead, @@ -339,28 +365,22 @@ export const SpikeAnalysisTable: FC = ({ return { 'data-test-subj': `aiopsSpikeAnalysisTableRow row-${changePoint.fieldName}-${changePoint.fieldValue}`, onClick: () => { - if (onPinnedChangePoint) { - onPinnedChangePoint(changePoint); + if ( + changePoint.fieldName === pinnedChangePoint?.fieldName && + changePoint.fieldValue === pinnedChangePoint?.fieldValue + ) { + setPinnedChangePoint(null); + } else { + setPinnedChangePoint(changePoint); } }, onMouseEnter: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(changePoint); - } + setSelectedChangePoint(changePoint); }, onMouseLeave: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(null); - } + setSelectedChangePoint(null); }, - style: - selectedChangePoint && - selectedChangePoint.fieldValue === changePoint.fieldValue && - selectedChangePoint.fieldName === changePoint.fieldName - ? { - backgroundColor: euiTheme.euiColorLightestShade, - } - : null, + style: getRowStyle(changePoint), }; }} /> diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index a7503e7d49111..5bbc0cecae4d4 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -9,6 +9,7 @@ import React, { FC, useCallback, useMemo, useState } from 'react'; import { sortBy } from 'lodash'; import { + useEuiBackgroundColor, EuiBadge, EuiBasicTable, EuiBasicTableColumn, @@ -34,6 +35,7 @@ import { MiniHistogram } from '../mini_histogram'; import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; import { SpikeAnalysisTable } from './spike_analysis_table'; +import { useSpikeAnalysisTableRowContext } from './spike_analysis_table_row_provider'; const NARROW_COLUMN_WIDTH = '120px'; const EXPAND_COLUMN_WIDTH = '40px'; @@ -64,10 +66,6 @@ interface SpikeAnalysisTableProps { groupTableItems: GroupTableItem[]; dataViewId?: string; loading: boolean; - onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; - onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; - selectedChangePoint?: ChangePoint; - onSelectedGroup?: (group: GroupTableItem | null) => void; } export const SpikeAnalysisGroupsTable: FC = ({ @@ -75,10 +73,6 @@ export const SpikeAnalysisGroupsTable: FC = ({ groupTableItems, dataViewId, loading, - onPinnedChangePoint, - onSelectedChangePoint, - selectedChangePoint, - onSelectedGroup, }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -89,6 +83,10 @@ export const SpikeAnalysisGroupsTable: FC = ({ ); const euiTheme = useEuiTheme(); + const primaryBackgroundColor = useEuiBackgroundColor('primary'); + + const { pinnedGroup, selectedGroup, setPinnedGroup, setSelectedGroup } = + useSpikeAnalysisTableRowContext(); const toggleDetails = (item: GroupTableItem) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; @@ -121,9 +119,6 @@ export const SpikeAnalysisGroupsTable: FC = ({ ); @@ -458,6 +453,24 @@ export const SpikeAnalysisGroupsTable: FC = ({ }; }, [pageIndex, pageSize, sortField, sortDirection, groupTableItems]); + const getRowStyle = (group: GroupTableItem) => { + if (pinnedGroup && pinnedGroup.id === group.id) { + return { + backgroundColor: primaryBackgroundColor, + }; + } + + if (selectedGroup && selectedGroup.id === group.id) { + return { + backgroundColor: euiTheme.euiColorLightestShade, + }; + } + + return { + backgroundColor: euiTheme.euiColorEmptyShade, + }; + }; + return ( = ({ rowProps={(group) => { return { 'data-test-subj': `aiopsSpikeAnalysisGroupsTableRow row-${group.id}`, - onMouseEnter: () => { - if (onSelectedGroup) { - onSelectedGroup(group); + onClick: () => { + if (group.id === pinnedGroup?.id) { + setPinnedGroup(null); + } else { + setPinnedGroup(group); } }, + onMouseEnter: () => { + setSelectedGroup(group); + }, onMouseLeave: () => { - if (onSelectedGroup) { - onSelectedGroup(null); - } + setSelectedGroup(null); }, + style: getRowStyle(group), }; }} /> diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_row_provider.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_row_provider.tsx new file mode 100644 index 0000000000000..88b6e508d2f4c --- /dev/null +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_row_provider.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { + createContext, + useContext, + useMemo, + useState, + type FC, + type Dispatch, + type SetStateAction, +} from 'react'; + +import type { ChangePoint } from '@kbn/ml-agg-utils'; + +import type { GroupTableItem } from './spike_analysis_table_groups'; + +type ChangePointOrNull = ChangePoint | null; +type GroupOrNull = GroupTableItem | null; + +interface SpikeAnalysisTableRow { + pinnedChangePoint: ChangePointOrNull; + setPinnedChangePoint: Dispatch>; + pinnedGroup: GroupOrNull; + setPinnedGroup: Dispatch>; + selectedChangePoint: ChangePointOrNull; + setSelectedChangePoint: Dispatch>; + selectedGroup: GroupOrNull; + setSelectedGroup: Dispatch>; + currentSelectedChangePoint?: ChangePoint; + currentSelectedGroup?: GroupTableItem; + clearAllRowState: () => void; +} + +export const spikeAnalysisTableRowContext = createContext( + undefined +); + +export const SpikeAnalysisTableRowStateProvider: FC = ({ children }) => { + // State that will be shared with all components + const [pinnedChangePoint, setPinnedChangePoint] = useState(null); + const [pinnedGroup, setPinnedGroup] = useState(null); + const [selectedChangePoint, setSelectedChangePoint] = useState(null); + const [selectedGroup, setSelectedGroup] = useState(null); + + // If a row is pinned, still overrule with a potentially hovered row. + const currentSelectedChangePoint = useMemo(() => { + if (selectedChangePoint) { + return selectedChangePoint; + } else if (pinnedChangePoint) { + return pinnedChangePoint; + } + }, [pinnedChangePoint, selectedChangePoint]); + + // If a group is pinned, still overrule with a potentially hovered group. + const currentSelectedGroup = useMemo(() => { + if (selectedGroup) { + return selectedGroup; + } else if (pinnedGroup) { + return pinnedGroup; + } + }, [selectedGroup, pinnedGroup]); + + const contextValue: SpikeAnalysisTableRow = useMemo( + () => ({ + pinnedChangePoint, + setPinnedChangePoint, + pinnedGroup, + setPinnedGroup, + selectedChangePoint, + setSelectedChangePoint, + selectedGroup, + setSelectedGroup, + currentSelectedChangePoint, + currentSelectedGroup, + clearAllRowState: () => { + setPinnedChangePoint(null); + setPinnedGroup(null); + setSelectedChangePoint(null); + setSelectedGroup(null); + }, + }), + [ + pinnedChangePoint, + setPinnedChangePoint, + pinnedGroup, + setPinnedGroup, + selectedChangePoint, + setSelectedChangePoint, + selectedGroup, + setSelectedGroup, + currentSelectedChangePoint, + currentSelectedGroup, + ] + ); + + return ( + // Provider managing the state + + {children} + + ); +}; + +export const useSpikeAnalysisTableRowContext = () => { + const spikeAnalysisTableRow = useContext(spikeAnalysisTableRowContext); + + // If `undefined`, throw an error. + if (spikeAnalysisTableRow === undefined) { + throw new Error('useSpikeAnalysisTableRowContext was used outside of its Provider'); + } + + return spikeAnalysisTableRow; +}; diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 05736fe00e4fc..9d8f889dd8476 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -40,8 +40,8 @@ export const useData = ( aiopsListState: AiOpsIndexBasedAppState, onUpdate: (params: Dictionary) => void, selectedChangePoint?: ChangePoint, - barTarget: number = DEFAULT_BAR_TARGET, - selectedGroup?: GroupTableItem | null + selectedGroup?: GroupTableItem | null, + barTarget: number = DEFAULT_BAR_TARGET ) => { const { uiSettings, @@ -49,6 +49,7 @@ export const useData = ( query: { filterManager }, }, } = useAiopsAppContext(); + const [lastRefresh, setLastRefresh] = useState(0); const [fieldStatsRequest, setFieldStatsRequest] = useState< DocumentStatsSearchStrategyParams | undefined From 4ab4b247e6c9ad3781001d2bf95e95447b1d760a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 26 Sep 2022 11:59:25 +0200 Subject: [PATCH 006/172] Bump backport to 8.9.4 (#141741) --- package.json | 2 +- yarn.lock | 94 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index be723374e4356..3c439a01a9b3d 100644 --- a/package.json +++ b/package.json @@ -1265,7 +1265,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^8.9.2", + "backport": "^8.9.4", "callsites": "^3.1.0", "chance": "1.0.18", "chokidar": "^3.5.3", diff --git a/yarn.lock b/yarn.lock index 374d3119e018e..23369504cad0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4284,6 +4284,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.10.1.tgz#57b5cc6c7b4e55d8642c93d06401fb1af4839899" integrity sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ== +"@octokit/openapi-types@^13.4.0": + version "13.4.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-13.4.0.tgz#06fe8fda93bf21bdd397fe7ef8805249efda6c06" + integrity sha512-2mVzW0X1+HDO3jF80/+QFZNzJiTefELKbhMu6yaBYbp/1gSMkVDm4rT472gJljTokWUlXaaE63m7WrWENhMDLw== + "@octokit/plugin-paginate-rest@^1.1.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" @@ -4291,12 +4296,12 @@ dependencies: "@octokit/types" "^2.0.1" -"@octokit/plugin-paginate-rest@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-3.0.0.tgz#df779de686aeb21b5e776e4318defc33b0418566" - integrity sha512-fvw0Q5IXnn60D32sKeLIxgXCEZ7BTSAjJd8cFAE6QU5qUp0xo7LjFUjjX1J5D7HgN355CN4EXE4+Q1/96JaNUA== +"@octokit/plugin-paginate-rest@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.1.0.tgz#670ac9ac369448c69a2371bfcd7e2b37d95534f2" + integrity sha512-2O5K5fpajYG5g62wjzHR7/cWYaCA88CextAW3vFP+yoIHD0KEdlVMHfM5/i5LyV+JMmqiYW7w5qfg46FR+McNw== dependencies: - "@octokit/types" "^6.39.0" + "@octokit/types" "^7.1.1" "@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.4": version "1.0.4" @@ -4411,17 +4416,17 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^19.0.3": - version "19.0.3" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.3.tgz#b9a4e8dc8d53e030d611c053153ee6045f080f02" - integrity sha512-5arkTsnnRT7/sbI4fqgSJ35KiFaN7zQm0uQiQtivNQLI8RQx8EHwJCajcTUwmaCMNDg7tdCvqAnc7uvHHPxrtQ== +"@octokit/rest@^19.0.4": + version "19.0.4" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.4.tgz#fd8bed1cefffa486e9ae46a9dc608ce81bcfcbdd" + integrity sha512-LwG668+6lE8zlSYOfwPj4FxWdv/qFXYBpv79TWIQEpBLKA9D/IMcWsF/U9RGpA3YqMVDiTxpgVpEW3zTFfPFTA== dependencies: "@octokit/core" "^4.0.0" - "@octokit/plugin-paginate-rest" "^3.0.0" + "@octokit/plugin-paginate-rest" "^4.0.0" "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^6.0.0" -"@octokit/types@6.40.0", "@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0": +"@octokit/types@6.40.0", "@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.16.1": version "6.40.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.40.0.tgz#f2e665196d419e19bb4265603cf904a820505d0e" integrity sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw== @@ -4442,6 +4447,13 @@ dependencies: "@types/node" ">= 8" +"@octokit/types@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-7.1.1.tgz#a30fd6ca3279d59d532fa75583d65d93b7588e6d" + integrity sha512-Dx6cNTORyVaKY0Yeb9MbHksk79L8GXsihbG6PtWqTpkyA2TY1qBWE26EQXVG3dHwY9Femdd/WEeRUEiD0+H3TQ== + dependencies: + "@octokit/openapi-types" "^13.4.0" + "@openpgp/web-stream-tools@^0.0.10": version "0.0.10" resolved "https://registry.yarnpkg.com/@openpgp/web-stream-tools/-/web-stream-tools-0.0.10.tgz#4496390da9715c9bfc581ad144f9fb8a36a37775" @@ -10478,18 +10490,18 @@ babel-runtime@6.x, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -backport@^8.9.2: - version "8.9.2" - resolved "https://registry.yarnpkg.com/backport/-/backport-8.9.2.tgz#cf0ec69428f9e86c20e1898dd77e8f6c12bf5afa" - integrity sha512-0ghVAwSssE0mamADGnsOybWn7RroKLSf9r4uU1IpAlxxa2zkRecfnGuSfEa5L1tQjh7lJwI/2i01JR6154r+qg== +backport@^8.9.4: + version "8.9.4" + resolved "https://registry.yarnpkg.com/backport/-/backport-8.9.4.tgz#2bbe58fd766ebda6c760852d029630a277098a54" + integrity sha512-REMiogdMQ+TOLQoEABttcCevbxJ14xlCMkHn7es0ZTeCleHz6T2bl93w/Fe+JIttuyZ0e8oPQW2DVe1feTG1pw== dependencies: - "@octokit/rest" "^19.0.3" + "@octokit/rest" "^19.0.4" axios "^0.27.2" dedent "^0.7.0" del "^6.1.1" - dotenv "^16.0.1" + dotenv "^16.0.2" find-up "^5.0.0" - graphql "^16.5.0" + graphql "^16.6.0" graphql-tag "^2.12.6" inquirer "^8.2.3" lodash "^4.17.21" @@ -10499,9 +10511,9 @@ backport@^8.9.2: strip-json-comments "^3.1.1" terminal-link "^2.1.1" utility-types "^3.10.0" - winston "^3.8.1" + winston "^3.8.2" yargs "^17.5.1" - yargs-parser "^21.0.1" + yargs-parser "^21.1.1" bail@^1.0.0: version "1.0.2" @@ -13642,10 +13654,10 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" - integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== +dotenv@^16.0.2: + version "16.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" + integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== dotenv@^8.0.0: version "8.2.0" @@ -16136,10 +16148,10 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -graphql@^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" - integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== +graphql@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== growly@^1.3.0: version "1.3.0" @@ -28716,7 +28728,7 @@ winston-transport@^4.5.0: readable-stream "^3.6.0" triple-beam "^1.3.0" -winston@^3.3.3, winston@^3.8.1: +winston@^3.3.3: version "3.8.1" resolved "https://registry.yarnpkg.com/winston/-/winston-3.8.1.tgz#76f15b3478cde170b780234e0c4cf805c5a7fb57" integrity sha512-r+6YAiCR4uI3N8eQNOg8k3P3PqwAm20cLKlzVD9E66Ch39+LZC+VH1UKf9JemQj2B3QoUHfKD7Poewn0Pr3Y1w== @@ -28732,6 +28744,23 @@ winston@^3.3.3, winston@^3.8.1: triple-beam "^1.3.0" winston-transport "^4.5.0" +winston@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.8.2.tgz#56e16b34022eb4cff2638196d9646d7430fdad50" + integrity sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew== + dependencies: + "@colors/colors" "1.5.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.5.0" + wkt-parser@^1.2.4: version "1.3.2" resolved "https://registry.yarnpkg.com/wkt-parser/-/wkt-parser-1.3.2.tgz#deeff04a21edc5b170a60da418e9ed1d1ab0e219" @@ -29000,11 +29029,16 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^21.0.0, yargs-parser@^21.0.1: +yargs-parser@^21.0.0: version "21.0.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" From c17a468cf79c5481cbce5ddb3c3f086efd212b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 26 Sep 2022 12:48:39 +0200 Subject: [PATCH 007/172] [APM] Use bundled version of APM integration (#141624) --- .../client/apm_synthtrace_kibana_client.ts | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts index 7bd2443031c80..133ec096370b9 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts @@ -7,7 +7,6 @@ */ import fetch from 'node-fetch'; -import Semver from 'semver'; import { Logger } from '../../utils/create_logger'; export class ApmSynthtraceKibanaClient { @@ -53,31 +52,33 @@ export class ApmSynthtraceKibanaClient { return kibanaUrl; }); } - async fetchLatestApmPackageVersion(currentKibanaVersion: string) { - const url = `https://epr-snapshot.elastic.co/search?package=apm&prerelease=true&all=true&kibana.version=${currentKibanaVersion}`; - const response = await fetch(url, { method: 'GET' }); - const json = (await response.json()) as Array<{ version: string }>; - const packageVersions = (json ?? []).map((item) => item.version).sort(Semver.rcompare); - const validPackageVersions = packageVersions.filter((v) => Semver.valid(v)); - const bestMatch = validPackageVersions[0]; - if (!bestMatch) { - throw new Error( - `None of the available APM package versions matches the current Kibana version (${currentKibanaVersion}). The latest available version is ${packageVersions[0]}. This can happen if the Kibana version was recently bumped, and no matching APM package was released. Reach out to the fleet team if this persists.` - ); - } - return bestMatch; + + async fetchLatestApmPackageVersion( + kibanaUrl: string, + version: string, + username: string, + password: string + ) { + const url = `${kibanaUrl}/api/fleet/epm/packages/apm`; + const response = await fetch(url, { + method: 'GET', + headers: kibanaHeaders(username, password), + }); + const json = (await response.json()) as { item: { latestVersion: string } }; + const { latestVersion } = json.item; + return latestVersion; } async installApmPackage(kibanaUrl: string, version: string, username: string, password: string) { - const packageVersion = await this.fetchLatestApmPackageVersion(version); + const packageVersion = await this.fetchLatestApmPackageVersion( + kibanaUrl, + version, + username, + password + ); const response = await fetch(`${kibanaUrl}/api/fleet/epm/packages/apm/${packageVersion}`, { method: 'POST', - headers: { - Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), - Accept: 'application/json', - 'Content-Type': 'application/json', - 'kbn-xsrf': 'kibana', - }, + headers: kibanaHeaders(username, password), body: '{"force":true}', }); @@ -93,3 +94,12 @@ export class ApmSynthtraceKibanaClient { } else this.logger.error(responseJson); } } + +function kibanaHeaders(username: string, password: string) { + return { + Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), + Accept: 'application/json', + 'Content-Type': 'application/json', + 'kbn-xsrf': 'kibana', + }; +} From 668a9899d34de12c1e3a90925b0c30bad1edee32 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:04:09 +0500 Subject: [PATCH 008/172] [Console] Add tests for context menu and its actions (#141202) * Add tests for context menu and its actions * Skip test if clipboard permission is not granted * Fix type checks * Remove log statement Co-authored-by: Muhammad Ibragimov --- .../application/components/console_menu.tsx | 3 +- test/functional/apps/console/_context_menu.ts | 104 ++++++++++++++++++ test/functional/apps/console/index.js | 1 + test/functional/page_objects/console_page.ts | 36 ++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 test/functional/apps/console/_context_menu.ts diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 3f5113b3ac44f..b22fd7db9baab 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -128,6 +128,7 @@ export class ConsoleMenu extends Component { const items = [ { @@ -174,7 +175,7 @@ export class ConsoleMenu extends Component { panelPaddingSize="none" anchorPosition="downLeft" > - + ); diff --git a/test/functional/apps/console/_context_menu.ts b/test/functional/apps/console/_context_menu.ts new file mode 100644 index 0000000000000..8114d4d05097e --- /dev/null +++ b/test/functional/apps/console/_context_menu.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'console']); + const browser = getService('browser'); + const toasts = getService('toasts'); + + describe('console context menu', function testContextMenu() { + before(async () => { + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.clearTextArea(); + }); + + it('should open context menu', async () => { + expect(await PageObjects.console.isContextMenuOpen()).to.be(false); + await PageObjects.console.enterRequest(); + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + }); + + it('should have options to copy as curl, open documentation, and auto indent', async () => { + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + expect(PageObjects.console.isCopyAsCurlButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isOpenDocumentationButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isAutoIndentButtonVisible()).to.be.eql(true); + }); + + it('should copy as curl and show toast when copy as curl button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickCopyAsCurlButton(); + + const resultToast = await toasts.getToastElement(1); + const toastText = await resultToast.getVisibleText(); + + if (toastText.includes('Write permission denied')) { + log.debug('Write permission denied, skipping test'); + return; + } + + expect(toastText).to.be('Request copied as cURL'); + + const canReadClipboard = await browser.checkBrowserPermission('clipboard-read'); + if (canReadClipboard) { + const clipboardText = await browser.getClipboardValue(); + expect(clipboardText).to.contain('curl -XGET'); + } + }); + + it('should open documentation when open documentation button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickOpenDocumentationButton(); + + await retry.tryForTime(10000, async () => { + await browser.switchTab(1); + }); + + // Retry until the documentation is loaded + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain('search-search.html'); + }); + + // Close the documentation tab + await browser.closeCurrentWindow(); + await browser.switchTab(0); + }); + + it('should auto indent when auto indent button is clicked', async () => { + await PageObjects.console.clearTextArea(); + await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is auto indented + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}'); + }); + }); + + it('should condense when auto indent button is clicked again', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is condensed + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}'); + }); + }); + }); +} diff --git a/test/functional/apps/console/index.js b/test/functional/apps/console/index.js index 9dee3cda26e83..06c29f0e031ec 100644 --- a/test/functional/apps/console/index.js +++ b/test/functional/apps/console/index.js @@ -24,6 +24,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_variables')); loadTestFile(require.resolve('./_xjson')); loadTestFile(require.resolve('./_misc_console_behavior')); + loadTestFile(require.resolve('./_context_menu')); } }); } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 50cc2e7029d48..9f662a09146e9 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -368,4 +368,40 @@ export class ConsolePageObject extends FtrService { const textArea = await this.testSubjects.find('console-textarea'); await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], '/']); } + + public async clickContextMenu() { + const contextMenu = await this.testSubjects.find('toggleConsoleMenu'); + await contextMenu.click(); + } + + public async isContextMenuOpen() { + return await this.testSubjects.exists('consoleMenu'); + } + + public async isCopyAsCurlButtonVisible() { + return await this.testSubjects.exists('consoleMenuCopyAsCurl'); + } + + public async isOpenDocumentationButtonVisible() { + return await this.testSubjects.exists('consoleMenuOpenDocs'); + } + + public async isAutoIndentButtonVisible() { + return await this.testSubjects.exists('consoleMenuAutoIndent'); + } + + public async clickCopyAsCurlButton() { + const button = await this.testSubjects.find('consoleMenuCopyAsCurl'); + await button.click(); + } + + public async clickOpenDocumentationButton() { + const button = await this.testSubjects.find('consoleMenuOpenDocs'); + await button.click(); + } + + public async clickAutoIndentButton() { + const button = await this.testSubjects.find('consoleMenuAutoIndent'); + await button.click(); + } } From 24b7d34c55949e47b1d085190900dc8376acc5c9 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 26 Sep 2022 05:16:29 -0700 Subject: [PATCH 009/172] =?UTF-8?q?[ML]=20Explain=20Log=20Rate=20Spikes:?= =?UTF-8?q?=20Add=20tooltip=20for=20group=20columns=20and=20adjust=20copy?= =?UTF-8?q?=20to=20reflect=20groups=20in=20ot=E2=80=A6=20(#141687)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the groups table column tooltips to reflect group language and adds a tooltip to the Group column for clarification of column values. --- .../spike_analysis_table_groups.tsx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index 5bbc0cecae4d4..6c8da64cb0e89 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -228,9 +228,26 @@ export const SpikeAnalysisGroupsTable: FC = ({ { 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnGroup', field: 'group', - name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.groupLabel', { - defaultMessage: 'Group', - }), + name: ( + + <> + + + + + ), render: (_, { group, repeatedValues }) => { const valuesBadges = []; for (const fieldName in group) { @@ -287,7 +304,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.logRateColumnTooltip', { defaultMessage: - 'A visual representation of the impact of the field on the message rate difference', + 'A visual representation of the impact of the group on the message rate difference', } )} > @@ -361,9 +378,9 @@ export const SpikeAnalysisGroupsTable: FC = ({ From 62820968a76c521036bc7a2f41a2de313576bbbf Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 26 Sep 2022 14:23:01 +0200 Subject: [PATCH 010/172] [Security Solution][Detections] Format execution gap in a human readable way (#141363) **Fixes:** [#138872](https://github.com/elastic/kibana/issues/138872) ## Summary The patch formats the rule execution gap column on the rule monitoring tab in a human readable way. Before: ![image](https://user-images.githubusercontent.com/3775283/191710889-9b3fde9d-32c8-4beb-8a9f-28a0143cf41c.png) After: ![image](https://user-images.githubusercontent.com/3775283/191710563-fe74e6fc-1a88-4b5a-aac5-10b6e61945b5.png) ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../pages/detection_engine/rules/all/use_columns.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx index 3b413cf67e4d5..c5032a1eb9b72 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx @@ -9,6 +9,7 @@ import type { EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eu import { EuiBadge, EuiLink, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; +import moment from 'moment'; import { IntegrationsPopover } from '../../../../components/rules/related_integrations/integrations_popover'; import { DEFAULT_RELATIVE_DATE_THRESHOLD, @@ -387,7 +388,7 @@ export const useMonitoringColumns = ({ hasPermissions }: ColumnsProps): TableCol ), render: (value: DurationMetric | undefined) => ( - {value != null ? value.toFixed() : getEmptyTagValue()} + {value != null ? moment.duration(value, 'seconds').humanize() : getEmptyTagValue()} ), sortable: !!isInMemorySorting, From add2a1240de10fdb6243d8bd80b0bd6c4719989f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Sep 2022 14:25:47 +0200 Subject: [PATCH 011/172] [Files] Dispose of subscriptions on unmount (#141597) * added dispose logic to state * hook dispose logic in to component lifecycle --- .../components/upload_file/upload_file.tsx | 2 + .../components/upload_file/upload_state.ts | 60 +++++++++++-------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx index c3b958c12d22d..8e0d8ed59392b 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx @@ -117,6 +117,8 @@ export const UploadFile = ({ return () => subs.forEach((sub) => sub.unsubscribe()); }, [uploadState, onDone, onError]); + useEffect(() => uploadState.dispose, [uploadState]); + return ( (); + private subscriptions: Subscription[]; + constructor( private readonly fileKind: FileKind, private readonly client: FilesClient, private readonly opts: UploadOptions = { allowRepeatedUploads: false } ) { const latestFiles$ = this.files$$.pipe(switchMap((files$) => combineLatest(files$))); - - latestFiles$ - .pipe( - map((files) => files.some((file) => file.status === 'uploading')), - distinctUntilChanged() - ) - .subscribe(this.uploading$); - - latestFiles$ - .pipe( - map((files) => { - const errorFile = files.find((file) => Boolean(file.error)); - return errorFile ? errorFile.error : undefined; - }), - filter(Boolean) - ) - .subscribe(this.error$); - - latestFiles$ - .pipe( - filter( - (files) => Boolean(files.length) && files.every((file) => file.status === 'uploaded') - ), - map((files) => files.map((file) => ({ id: file.id!, kind: this.fileKind.id }))) - ) - .subscribe(this.done$); + this.subscriptions = [ + latestFiles$ + .pipe( + map((files) => files.some((file) => file.status === 'uploading')), + distinctUntilChanged() + ) + .subscribe(this.uploading$), + + latestFiles$ + .pipe( + map((files) => { + const errorFile = files.find((file) => Boolean(file.error)); + return errorFile ? errorFile.error : undefined; + }), + filter(Boolean) + ) + .subscribe(this.error$), + + latestFiles$ + .pipe( + filter( + (files) => Boolean(files.length) && files.every((file) => file.status === 'uploaded') + ), + map((files) => files.map((file) => ({ id: file.id!, kind: this.fileKind.id }))) + ) + .subscribe(this.done$), + ]; } public isUploading(): boolean { @@ -226,6 +230,10 @@ export class UploadState { return upload$; }; + + public dispose = (): void => { + for (const sub of this.subscriptions) sub.unsubscribe(); + }; } export const createUploadState = ({ From adffaa48d611051301d581d40280963928aeea0a Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:33:20 +0200 Subject: [PATCH 012/172] [Fleet] added cardinality agg when counting acks in action_status (#141651) * added cardinality agg when counting acks in action_status * added precision_threshold, added tests for activity flyout * fixed tests * fixed tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/agent_activity_flyout.test.tsx | 316 ++++++++++++++++++ .../components/agent_activity_flyout.tsx | 26 +- .../server/services/agents/action_status.ts | 8 +- 3 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx new file mode 100644 index 0000000000000..68a3420780039 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx @@ -0,0 +1,316 @@ +/* + * 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 { act, render, fireEvent } from '@testing-library/react'; +// eslint-disable-next-line @kbn/eslint/module_migration +import { IntlProvider } from 'react-intl'; + +import { useActionStatus } from '../hooks'; +import { useGetAgentPolicies, useStartServices } from '../../../../hooks'; + +import { AgentActivityFlyout } from './agent_activity_flyout'; + +jest.mock('../hooks'); +jest.mock('../../../../hooks'); + +const mockUseActionStatus = useActionStatus as jest.Mock; +const mockUseGetAgentPolicies = useGetAgentPolicies as jest.Mock; +const mockUseStartServices = useStartServices as jest.Mock; + +describe('AgentActivityFlyout', () => { + const mockOnClose = jest.fn(); + const mockOnAbortSuccess = jest.fn(); + const mockAbortUpgrade = jest.fn(); + + beforeEach(() => { + mockOnClose.mockReset(); + mockOnAbortSuccess.mockReset(); + mockAbortUpgrade.mockReset(); + mockUseActionStatus.mockReset(); + mockUseGetAgentPolicies.mockReturnValue({ + data: { + items: [ + { id: 'policy1', name: 'Policy 1' }, + { id: 'policy2', name: 'Policy 2' }, + ], + }, + }); + mockUseStartServices.mockReturnValue({ + docLinks: { links: { fleet: { upgradeElasticAgent: 'https://elastic.co' } } }, + }); + }); + + beforeEach(() => { + jest.useFakeTimers('modern').setSystemTime(new Date('2022-09-15T10:00:00.000Z')); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const renderComponent = () => { + return render( + + + + ); + }; + + it('should render agent activity for in progress upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('Upgrading 5 agents to version 8.5.0'); + // compare without whitespace,   doesn't match + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Started on Sep 15, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for scheduled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-16T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-17T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('5 agents scheduled to upgrade to version 8.5.0'); + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Scheduled for Sep 16, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for complete upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action3', + nbAgentsActionCreated: 2, + nbAgentsAck: 2, + type: 'UPGRADE', + nbAgentsActioned: 2, + status: 'COMPLETE', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + completionTime: '2022-09-15T12:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '2 agents upgraded' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); + + it('should render agent activity for expired unenroll', () => { + const mockActionStatuses = [ + { + actionId: 'action4', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNENROLL', + nbAgentsActioned: 3, + status: 'EXPIRED', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent unenrollment expired' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Expired on Sep 14, 2022 10:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for cancelled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action5', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 3, + status: 'CANCELLED', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + cancellationTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent upgrade cancelled' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Cancelled on Sep 15, 2022 11:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for failed reassign', () => { + const mockActionStatuses = [ + { + actionId: 'action7', + nbAgentsActionCreated: 1, + nbAgentsAck: 0, + type: 'POLICY_REASSIGN', + nbAgentsActioned: 1, + status: 'FAILED', + expiration: '2099-09-16T10:00:00.000Z', + newPolicyId: 'policy1', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 1, + completionTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 1 agent assigned to a new policy' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain( + 'A problem occurred during this operation. Started on Sep 15, 2022 10:00 AM.'.replace( + /\s/g, + '' + ) + ); + }); + + it('should render agent activity for unknown action', () => { + const mockActionStatuses = [ + { + actionId: 'action8', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNKNOWN', + nbAgentsActioned: 3, + status: 'COMPLETE', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + completionTime: '2022-09-15T12:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 3 agents actioned' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index 929eb4bbf54d1..dfa6623f4a90a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -176,6 +176,7 @@ export const AgentActivityFlyout: React.FunctionComponent<{ ) : null} {Object.keys(otherDays).map((day) => ( } actions={otherDays[day]} abortUpgrade={abortUpgrade} @@ -215,9 +216,13 @@ const ActivitySection: React.FunctionComponent<{ {actions.map((currentAction) => currentAction.type === 'UPGRADE' && currentAction.status === 'IN_PROGRESS' ? ( - + ) : ( - + ) )} @@ -272,7 +277,7 @@ const inProgressTitle = (action: ActionStatus) => ( defaultMessage="{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}" values={{ nbAgents: - action.nbAgentsAck === action.nbAgentsActioned + action.nbAgentsAck >= action.nbAgentsActioned ? action.nbAgentsAck : action.nbAgentsAck === 0 ? action.nbAgentsActioned @@ -438,14 +443,19 @@ const ActivityItem: React.FunctionComponent<{ action: ActionStatus }> = ({ actio {displayByStatus[action.status].icon} - + {displayByStatus[action.status].title} - {displayByStatus[action.status].description} + + {displayByStatus[action.status].description} + @@ -486,7 +496,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ {isScheduled ? : } - + {isScheduled && action.startTime ? ( - +

{isScheduled && action.startTime ? ( <> @@ -541,7 +551,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ size="s" onClick={onClickAbortUpgrade} isLoading={isAborting} - data-test-subj="currentBulkUpgrade.abortBtn" + data-test-subj="abortBtn" > bucket.key === action.actionId ); - const nbAgentsAck = matchingBucket?.doc_count ?? 0; + const nbAgentsAck = (matchingBucket?.agent_count as any)?.value ?? 0; const completionTime = (matchingBucket?.max_timestamp as any)?.value_as_string; const nbAgentsActioned = action.nbAgentsActioned || action.nbAgentsActionCreated; const complete = nbAgentsAck >= nbAgentsActioned; From 0a7a1a9200f2677635156019e9df28bc454c6090 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Mon, 26 Sep 2022 09:02:07 -0400 Subject: [PATCH 013/172] Create GetSLO application service (#141518) Co-authored-by: Faisal Kanout --- .../observability/server/routes/slo/route.ts | 27 ++++++++- .../server/services/slo/get_slo.test.ts | 55 +++++++++++++++++++ .../server/services/slo/get_slo.ts | 31 +++++++++++ .../server/services/slo/index.ts | 1 + .../server/types/rest_specs/slo.ts | 26 ++++----- 5 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/observability/server/services/slo/get_slo.test.ts create mode 100644 x-pack/plugins/observability/server/services/slo/get_slo.ts diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index dc1db41e44995..3f04d5e0b13c6 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -11,14 +11,20 @@ import { DefaultResourceInstaller, DefaultTransformManager, KibanaSavedObjectsSLORepository, + GetSLO, } from '../../services/slo'; + import { ApmTransactionDurationTransformGenerator, ApmTransactionErrorRateTransformGenerator, TransformGenerator, } from '../../services/slo/transform_generators'; import { SLITypes } from '../../types/models'; -import { createSLOParamsSchema, deleteSLOParamsSchema } from '../../types/rest_specs'; +import { + createSLOParamsSchema, + deleteSLOParamsSchema, + getSLOParamsSchema, +} from '../../types/rest_specs'; import { createObservabilityServerRoute } from '../create_observability_server_route'; const transformGenerators: Record = { @@ -78,4 +84,21 @@ const deleteSLORoute = createObservabilityServerRoute({ }, }); -export const slosRouteRepository = { ...createSLORoute, ...deleteSLORoute }; +const getSLORoute = createObservabilityServerRoute({ + endpoint: 'GET /api/observability/slos/{id}', + options: { + tags: [], + }, + params: getSLOParamsSchema, + handler: async ({ context, params }) => { + const soClient = (await context.core).savedObjects.client; + const repository = new KibanaSavedObjectsSLORepository(soClient); + const getSLO = new GetSLO(repository); + + const response = await getSLO.execute(params.path.id); + + return response; + }, +}); + +export const slosRouteRepository = { ...createSLORoute, ...getSLORoute, ...deleteSLORoute }; diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.test.ts b/x-pack/plugins/observability/server/services/slo/get_slo.test.ts new file mode 100644 index 0000000000000..768424d6b9eec --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/get_slo.test.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 { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; +import { GetSLO } from './get_slo'; +import { createSLORepositoryMock } from './mocks'; +import { SLORepository } from './slo_repository'; + +describe('GetSLO', () => { + let mockRepository: jest.Mocked; + let getSLO: GetSLO; + + beforeEach(() => { + mockRepository = createSLORepositoryMock(); + getSLO = new GetSLO(mockRepository); + }); + + describe('happy path', () => { + it('retrieves the SLO from the repository', async () => { + const slo = createSLO(createAPMTransactionErrorRateIndicator()); + mockRepository.findById.mockResolvedValueOnce(slo); + + const result = await getSLO.execute(slo.id); + + expect(mockRepository.findById).toHaveBeenCalledWith(slo.id); + expect(result).toEqual({ + id: slo.id, + name: 'irrelevant', + description: 'irrelevant', + budgeting_method: 'occurrences', + indicator: { + params: { + environment: 'irrelevant', + good_status_codes: ['2xx', '3xx', '4xx'], + service: 'irrelevant', + transaction_name: 'irrelevant', + transaction_type: 'irrelevant', + }, + type: 'slo.apm.transaction_error_rate', + }, + objective: { + target: 0.999, + }, + time_window: { + duration: '7d', + is_rolling: true, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.ts b/x-pack/plugins/observability/server/services/slo/get_slo.ts new file mode 100644 index 0000000000000..1730dec464964 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/get_slo.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. + */ + +import { SLO } from '../../types/models'; +import { GetSLOResponse } from '../../types/rest_specs'; +import { SLORepository } from './slo_repository'; + +export class GetSLO { + constructor(private repository: SLORepository) {} + + public async execute(sloId: string): Promise { + const slo = await this.repository.findById(sloId); + return this.toResponse(slo); + } + + private toResponse(slo: SLO): GetSLOResponse { + return { + id: slo.id, + name: slo.name, + description: slo.description, + indicator: slo.indicator, + time_window: slo.time_window, + budgeting_method: slo.budgeting_method, + objective: slo.objective, + }; + } +} diff --git a/x-pack/plugins/observability/server/services/slo/index.ts b/x-pack/plugins/observability/server/services/slo/index.ts index 895a7f4497ad5..7515262628f08 100644 --- a/x-pack/plugins/observability/server/services/slo/index.ts +++ b/x-pack/plugins/observability/server/services/slo/index.ts @@ -10,3 +10,4 @@ export * from './slo_repository'; export * from './transform_manager'; export * from './create_slo'; export * from './delete_slo'; +export * from './get_slo'; diff --git a/x-pack/plugins/observability/server/types/rest_specs/slo.ts b/x-pack/plugins/observability/server/types/rest_specs/slo.ts index dec07a5d4174f..7ee912cfc3303 100644 --- a/x-pack/plugins/observability/server/types/rest_specs/slo.ts +++ b/x-pack/plugins/observability/server/types/rest_specs/slo.ts @@ -8,17 +8,8 @@ import * as t from 'io-ts'; import { commonSLOSchema } from '../schema'; -const createSLOBodySchema = t.intersection([ - commonSLOSchema, - t.partial({ - settings: t.partial({ - destination_index: t.string, - }), - }), -]); - const createSLOParamsSchema = t.type({ - body: createSLOBodySchema, + body: commonSLOSchema, }); const createSLOResponseSchema = t.type({ @@ -31,8 +22,17 @@ const deleteSLOParamsSchema = t.type({ }), }); -type CreateSLOParams = t.TypeOf; +const getSLOParamsSchema = t.type({ + path: t.type({ + id: t.string, + }), +}); + +const getSLOResponseSchema = t.intersection([t.type({ id: t.string }), commonSLOSchema]); + +type CreateSLOParams = t.TypeOf; type CreateSLOResponse = t.TypeOf; +type GetSLOResponse = t.TypeOf; -export { createSLOParamsSchema, deleteSLOParamsSchema }; -export type { CreateSLOParams, CreateSLOResponse }; +export { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema }; +export type { CreateSLOParams, CreateSLOResponse, GetSLOResponse }; From 7f4608bf669ee860cdf969c685c640e416e98c5f Mon Sep 17 00:00:00 2001 From: Andrew Tate Date: Mon, 26 Sep 2022 08:04:37 -0500 Subject: [PATCH 014/172] [Lens] use min metric height from theme (#141683) * use min height from charts theme * update test mock Co-authored-by: Stratoula Kalafateli --- .../expression_metric/public/__mocks__/theme_service.ts | 1 + .../expression_metric/public/components/metric_vis.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/chart_expressions/expression_metric/public/__mocks__/theme_service.ts b/src/plugins/chart_expressions/expression_metric/public/__mocks__/theme_service.ts index 9690bd4a2d486..b00ec8a6ee569 100644 --- a/src/plugins/chart_expressions/expression_metric/public/__mocks__/theme_service.ts +++ b/src/plugins/chart_expressions/expression_metric/public/__mocks__/theme_service.ts @@ -9,5 +9,6 @@ export const getThemeService = () => { return { useChartsTheme: () => ({}), + useChartsBaseTheme: () => ({ metric: { minHeight: 64 } }), }; }; diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx index 539b41950a23c..f3e7a2864ec86 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx @@ -311,15 +311,18 @@ export const MetricVis = ({ const scrollContainerRef = useRef(null); const scrollDimensions = useResizeObserver(scrollContainerRef.current); + const { + metric: { minHeight }, + } = getThemeService().useChartsBaseTheme(); + useEffect(() => { - const minTileHeight = 64; // TODO - magic number from the @elastic/charts side. would be nice to deduplicate - const minimumRequiredVerticalSpace = minTileHeight * grid.length; + const minimumRequiredVerticalSpace = minHeight * grid.length; setScrollChildHeight( (scrollDimensions.height ?? -Infinity) > minimumRequiredVerticalSpace ? '100%' : `${minimumRequiredVerticalSpace}px` ); - }, [grid.length, scrollDimensions.height]); + }, [grid.length, minHeight, scrollDimensions.height]); return (

Date: Mon, 26 Sep 2022 15:12:18 +0200 Subject: [PATCH 015/172] Move save button into connector config form (#141361) * move save button into connector config form --- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../create_connector_flyout/footer.tsx | 70 +---- .../create_connector_flyout/index.test.tsx | 204 ++++++------- .../create_connector_flyout/index.tsx | 69 ++++- .../edit_connector_flyout/footer.tsx | 75 +---- .../edit_connector_flyout/index.test.tsx | 234 +++++++++------ .../edit_connector_flyout/index.tsx | 284 +++++++++++------- .../components/actions_connectors_list.tsx | 2 +- .../apps/triggers_actions_ui/connectors.ts | 15 +- 11 files changed, 488 insertions(+), 474 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 4b06b5ae9b13e..1066277da776f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -32422,7 +32422,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "Enregistrer le calendrier", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "Fuseau horaire", "xpack.triggersActionsUI.sections.actionConnectorAdd.backButtonLabel": "Retour", - "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "Annuler", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "Gérer la licence", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "Enregistrer et tester", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel": "Enregistrer", @@ -32528,11 +32527,9 @@ "xpack.triggersActionsUI.sections.connectorAddInline.unableToLoadConnectorTitle'": "Impossible de charger le connecteur", "xpack.triggersActionsUI.sections.connectorAddInline.unauthorizedToCreateForEmptyConnectors": "Seuls les utilisateurs autorisés peuvent configurer un connecteur. Contactez votre administrateur.", "xpack.triggersActionsUI.sections.deprecatedTitleMessage": "(déclassé)", - "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "Annuler", "xpack.triggersActionsUI.sections.editConnectorForm.descriptionText": "Ce connecteur est en lecture seule.", "xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle": "Modifier un connecteur", "xpack.triggersActionsUI.sections.editConnectorForm.preconfiguredHelpLabel": "En savoir plus sur les connecteurs préconfigurés.", - "xpack.triggersActionsUI.sections.editConnectorForm.saveAndCloseButtonLabel": "Enregistrer et fermer", "xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "Enregistrer", "xpack.triggersActionsUI.sections.editConnectorForm.tabText": "Configuration", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "Impossible de mettre à jour un connecteur.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 95f9d1a2494be..e077421392645 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -32396,7 +32396,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "スケジュールを保存", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "タイムゾーン", "xpack.triggersActionsUI.sections.actionConnectorAdd.backButtonLabel": "戻る", - "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "ライセンスの管理", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存してテスト", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel": "保存", @@ -32502,11 +32501,9 @@ "xpack.triggersActionsUI.sections.connectorAddInline.unableToLoadConnectorTitle'": "コネクターを読み込めません", "xpack.triggersActionsUI.sections.connectorAddInline.unauthorizedToCreateForEmptyConnectors": "許可されたユーザーのみがコネクターを構成できます。管理者にお問い合わせください。", "xpack.triggersActionsUI.sections.deprecatedTitleMessage": "(非推奨)", - "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.editConnectorForm.descriptionText": "このコネクターは読み取り専用です。", "xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle": "コネクターを編集", "xpack.triggersActionsUI.sections.editConnectorForm.preconfiguredHelpLabel": "あらかじめ構成されたコネクターの詳細をご覧ください。", - "xpack.triggersActionsUI.sections.editConnectorForm.saveAndCloseButtonLabel": "保存して閉じる", "xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.editConnectorForm.tabText": "構成", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "コネクターを更新できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3d8f0576aa13d..41c4320dae010 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -32430,7 +32430,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "保存计划", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "时区", "xpack.triggersActionsUI.sections.actionConnectorAdd.backButtonLabel": "返回", - "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "管理许可证", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存并测试", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel": "保存", @@ -32536,11 +32535,9 @@ "xpack.triggersActionsUI.sections.connectorAddInline.unableToLoadConnectorTitle'": "无法加载连接器", "xpack.triggersActionsUI.sections.connectorAddInline.unauthorizedToCreateForEmptyConnectors": "只有获得授权的用户才能配置连接器。请联系您的管理员。", "xpack.triggersActionsUI.sections.deprecatedTitleMessage": "(已过时)", - "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.editConnectorForm.descriptionText": "此连接器为只读。", "xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle": "编辑连接器", "xpack.triggersActionsUI.sections.editConnectorForm.preconfiguredHelpLabel": "详细了解预配置的连接器。", - "xpack.triggersActionsUI.sections.editConnectorForm.saveAndCloseButtonLabel": "保存并关闭", "xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.editConnectorForm.tabText": "配置", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "无法更新连接器。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/footer.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/footer.tsx index 4c4ccfe965716..c73c6ed042810 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/footer.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/footer.tsx @@ -6,35 +6,16 @@ */ import React, { memo } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutFooter, - EuiButton, - EuiButtonEmpty, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; interface Props { - isSaving: boolean; - disabled: boolean; hasConnectorTypeSelected: boolean; - onSubmit: () => Promise; - onTestConnector?: () => void; onBack: () => void; onCancel: () => void; } -const FlyoutFooterComponent: React.FC = ({ - isSaving, - disabled, - hasConnectorTypeSelected, - onCancel, - onBack, - onTestConnector, - onSubmit, -}) => { +const FlyoutFooterComponent: React.FC = ({ hasConnectorTypeSelected, onCancel, onBack }) => { return ( @@ -49,57 +30,16 @@ const FlyoutFooterComponent: React.FC = ({ )} ) : ( - + {i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel', + 'xpack.triggersActionsUI.sections.actionConnectorAdd.closeButtonLabel', { - defaultMessage: 'Cancel', + defaultMessage: 'Close', } )} )} - {hasConnectorTypeSelected ? ( - - - <> - {onTestConnector && ( - - - - - - )} - - - - - - - - - ) : null} ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx index f432f8fd6634c..a0ca1a501dc9c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx @@ -102,6 +102,106 @@ describe('CreateConnectorFlyout', () => { expect(await getByTestId(`${actionTypeModel.id}-card`)).toBeInTheDocument(); }); + it('shows the correct buttons without an action type selected', async () => { + const { getByTestId, queryByTestId } = appMockRenderer.render( + + ); + await act(() => Promise.resolve()); + + expect(getByTestId('create-connector-flyout-close-btn')).toBeInTheDocument(); + expect(queryByTestId('create-connector-flyout-save-test-btn')).toBe(null); + expect(queryByTestId('create-connector-flyout-save-btn')).toBe(null); + }); + + it('shows the correct buttons when selecting an action type', async () => { + const { getByTestId } = appMockRenderer.render( + + ); + await act(() => Promise.resolve()); + + act(() => { + userEvent.click(getByTestId(`${actionTypeModel.id}-card`)); + }); + + await waitFor(() => { + expect(getByTestId('create-connector-flyout-back-btn')).toBeInTheDocument(); + expect(getByTestId('create-connector-flyout-save-test-btn')).toBeInTheDocument(); + expect(getByTestId('create-connector-flyout-save-btn')).toBeInTheDocument(); + }); + }); + + it('does not show the save and test button if the onTestConnector is not provided', async () => { + const { queryByTestId } = appMockRenderer.render( + + ); + await act(() => Promise.resolve()); + + expect(queryByTestId('create-connector-flyout-save-test-btn')).not.toBeInTheDocument(); + }); + + it('disables the buttons when the user does not have permissions to create a connector', async () => { + appMockRenderer.coreStart.application.capabilities = { + ...appMockRenderer.coreStart.application.capabilities, + actions: { save: false, show: true }, + }; + + const { getByTestId } = appMockRenderer.render( + + ); + await act(() => Promise.resolve()); + + expect(getByTestId('create-connector-flyout-close-btn')).not.toBeDisabled(); + }); + + it('disables the buttons when there are error on the form', async () => { + const { getByTestId } = appMockRenderer.render( + + ); + await act(() => Promise.resolve()); + + act(() => { + userEvent.click(getByTestId(`${actionTypeModel.id}-card`)); + }); + + await waitFor(() => { + expect(getByTestId('test-connector-text-field')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(getByTestId('create-connector-flyout-save-btn')); + }); + + await waitFor(() => { + expect(getByTestId('create-connector-flyout-back-btn')).not.toBeDisabled(); + expect(getByTestId('create-connector-flyout-save-test-btn')).toBeDisabled(); + expect(getByTestId('create-connector-flyout-save-btn')).toBeDisabled(); + }); + }); + describe('Licensing', () => { it('renders banner with subscription links when gold features are disabled due to licensing', async () => { const disabledActionType = actionTypeRegistryMock.createMockActionTypeModel(); @@ -509,44 +609,6 @@ describe('CreateConnectorFlyout', () => { }); describe('Footer', () => { - it('shows the correct buttons without an action type selected', async () => { - const { getByTestId, queryByTestId } = appMockRenderer.render( - - ); - await act(() => Promise.resolve()); - - expect(getByTestId('create-connector-flyout-cancel-btn')).toBeInTheDocument(); - expect(queryByTestId('create-connector-flyout-save-test-btn')).toBe(null); - expect(queryByTestId('create-connector-flyout-save-btn')).toBe(null); - }); - - it('shows the correct buttons when selecting an action type', async () => { - const { getByTestId } = appMockRenderer.render( - - ); - await act(() => Promise.resolve()); - - act(() => { - userEvent.click(getByTestId(`${actionTypeModel.id}-card`)); - }); - - await waitFor(() => { - expect(getByTestId('create-connector-flyout-back-btn')).toBeInTheDocument(); - expect(getByTestId('create-connector-flyout-save-test-btn')).toBeInTheDocument(); - expect(getByTestId('create-connector-flyout-save-btn')).toBeInTheDocument(); - }); - }); - it('shows the action types when pressing the back button', async () => { const { getByTestId } = appMockRenderer.render( { expect(getByTestId(`${actionTypeModel.id}-card`)).toBeInTheDocument(); }); - it('does not show the save and test button if the onTestConnector is not provided', async () => { - const { queryByTestId } = appMockRenderer.render( - - ); - await act(() => Promise.resolve()); - - expect(queryByTestId('create-connector-flyout-save-test-btn')).not.toBeInTheDocument(); - }); - - it('closes the flyout when pressing cancel', async () => { + it('closes the flyout when pressing close', async () => { const { getByTestId } = appMockRenderer.render( { await act(() => Promise.resolve()); act(() => { - userEvent.click(getByTestId('create-connector-flyout-cancel-btn')); + userEvent.click(getByTestId('create-connector-flyout-close-btn')); }); expect(onClose).toHaveBeenCalled(); }); - - it('disables the buttons when the user does not have permissions to create a connector', async () => { - appMockRenderer.coreStart.application.capabilities = { - ...appMockRenderer.coreStart.application.capabilities, - actions: { save: false, show: true }, - }; - - const { getByTestId } = appMockRenderer.render( - - ); - await act(() => Promise.resolve()); - - expect(getByTestId('create-connector-flyout-cancel-btn')).not.toBeDisabled(); - }); - - it('disables the buttons when there are error on the form', async () => { - const { getByTestId } = appMockRenderer.render( - - ); - await act(() => Promise.resolve()); - - act(() => { - userEvent.click(getByTestId(`${actionTypeModel.id}-card`)); - }); - - await waitFor(() => { - expect(getByTestId('test-connector-text-field')).toBeInTheDocument(); - }); - - act(() => { - userEvent.click(getByTestId('create-connector-flyout-save-btn')); - }); - - await waitFor(() => { - expect(getByTestId('create-connector-flyout-back-btn')).not.toBeDisabled(); - expect(getByTestId('create-connector-flyout-save-test-btn')).toBeDisabled(); - expect(getByTestId('create-connector-flyout-save-btn')).toBeDisabled(); - }); - }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx index f25d86458632a..32bd7931aecc8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx @@ -6,9 +6,10 @@ */ import React, { memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ActionConnector, ActionType, @@ -163,15 +164,7 @@ const CreateConnectorFlyoutComponent: React.FC = ({ : null} > - {actionType == null ? ( - - ) : null} - {actionType != null ? ( + {hasConnectorTypeSelected ? ( <> = ({ isEdit={false} onChange={setFormState} /> - {preSubmitValidationErrorMessage} + {!!preSubmitValidationErrorMessage &&

{preSubmitValidationErrorMessage}

} + + + + <> + {onTestConnector && ( + + + + + + )} + + + + + + + + + - ) : null} + ) : ( + + )}
); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/footer.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/footer.tsx index 5f19449df2bb5..75d5e8aba4000 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/footer.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/footer.tsx @@ -6,84 +6,25 @@ */ import React, { memo } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutFooter, - EuiButton, - EuiButtonEmpty, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; interface Props { - isSaving: boolean; - showButtons: boolean; - disabled: boolean; - onCancel: () => void; - onSubmit: () => void; - onSubmitAndClose: () => void; + onClose: () => void; } -const FlyoutFooterComponent: React.FC = ({ - isSaving, - showButtons, - disabled, - onCancel, - onSubmit, - onSubmitAndClose, -}) => { +const FlyoutFooterComponent: React.FC = ({ onClose }) => { return ( - - {i18n.translate( - 'xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel', - { - defaultMessage: 'Cancel', - } - )} + + {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.closeButtonLabel', { + defaultMessage: 'Close', + })} - - - {showButtons ? ( - <> - - - - - - - - - - - - ) : null} - - + ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx index 8a828fcfc9539..23793d3c5766b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx @@ -88,6 +88,52 @@ describe('EditConnectorFlyout', () => { expect(getByTestId('edit-connector-flyout-footer')).toBeInTheDocument(); }); + it('enables save button when the form is modified', async () => { + const { getByTestId } = appMockRenderer.render( + + ); + expect(getByTestId('edit-connector-flyout-save-btn')).toBeDisabled(); + + await act(async () => { + await userEvent.clear(getByTestId('nameInput')); + await userEvent.type(getByTestId('nameInput'), 'My new name', { + delay: 10, + }); + }); + + expect(getByTestId('edit-connector-flyout-save-btn')).not.toBeDisabled(); + }); + + it('shows a confirmation modal on close if the form is modified', async () => { + const { getByTestId, getByText } = appMockRenderer.render( + + ); + expect(getByTestId('edit-connector-flyout-save-btn')).toBeDisabled(); + + await act(async () => { + await userEvent.clear(getByTestId('nameInput')); + await userEvent.type(getByTestId('nameInput'), 'My new name', { + delay: 10, + }); + }); + + act(() => { + userEvent.click(getByTestId('edit-connector-flyout-close-btn')); + }); + + expect(getByText('Discard unsaved changes to connector?')).toBeInTheDocument(); + }); + it('renders the connector form correctly', async () => { const { getByTestId, queryByText } = appMockRenderer.render( { expect(getByText('This connector is readonly.')).toBeInTheDocument(); }); + it('shows the buttons', async () => { + const { getByTestId } = appMockRenderer.render( + + ); + + expect(getByTestId('edit-connector-flyout-save-btn')).toBeInTheDocument(); + expect(getByTestId('edit-connector-flyout-close-btn')).toBeInTheDocument(); + }); + + it('does not show the save button if the use does not have permissions to update connector', async () => { + appMockRenderer.coreStart.application.capabilities = { + ...appMockRenderer.coreStart.application.capabilities, + actions: { save: false, show: true }, + }; + + const { queryByTestId } = appMockRenderer.render( + + ); + + expect(queryByTestId('edit-connector-flyout-save-btn')).not.toBeInTheDocument(); + }); + + it('does not show the save button if the connector is preconfigured', async () => { + const { queryByTestId } = appMockRenderer.render( + + ); + + expect(queryByTestId('edit-connector-flyout-save-btn')).not.toBeInTheDocument(); + }); + + it('disables the buttons when there are error on the form', async () => { + const { getByTestId } = appMockRenderer.render( + + ); + + await waitFor(() => { + expect(getByTestId('test-connector-text-field')).toBeInTheDocument(); + }); + + act(() => { + /** + * Clear the name so the form can be invalid + */ + userEvent.clear(getByTestId('nameInput')); + }); + act(() => { + userEvent.click(getByTestId('edit-connector-flyout-save-btn')); + }); + + await waitFor(() => { + expect(getByTestId('edit-connector-flyout-close-btn')).not.toBeDisabled(); + expect(getByTestId('edit-connector-flyout-save-btn')).toBeDisabled(); + }); + }); + describe('Header', () => { it('shows the icon', async () => { const { getByTestId } = appMockRenderer.render( @@ -327,7 +448,7 @@ describe('EditConnectorFlyout', () => { }); it('updates the connector and close the flyout correctly', async () => { - const { getByTestId } = appMockRenderer.render( + const { getByTestId, getByText } = appMockRenderer.render( { }); act(() => { - userEvent.click(getByTestId('edit-connector-flyout-save-close-btn')); + userEvent.click(getByTestId('edit-connector-flyout-save-btn')); }); await waitFor(() => { @@ -363,6 +484,12 @@ describe('EditConnectorFlyout', () => { ); }); + expect(getByText('Changes Saved')).toBeInTheDocument(); + + act(() => { + userEvent.click(getByTestId('edit-connector-flyout-close-btn')); + }); + expect(onClose).toHaveBeenCalled(); expect(onConnectorUpdated).toHaveBeenCalledWith({ actionTypeId: 'test', @@ -395,6 +522,13 @@ describe('EditConnectorFlyout', () => { expect(getByTestId('test-connector-error-text-field')).toBeInTheDocument(); }); + await act(async () => { + await userEvent.clear(getByTestId('nameInput')); + await userEvent.type(getByTestId('nameInput'), 'My new name', { + delay: 100, + }); + }); + act(() => { userEvent.click(getByTestId('edit-connector-flyout-save-btn')); }); @@ -559,100 +693,4 @@ describe('EditConnectorFlyout', () => { expect(getByTestId('executeActionButton')).toBeDisabled(); }); }); - - describe('Footer', () => { - it('shows the buttons', async () => { - const { getByTestId } = appMockRenderer.render( - - ); - - expect(getByTestId('edit-connector-flyout-cancel-btn')).toBeInTheDocument(); - expect(getByTestId('edit-connector-flyout-save-btn')).toBeInTheDocument(); - expect(getByTestId('edit-connector-flyout-save-close-btn')).toBeInTheDocument(); - }); - - it('does not show the save and save and close button if the use does not have permissions to update connector', async () => { - appMockRenderer.coreStart.application.capabilities = { - ...appMockRenderer.coreStart.application.capabilities, - actions: { save: false, show: true }, - }; - - const { queryByTestId } = appMockRenderer.render( - - ); - - expect(queryByTestId('edit-connector-flyout-save-btn')).not.toBeInTheDocument(); - expect(queryByTestId('edit-connector-flyout-save-close-btn')).not.toBeInTheDocument(); - }); - - it('does not show the save and save and close button if the connector is preconfigured', async () => { - const { queryByTestId } = appMockRenderer.render( - - ); - - expect(queryByTestId('edit-connector-flyout-save-btn')).not.toBeInTheDocument(); - expect(queryByTestId('edit-connector-flyout-save-close-btn')).not.toBeInTheDocument(); - }); - - it('closes the flyout when pressing cancel', async () => { - const { getByTestId } = appMockRenderer.render( - - ); - - act(() => { - userEvent.click(getByTestId('edit-connector-flyout-cancel-btn')); - }); - - expect(onClose).toHaveBeenCalled(); - }); - - it('disables the buttons when there are error on the form', async () => { - const { getByTestId } = appMockRenderer.render( - - ); - - await waitFor(() => { - expect(getByTestId('test-connector-text-field')).toBeInTheDocument(); - }); - - act(() => { - /** - * Clear the name so the form can be invalid - */ - userEvent.clear(getByTestId('nameInput')); - userEvent.click(getByTestId('edit-connector-flyout-save-btn')); - }); - - await waitFor(() => { - expect(getByTestId('edit-connector-flyout-cancel-btn')).not.toBeDisabled(); - expect(getByTestId('edit-connector-flyout-save-close-btn')).toBeDisabled(); - expect(getByTestId('edit-connector-flyout-save-btn')).toBeDisabled(); - }); - }); - }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx index 452a89d04f9c1..e00349cecd62b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx @@ -6,7 +6,14 @@ */ import React, { memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { EuiFlyout, EuiText, EuiFlyoutBody, EuiLink } from '@elastic/eui'; +import { + EuiFlyout, + EuiText, + EuiFlyoutBody, + EuiLink, + EuiButton, + EuiConfirmModal, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { ActionTypeExecutorResult, isActionTypeExecutorResult } from '@kbn/actions-plugin/common'; @@ -73,6 +80,7 @@ const EditConnectorFlyoutComponent: React.FC = ({ docLinks, application: { capabilities }, } = useKibana().services; + const isMounted = useRef(false); const canSave = hasSaveActionsCapability(capabilities); const { isLoading: isUpdatingConnector, updateConnector } = useUpdateConnector(); @@ -117,7 +125,9 @@ const EditConnectorFlyoutComponent: React.FC = ({ ); const [isFormModified, setIsFormModified] = useState(false); - + const [showConfirmModal, setShowConfirmModal] = useState(false); + const [isEdit, setIsEdit] = useState(true); + const [isSaved, setIsSaved] = useState(false); const { preSubmitValidator, submit, isValid: isFormValid, isSubmitting } = formState; const hasErrors = isFormValid === false; const isSaving = isUpdatingConnector || isSubmitting || isExecutingConnector; @@ -146,6 +156,9 @@ const EditConnectorFlyoutComponent: React.FC = ({ const onFormModifiedChange = useCallback( (formModified: boolean) => { + if (formModified) { + setIsSaved(false); + } setIsFormModified(formModified); setTestExecutionResult(none); }, @@ -153,76 +166,72 @@ const EditConnectorFlyoutComponent: React.FC = ({ ); const closeFlyout = useCallback(() => { + if (isFormModified) { + setShowConfirmModal(true); + return; + } onClose(); - }, [onClose]); + }, [onClose, isFormModified, setShowConfirmModal]); - const onClickSave = useCallback( - async (closeAfterSave: boolean = true) => { - setPreSubmitValidationErrorMessage(null); + const onClickSave = useCallback(async () => { + setPreSubmitValidationErrorMessage(null); - const { isValid, data } = await submit(); - if (!isMounted.current) { - // User has closed the flyout meanwhile submitting the form - return; - } + const { isValid, data } = await submit(); + if (!isMounted.current) { + // User has closed the flyout meanwhile submitting the form + return; + } - if (isValid) { - if (preSubmitValidator) { - const validatorRes = await preSubmitValidator(); + if (isValid) { + if (preSubmitValidator) { + const validatorRes = await preSubmitValidator(); - if (validatorRes) { - setPreSubmitValidationErrorMessage(validatorRes.message); - return; - } + if (validatorRes) { + setPreSubmitValidationErrorMessage(validatorRes.message); + return; } + } + + /** + * At this point the form is valid + * and there are no pre submit error messages. + */ + const { name, config, secrets } = data; + const validConnector = { + id: connector.id, + name: name ?? '', + config: config ?? {}, + secrets: secrets ?? {}, + }; + + const updatedConnector = await updateConnector(validConnector); + + if (updatedConnector) { /** - * At this point the form is valid - * and there are no pre submit error messages. + * ConnectorFormSchema has been saved. + * Set the from to clean state. */ + onFormModifiedChange(false); - const { name, config, secrets } = data; - const validConnector = { - id: connector.id, - name: name ?? '', - config: config ?? {}, - secrets: secrets ?? {}, - }; - - const updatedConnector = await updateConnector(validConnector); - - if (updatedConnector) { - /** - * ConnectorFormSchema has been saved. - * Set the from to clean state. - */ - onFormModifiedChange(false); - - if (onConnectorUpdated && updatedConnector) { - onConnectorUpdated(updatedConnector); - } - - if (closeAfterSave) { - closeFlyout(); - } + if (onConnectorUpdated) { + onConnectorUpdated(updatedConnector); } - - return updatedConnector; + setIsSaved(true); + setIsEdit(false); + setIsEdit(true); } - }, - [ - submit, - preSubmitValidator, - connector.id, - updateConnector, - onFormModifiedChange, - onConnectorUpdated, - closeFlyout, - ] - ); - const onSubmit = useCallback(() => onClickSave(false), [onClickSave]); - const onSubmitAndClose = useCallback(() => onClickSave(true), [onClickSave]); + return updatedConnector; + } + }, [ + onConnectorUpdated, + submit, + preSubmitValidator, + connector.id, + updateConnector, + onFormModifiedChange, + ]); useEffect(() => { isMounted.current = true; @@ -233,59 +242,114 @@ const EditConnectorFlyoutComponent: React.FC = ({ }, []); return ( - - - - {selectedTab === EditConnectorTabs.Configuration ? ( - !connector.isPreconfigured ? ( - <> - - {preSubmitValidationErrorMessage} - + <> + + + + {selectedTab === EditConnectorTabs.Configuration ? ( + !connector.isPreconfigured ? ( + <> + {isEdit && ( + <> + + {!!preSubmitValidationErrorMessage &&

{preSubmitValidationErrorMessage}

} + {showButtons && ( + + {isSaved ? ( + + ) : ( + + )} + + )} + + )} + + ) : ( + + ) ) : ( - - ) - ) : ( - + )} +
+ +
+ {showConfirmModal && ( + { + setShowConfirmModal(false); + }} + onConfirm={onClose} + cancelButtonText={i18n.translate( + 'xpack.triggersActionsUI.sections.confirmConnectorEditClose.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.triggersActionsUI.sections.confirmConnectorEditClose.discardButtonLabel', + { + defaultMessage: 'Discard Changes', + } + )} + > + - )} -
- -
+ + )} + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 249f9f503fcf8..e973fce282dcb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -525,7 +525,7 @@ const ActionsConnectorsList: React.FunctionComponent = () => { setEditConnectorProps(omit(editConnectorProps, 'initialConnector')); }} onConnectorUpdated={(connector) => { - setEditConnectorProps({ initialConnector: connector }); + setEditConnectorProps({ ...editConnectorProps, initialConnector: connector }); loadActions(); }} actionTypeRegistry={actionTypeRegistry} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 0abb9bd7a395e..cabbadb43ac57 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -88,12 +88,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.setValue('slackWebhookUrlInput', 'https://test.com'); await find.clickByCssSelector( - '[data-test-subj="edit-connector-flyout-save-close-btn"]:not(disabled)' + '[data-test-subj="edit-connector-flyout-save-btn"]:not(disabled)' ); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`); + await testSubjects.click('euiFlyoutCloseButton'); + await pageObjects.triggersActionsUI.searchConnectors(updatedConnectorName); const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getConnectorsList(); @@ -130,7 +132,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); await find.clickByCssSelector( - '[data-test-subj="edit-connector-flyout-cancel-btn"]:not(disabled)' + '[data-test-subj="edit-connector-flyout-close-btn"]:not(disabled)' ); }); @@ -158,7 +160,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); await find.clickByCssSelector( - '[data-test-subj="edit-connector-flyout-cancel-btn"]:not(disabled)' + '[data-test-subj="edit-connector-flyout-close-btn"]:not(disabled)' ); }); @@ -174,9 +176,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); await testSubjects.setValue('nameInput', 'some test name to cancel'); - await testSubjects.click('edit-connector-flyout-cancel-btn'); + await testSubjects.click('edit-connector-flyout-close-btn'); + await testSubjects.click('confirmModalConfirmButton'); - await find.waitForDeletedByCssSelector('[data-test-subj="edit-connector-flyout-cancel-btn"]'); + await find.waitForDeletedByCssSelector('[data-test-subj="edit-connector-flyout-close-btn"]'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -265,7 +268,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); expect(await testSubjects.exists('preconfiguredBadge')).to.be(true); - expect(await testSubjects.exists('edit-connector-flyout-save-close-btn')).to.be(false); + expect(await testSubjects.exists('edit-connector-flyout-save-btn')).to.be(false); }); }); From 85c8d379253e4f3b60bad0f70286f4243e4d5b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:22:35 +0200 Subject: [PATCH 016/172] [Guided onboarding] Updated the examples to only use the Observable for the active step check (#141265) --- .../public/components/step_one.tsx | 5 ++-- .../public/components/step_two.tsx | 26 +++++++++---------- .../public/constants/search.ts | 2 +- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/examples/guided_onboarding_example/public/components/step_one.tsx b/examples/guided_onboarding_example/public/components/step_one.tsx index bacb43ad0f67a..3441b4d8e5d99 100644 --- a/examples/guided_onboarding_example/public/components/step_one.tsx +++ b/examples/guided_onboarding_example/public/components/step_one.tsx @@ -55,9 +55,8 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>

diff --git a/examples/guided_onboarding_example/public/components/step_two.tsx b/examples/guided_onboarding_example/public/components/step_two.tsx index 9f96532450bfc..a79ce2329351e 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -11,7 +11,6 @@ import React, { useEffect, useState } from 'react'; import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui'; import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; -import { useHistory, useLocation } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageContentHeader_Deprecated as EuiPageContentHeader, @@ -26,18 +25,18 @@ export const StepTwo = (props: StepTwoProps) => { const { guidedOnboarding: { guidedOnboardingApi }, } = props; - const { search } = useLocation(); - const history = useHistory(); - - const query = React.useMemo(() => new URLSearchParams(search), [search]); - useEffect(() => { - if (query.get('showTour') === 'true') { - setIsTourStepOpen(true); - } - }, [query]); const [isTourStepOpen, setIsTourStepOpen] = useState(false); + useEffect(() => { + const subscription = guidedOnboardingApi + ?.isGuideStepActive$('search', 'browse_docs') + .subscribe((isStepActive) => { + setIsTourStepOpen(isStepActive); + }); + return () => subscription?.unsubscribe(); + }, [guidedOnboardingApi]); + return ( <> @@ -55,7 +54,8 @@ export const StepTwo = (props: StepTwoProps) => {

@@ -69,13 +69,11 @@ export const StepTwo = (props: StepTwoProps) => { isStepOpen={isTourStepOpen} minWidth={300} onFinish={() => { - history.push('/stepTwo'); - query.set('showTour', 'false'); setIsTourStepOpen(false); }} step={1} stepsTotal={1} - title="Step Add data" + title="Step Search experience" anchorPosition="rightUp" > Date: Mon, 26 Sep 2022 09:11:58 -0500 Subject: [PATCH 017/172] [TIP] Remove feature flag for Threat Intelligence plugin (#141117) --- .../security_solution/common/experimental_features.ts | 1 - .../navigation/use_security_solution_navigation/index.tsx | 3 --- .../security_solution/public/threat_intelligence/links.ts | 1 - .../public/threat_intelligence/routes.tsx | 7 ------- x-pack/test/security_solution_cypress/config.ts | 4 +--- x-pack/test/threat_intelligence_cypress/config.ts | 4 +--- 6 files changed, 2 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index db74802dcb599..fe65aab259395 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -20,7 +20,6 @@ export const allowedExperimentalValues = Object.freeze({ pendingActionResponsesWithAck: true, policyListEnabled: true, policyResponseInFleetEnabled: true, - threatIntelligenceEnabled: false, /** * This is used for enabling the end-to-end tests for the security_solution telemetry. diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx index 3a010bdb4fd7a..0444b183e9425 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx @@ -31,9 +31,6 @@ export const useSecuritySolutionNavigation = () => { const disabledNavTabs = [ ...(!useIsExperimentalFeatureEnabled('kubernetesEnabled') ? ['kubernetes'] : []), - ...(!useIsExperimentalFeatureEnabled('threatIntelligenceEnabled') - ? ['threat-intelligence'] - : []), ]; const enabledNavTabs: GenericNavRecord = omit(disabledNavTabs, navTabs); diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/links.ts b/x-pack/plugins/security_solution/public/threat_intelligence/links.ts index 5b59c0a50799f..c443b74690411 100644 --- a/x-pack/plugins/security_solution/public/threat_intelligence/links.ts +++ b/x-pack/plugins/security_solution/public/threat_intelligence/links.ts @@ -17,7 +17,6 @@ import type { LinkItem } from '../common/links'; */ export const indicatorsLinks: LinkItem = { ...getSecuritySolutionLink('indicators'), - experimentalKey: 'threatIntelligenceEnabled', globalNavPosition: 7, capabilities: [`${SERVER_APP_ID}.show`], }; diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx index ab197f60236c6..774d5ea06a350 100644 --- a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx +++ b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx @@ -6,7 +6,6 @@ */ import React, { memo } from 'react'; -import { Redirect } from 'react-router-dom'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import type { SecuritySolutionPluginContext } from '@kbn/threat-intelligence-plugin/public'; import { THREAT_INTELLIGENCE_BASE_PATH } from '@kbn/threat-intelligence-plugin/public'; @@ -17,7 +16,6 @@ import { getStore } from '../common/store'; import { useKibana } from '../common/lib/kibana'; import { FiltersGlobal } from '../common/components/filters_global'; import { SpyRoute } from '../common/utils/route/spy_routes'; -import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features'; import { licenseService } from '../common/hooks/use_license'; import { SecurityPageName } from '../app/types'; import type { SecuritySubPluginRoutes } from '../app/types'; @@ -30,11 +28,6 @@ const ThreatIntelligence = memo(() => { const sourcererDataView = useSourcererDataView(); - const enabled = useIsExperimentalFeatureEnabled('threatIntelligenceEnabled'); - if (!enabled) { - return ; - } - const securitySolutionStore = getStore() as Store; const securitySolutionContext: SecuritySolutionPluginContext = { diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 982d52920e2ac..e77ab1fe4d489 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -49,9 +49,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // See https://github.com/elastic/kibana/pull/125396 for details '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'threatIntelligenceEnabled', - ])}`, + `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, `--home.disableWelcomeScreen=true`, ], }, diff --git a/x-pack/test/threat_intelligence_cypress/config.ts b/x-pack/test/threat_intelligence_cypress/config.ts index 8d638789b61ff..3fc305ab35f74 100644 --- a/x-pack/test/threat_intelligence_cypress/config.ts +++ b/x-pack/test/threat_intelligence_cypress/config.ts @@ -48,9 +48,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // See https://github.com/elastic/kibana/pull/125396 for details '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'threatIntelligenceEnabled', - ])}`, + `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, `--home.disableWelcomeScreen=true`, ], }, From c1dc8671bbc70377e52d062918d793c441d51a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Mon, 26 Sep 2022 16:33:09 +0200 Subject: [PATCH 018/172] [DOCS] Expands documentation on Explain log rate spikes (#141370) --- docs/user/ml/images/classification.png | Bin 0 -> 82234 bytes .../ml/images/ml-explain-log-rate-before.png | Bin 93841 -> 217826 bytes docs/user/ml/images/ml-explain-log-rate.png | Bin 129559 -> 144957 bytes docs/user/ml/images/outliers.png | Bin 178520 -> 0 bytes docs/user/ml/index.asciidoc | 25 +++++++++--------- 5 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 docs/user/ml/images/classification.png delete mode 100644 docs/user/ml/images/outliers.png diff --git a/docs/user/ml/images/classification.png b/docs/user/ml/images/classification.png new file mode 100644 index 0000000000000000000000000000000000000000..492c44bfa5ce89a39a067ff00f4b126e61670bc1 GIT binary patch literal 82234 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 literal 0 HcmV?d00001 diff --git a/docs/user/ml/images/ml-explain-log-rate-before.png b/docs/user/ml/images/ml-explain-log-rate-before.png index e154ddbeebebfd3059b31df84ee087ae9d40b537..2fcfae89515672a61277191ac279e2ac89902a4c 100644 GIT binary patch literal 217826 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%0AB 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 Q1o)GBEB~fQOdtIJ0XIye(EtDd diff --git a/docs/user/ml/images/ml-explain-log-rate.png b/docs/user/ml/images/ml-explain-log-rate.png index c814ec50d6774e81638e85e50fbcdfa8a39b9097..be007b22e1d4cc26da7303b7ecd62b5ee868a95e 100644 GIT binary patch literal 144957 zcmeFZWmp|q(=JSK3-0b3Ah>PZA-H=8?(P;GLU8xs?!hfcaCesg!QK6IX6AX`%)Imc z_yJ9Yte*ygfV>yme4{_qB@}Q$aRqC|ptK`@n_AlL(bo`!bRD_#CtE z$sj`FnCZYZ^9ER}-oM8&s2QA7DPq}NTTo@czzMxZkyd43h0{5!^b0SUeHr9sZCXWd zE`Ooer>DD{QJmSF1s`|XYPJpk=nF<8(0uIK={#gAxXEf7p7l}I5FEoy@f9FyHDe7a z6Iod>I^Y-<>{W<47$k7?>k1z5185{R7z_sZjRyRPegXf_RmjLM5dS%bF!*(&kg|xB z6!2Tw(81W)#?j2yDMrw?A9&Q9xr&C9hU_~YLtAS`10&lH#*A*(cE6f{f!uh2Lu+Fv z0}?lDD;q~1H$JjI?%)BAf1PF`Bl+VNCrdsu4Os;e5nBgi5)Q_Dfb@sXK1Ioa_rF}b?BGP<%d+B%prF>`ZsGreVDVqswb?qG0qw{bFXW3X`~ z|MMaLd5);DqoISjos+q(4au+P8ho&IcH$!=`}Ly#{`<3^#%|{S^Cla|zlH@2km=VI zCT7OBO#gj0&=mCRERTY@o3WLqsJS&D9^f7PTpaA6KkoluSN`XXzqQo(pO(zb92~zl z{q52}o2oh*JBZj?1MhU={~y)-)%f>|e>DU#{TljjqWDwJf1Cvb%?}S^`mfUX;XO*? zWx&7$!K6fmRNP)2WL4Rw-|3VM~ftqo;FjYv{`Os z!iOPZf{OCnPEVn2x};?kryF+rwtFQW`mp}%$U8}BOy0w`EMWlLawn753Ac;LLiu*5dbWZ_t>zR)%vsPALD(%H`8h=BZ(YiS z)96Cwiv)?lM=|V-=|>kS=1Rs=NOc*yF2leDe)?9f-LN&8KNQRF-K;QkQz-3vA`ma5 zZZpWoE}KqLpC_Fv81i&K#eA-z0|!#mGe&`D(rd~uaHW*$;zFZ`hDo%e{LcQn!k>jn zhJ$kPIc)}?9&Utt=shlX(U|mFQD)$Vx^9kF;?g*6wOT^)*h?rRqrYkSB;vMq+RU}* zzR_=2;+*vMjFRi3@}%PW$6#)>hsIcH4g2KW8}uMTsM=R* zq-uj?9?ZtXkatx58c^`JI?FkV9ZUC-oJ=Ib^QP7pA8)=F zq3+)EqXzBrBj92@bFPHe&@&r^e6H`Peu7chk9|8zUmgx3nSMAObv+5}=N4M?Sn6mE z<~?@|2mi9))qmQG9*Q?h*y_nSo=aJFgQ<2T;PFUPmWt)3&b55LX#ip&UPc#e-E-)_Xdyc66Q%Ti}?OF#m|J>olH~bCre%k=#2uM4^ zW#3Z;M*SAb%AR+UB)=O0#kx9V^eoM=$c(Vep*!Bjh_3!}Il@Ra7Nd@2NxQO`TDe|s zDd*$5_dp!At6A!~6rnz0l044^0lUMGEn3YM4h1g%xrM6ifUI1iU~6?0m)Q<|;=Pr^ ztX0K!FZX=CHrKYup53Uq$p~%N1j$nx(G7Fh&F;R1A3b5Yapp@Rj|$KR%@a9L9u

    s4DCN|}U&z9?et2iVbfIk(l22A2Pr%beg9XVeB>f&Kc^ZQ671Pqr`jSntbQi=*Ef zfQK~9YD96KU^D5lh+VR&mc;WQA~tDO=(U+x2kZp8$9?7j45h^fJ(JdvN-BjcNR{y` ztJ^%6?{n+p#aM@f>d`|2n>mGY!Dkzh$tq-uzFua$hl9$gSSneID-OQ(X|5Xq#hgI- zHfYff?w7kAk^R>XA_J-HGRaZ>E*o89&N?q*w*5n{PBf3}$lRpF~6aok0%*f)yb7x|JKP4TU_-|rN>8XwefNc3ht_$7O)P`-4N zm%3s4#pT>+fWol%bLT2H$nqP3*Qu}3{4LqpLwU;$rLmIR5#(BlO7WY!i=8+=&zpgd zuL6v=LYbV9Y7ucKwKIcRP2VMK4M0@5mxa8><#~r7Dw(YRRT34K+2QN7cSHN*UEfLP zs(XtIq4D=~N~G-!zRuPom>2n5V$JpsK{kdjep#0{s?AR3go~G7-(Sz<%TN!baYkL9 z2q@rjno||N@EEA3@L4wFr5v}j**;gVd*CpGNU{iinvSI!B*^4cG{A2U?QLHja7rc& zCR|gvYGpS{4G9A+^R>*FB{L;?5>UpAj z6tT6O*su+0Z&(~N3l5`rs+PF)w{T-!j11+tOHp!t{Z)-mK(NCP(RC4VVA>fyHP0Y% zUh#Qc#!|{8Y0CCR;7j<-H)soh`7W14SgkaVECz>EsS!7zl=VUQuM8&8mrAf99E-%X zF%o@CV$@|c?2EuArG|P6CHot*T6XdO(((v0x{hJzB0-6`(Q9cYHx*RMF!D zUG1;Td8{)hgkv*N<*GPXf@mHlU5*#a(Fmgw2zgzb*buU_We?=@)XMP~mxezHynsl( z@2?#tGe294g+I#o%Hza4bSA0W_*!WXxM{b&>+ZbX>Km;d_qaJ@f(xFjZ=a2hLXcm6C}GX< zjfw$Y-B~h{cKsMHAY|$OH9q7EuS<8ghr~=nZv@_=dZ`_S`l@7}-pNWc(l&@;opj}n zN5Z$`;Tj)i!)!F&oyC6AsNqF~-9ja41%5{5RvSsUP1FPzq)%bPFM830Uj| z1$5bJ(HdNfU%p$;RcfpH3V5i5r_I6_;(GPTW^jn;sXFteG&~7%Eml+*KMGv!U8G!t zgexkev^j33)0eLHrt;$^^Q8x`Ph9(f@MwE-TQHlr54M_MwWv|;$RwFW6zKv*pY*s) z=dKPbdDvAcVt?jnhTvz$Le6WQofg-VgoafQWn%maF#_ryJB_``e1b*TQSgfJ5M*MQ zLYkq!McCgE#7LHb)m#|njvD$B8RvOvx*Ouc1M`Q$VZUt$cPNs^TCN1=5dB`Zhqeu8 zD2%Oo*$*BoUuUCE6BfwZFmaF3d@31M^svob98}X6IcvlQ>tP;JV*( z8u!gh??~EY-ktiI;~jo%yZbba1e993Y?CgiU{8zb=V!>un{A58&WCJQ4Qx7S8=cb+aZ3O zEnix19XMbC6&*sGoFdv&<7E>>;=d!cgPOC;a8e@WX3+ZTK#!MANPsQ=Qd%iKRJ$`` z?;0VS3HmO^CXA2LLqc`+SjLIP;Mljch|@Ei!s<)^>J}nN2?2X_E>qqFBCy_O#na=F z*k41T;&G|Q`vFP$dn%(wmE@{>VY9>pEY!|$M&W@-KVo@P)1mNEyhvN{>omzb_=L+H zz(N)+k=7`otc6*{wX7!dVggL_+A>7~EM9>-Jh)hYO&VkBAhKUAOWN?pp7v3zgdgsJ ze&w}L19n2*@Y+M;Zbn`I;6x&{9_7>FYQ~7RS6TPzss9&~2*Fpdb2$c+Rf_1P+xs)C z6BoAvEMx{v4#s6iqpU9C?-7`aq%r9SM>BYxce7A+&R%Tws9LW~6n9Mg>#RU!{;J7A ze|J_;q^CLv67Myn+wy~9KCM90BV%rDfAoI7LwTSgw7Bqr=-W6tf;v47gF%xI4mqhm z;?X$rq%+kQP;<{CaU$d5kaj578g%XQ-`L;JFKA1JP3yP-_{#P60OWSq9(vJ+8>K64Gac-y&P&yox^$ z*`L&aLsGW|$TO->RyQAeBi0&W^D^PPFQCC=In&tOOBzO49DiCJWG`EGl5ngx+Kcd; z&ChS+C9@dkw4QnRlyZPN#XsX1GkB(U-rZGcIvzZJA#j*-QggTPH!OR@=mSteo#xl;N3?_NFf9R3rza%S)9efXH%UB-{nb<~6*R4hTTiP6m>t}}@iEG`EE0JD|zFgRvX ztwMt?#D||Gj;i?+F1;EgKjgwekP*>_T$UGFGF6X#R%FbwkGwH@NG z1r&DNa^cOF^V?X4Jy>-+_`j7Nktd*d%*@em5nkDua}w~*Zca5hO0>a|g|V`N9>=mc zft8Yc4iUt07#!Wbo?cpaFuPE?cSk9MRieY}3ondMuU@fR+oUZO!%4FK>vLshM@PZ-{mE)oCxb#^4EW;boJE z5ZbI`mgM;}{!PBB&rsq?nLBGhRf(ZQRR4wu4CI~jVekpE8(r}9SEIaVkk6yli}vxg zr;hvaQ%<|d6g+j_5X;({(=6N+UOlNPwn!;k7 zs`?s0DPt{b(QGqgYRPEu*)1BeFcpyQSfY6$iD+E{)KTi8jA4}zrdpEZaGtW@1G=N< z`I>z#wg-!}t_TSitKEkh)RalRwQW_scmlk5-0b|l7wf{OOLXVNa5`Z{Gl{6OI5wSY z6eGO0yfUF*?=#8baZX-2Y>8N$5Zf;cAX00ZEo6i>EP2unk?mm0Yk7ex! z5R)8%wbE0X&$~v5aIF~^s-5)xqln0jGD&Z$9H=(D-Xsf1?1-~$uR}Qt!fCsl;MXEQ zTn=LP+3|1f`MHJK$x%cS@oYI}=-_ro81`S?THN2BkAn#W?YRpk)fu@ge}{4xx?KJJ1@74w*rg1#v;_ROffFub_!FG`7bMa>d3ddQK&*y))zl*6KJMnu$MY~JK7pEne@t%yPKh} z*5W!XVC_Bek)VXDz!bYQ*@T^818(PEufD}0MY?l!DV>NX0j2L&Tv3d$4Q z3T=RW)n5P7- zo!XnCK#>X?Vo1BK;Y>gpugugo@VPsDk-!?{d?`wKeoth=bYLwIMwpwv7p;>z3F1J)^0n@q+LI6F!MeEhd66|(Ra=! z#I2`o{5bsTZ_xw^2rjV24lCi2xJwDwC)Kg9v5R7`|iiYI`rhY)W%RteB=ni!o?II$&PG2C8hvt@R2hh2Sn>W7a zTLVXRJi;hDqiL1mHunQ!BxhR8$@rwg3r7Ix;Vf+Pz~g#>a_3(I#NT^8Yu64aQPI=` zIKVW0^!tbvZ(p5ltLAqzHrAQ(VA&RICPb&YrguaDDyLytZ}b_EK)UlvYgJ6SY~ zql$pCO>FZ{{|+_v3C`f{(6u?kl^_m5_))LptE=-Rw5t!e$eAbt{)Vu1v}&z+MDUU;_v1ZLi-iHeDOAzjXC9W-d$U19(}(Afl8L$RoyMfh5R8ItpiP9x>aw+uYXZgDU((|{!o0^^j_{OpQ<(pP&Ifr+` z*0u;z4H8jA2Kw7B&n0PIG&F8A=8|r1Z%G2lX#zCHvjZ&st*1j=6|M;u5<-T2vfQR2 zXR!5c=X5*c!fz#Cb_a2@a$JpOA*8q_6=N^ccI~{yOQ6VqQ_3Vx`{6D!Xtv|34C3CyK8;m9|@s~!_uQhQl9SDTe6%*M?ABbiSQ0<+1S6}9rLn(|3 zWRd0QRJMy>7f8#-ntbNuTVxX7R5r`P9_!4mID7`mCt{Os4$qUXn{L*#1Cw@QTH5V& zd(b`>q6X%wNNvgt<1kH+nfLf_u1ft@GSQ;}XBy66L?aL(I5(0>WQ@C6t2~LVns0gp zvV}U%&wQ7g6S)$MU2^-=#Tli|y1&4|q^nazTFsn5%tUshU2Mjq78Q?CwLG~^w?`Cm zJEZ8MWt?-!g*YEWz8*9jNUE*4ns8h+rHank?ihaVUM++u*8sQ!;`;g!8 zox%Y5dQ?T|QJ&LgTEX{Jz|iLU98@=V+qe<1J+v@Z40M3r$6Y$X7FqZ zJE1{}KnjI35iN}{XeRnH%UB-~9Eb09SXImv?1s6y5=DHng*5r^A=4{g%G0jpqwBq; zu?+&FkyNoLo!}4?!z`BrT71qjXB4Vo4%Q&BUpcOMsrVa~db^De*c0o%LAn=h$>-O_ zj>2025){g}z(Mr>$ouP@cjgZBd5WXIAtKybg*ATZ(r91gYugUp)kVDuE8Naq^mBmB4_tA#}~OJ=EvD6Hz{vMa&FtE#si8Z>>jGBQrV!P_c$q5m&x`>SJlx>`b0_2otm#Y z_iM*IB;R1-@qFl^DTRN2;c+_5kw{`C^2Qk% zTKS@%BLeY=xGcLMc~iNpn6w6)!3ccL?W@PTt5D1JNO|Y)w8}rN)QI02I@oiUA#Mme zZM$E7LVInq+D<)ZyQ1nIWmAlm*-uXSX8bWq(C8`yN`8<=k?cT6d;q-^_5MfCX$QnM zYRvC-#*gT~(j^k{M~og34?Xrnr!k_HvI?LkIq==kRPd#JKbtcs{*CG^T?hfw1@@a$ zF6kIrSmZAP&yUK~GfzkTCrCenX_c!#K(QV;r*heU2sr{Ij_$^}HJBh1QZ$mvr_RKS z;qeMLqwH3~IQMxjq(F&QU! z;VVsws$*qh<&r@0_*|~rpjT2t+7a^>HK$7eNG`7Ftkry_pslsw99&h2h=tqn zXZ$CNue`7ZC=#D%iZr$FFOMnv6`k&faZ_?DU`(0Ho1HDve%KVEB-dLn9h2C&>@CVt zrbbp&ZUE3=vn*8xt)Y0skpZRKFLcFy$BRT!CGrat0#;Kft!+`O{wW?pM2e1%^^zCa z!K}4iKRpyF<{VUOz02vR#S8; z+kQ?$67}mlbqCyi+LlScmLY^8?MdtR&#q#Atgj{$d!|MaLlHOQbn~3&<0o0446r7s-pvCdelLD2!2$m!j2i@Yy_y1PDxJmi3YFLQ8yPQ6l36)3&F*WYH*&lP7+a%Sp+r-;ZDu$8N7=5gr3 zwz=3tBmyZ|%pqYYrQ;tgH|56DWAC#6Y+b<~0?;M9M)CBeK)WCm5nE{cK4pQM6M^@t zVomun*qTGDCg-$c&Be;p?(`q5EoVyd1xer{F@cgH7E|gDyHTzCg^6C;{BdP4p+P(R)T7Ehv)>;iU9GG+}3QuD`1`M8QelRop zGx}0M0iX?YljCpo-d_X+ze31NnGIM{uBxFVrlHdf@+=qj_mg>2C{Aw2=DOS9|m3S%M|+Oq9KOc`MzEd`z|YO%M2bU5bB9J?u^vV z)+riTZ}vvI(3-*vqKN#sBMpEl2C*s*Tkw_N*y!?=q*BLV5otu=`H-yHEYQGq=Kjrt z{^3m_qXhH9lPdV#PuKnARMJV?ZbC-Z+3&m(O0;kI7OEYSW@tmPP*~n`*=+InpT>$u zkYTZ!yeri)Gl{;vJ?VIfS!%F7KuPDa2j|Nco?5+7gIU#qwQY7eO6`e6*H+4ZSBwsV zDa?{Y^Lcsp<~vVtbl-7QxyqnpF^}Cry-Ys{F~SYYkI91bCl(LwX3K+SO1an>+Y_DX z&x!|WvaqKndcK~0E0SYD(IuBFTDJa4Z1p|}Wz=zJwAe>S;fK6Z1_h6WiUjlb>9rQu ztWjK}MCO$y!%`!!REWK1eWxe6H|t5B`Tk4Mv{ zTttcl%EAiO+?97dp{OERruyM^oqI5Uhui^CeH1`}y;H6gAh?<`{HPwrM?F?cgcnF1 ziu!B;3pX6QJ@cM;b)VhRnW$Lc1+m{e@NM?%gE`n9I75j@0(+F}Dt=|EF{upMwsEl5hl>d4my$w7hIcK6VvkX=!zzW&O6$|+j|akTbSDTO1bHALKl{)5Ut ziw#jAHrR2{#)Rap?fL!&90J9G)qulg zvRGsi4T(1pZ~ixK!GG9AsPf;uKO}~pV0*5^K)(I_m>0IW-FD+obWcF4TWcztH0Jdd zvX%JDCh6ZQ8Go+l-yDU$o$y7L0QIE1^PSrFJ01*$>|-fa)E_}go>V2ZOes867qk;GyXfMZ0c_cb>p>$vj58~{Sh^uT7qLlKHguRum5Aw`|B&o zB_s_&P$r?}pCqGyj`pKI`WL&cxxyqEiT|(H3vSR{9%{t>b5Z^C`H!h!LF66$Z6L0H z?;b4QD|m@))4tUIj^}R&>vKTw?ymN>JO8&M_|;XxT{s0Ucs|})iob=(|9w{nmEcBy zr92aI!i$ftg@Ix&Jp+ABvYK z(eTp0S7nuH9rvg0|F8cch1Mg|_xx!#+#QqQCB`P1$8>*U{bz*#(2CzF31F*^{sdTLK;T^gEH}UNl>Vy@L2xD#Kw(VlQie(Yt`QOtEMPCl zg8+N&UwhE{5wN$mkJJ&mRR8GupI?Ci0Ok&&aU}kCgE$38`(k$qp2_C&I|t{te7GwB zP}%$V!8Idb`Lf3bS3_ciW4c68Xy(moI6d?mC2e|Al?FTpLJ$~E~_QrcXX z%sKRcS3T6}Y;uMgnFtgO@D8PdFU`{%9Dg$cFS+&U?G}f-S&^)UhZ@dg3OSX5Xo>=e z;L!I+6~xzaLKOE8%c{Sym7YjUT{0nO4><1zAIR;TbS0!rQ2$3noDFR6ONevzw4DJPx5mzEhvHn?|T^GW)oJ#J6~?fKTQ2p4vY!xG$ZoF#jDSg z#SKr9?Oc@FYBqqgQ6OBA%LB`!Oy)mM3*`eitbA?|;VWMK2e0{~3GboR>xfe}H@u0z z)_`!Z_N6nzlL3@WaRguRvB8Wk#oH{_Go`&OYt`u&tf_@ zji(IEnayjT-)D-0j|s1zZT2|;g@^=AD;alK1hYdR9k>k2~ld{F}bAryk^S7b0-9aRq9A5WfFg`4na;Vg! z$!dWGC^L=$4H%_TDT-C0Spg2ElZFo}761;D=n5PiPtJ6Q_@agybv#|S0Ey$c@q=S+ z*HknO|KYlA$5R2Ca|k82XWL*Vi}&%- zC>9WZO0f%795k7I-d9=XKR%F?q+d zH&u9H?mGUQEz;3nT4|E#b_QO->3RS1L+YqoB2TwYP6Q+xm_k1)H(oI~B7Rkf0nBoMUxk$|?^J0CUxl8Y(Wt(00 zNNANPTb-z3v1CG7i|KlHZ;Z2monEB*C4m&1!Rb11{0%>y1X>tL|}8 z(kHWSJ)}dQhod5$CI_+epCb%_Rk&6Vm29s?5hWci)C4vj15_6TB7k({{kB?)i*)-$s>BcaYM951jfi8ar+F=TAFbOARMQ4^PN>T6J{(6L>2r4VE)g)s2=g zOhxvx8;v85gXZ5)Ccn$5oi*;iupUgAYFXw-nIJuRN$R2t3_UhoIE9OJ+zP`<>Kc7N z$7E54lMIWaxNfF?lqV4I(K)8ernRiMVw`&lL#N(qy+;}`-?geUp*Yn*kn=EKJ=>JT~X(k)VrHC-+Z3{NM46}VJeql}wK3}Q42W#wIst^~g&O1V^$^3kW4_7D?w>4br#|j(v?F@qHwFVN|d<|b; zzHb{D(U53QFzp&aA}33JXsGUi_iQE>i5Z4F9TILQELm)IjP_`MjY-q+her9cw+s1} z@^=?wap#Mc3&tm_#Q82qc$4)bef)a}L{<-%gn44Aw936h2X`06-c1!3Us>}c&F&3k zD9wzc>0fkXAf*FS-ulFgMiJ=(=qoL8gGJ$S!-xHSx3^hr_GLW)L$XlqRK@loQkSk= zRGn7uQ1)(nj$EnE!r1&tN+yZ%%kp|>xA9geX{;<9-Oo;+se+Mnp>Aa3=hl?iHz}Jk zAK1?CcJ%w(iGAYBH1!iQ`5p!@&D}cFH48n=mMUs>RFL;kGQJt~UP_%0(nsEmzZ7p; zE!0F%NT*8z+e6BRM$(xzS1b#$(koq#TFkJDXU;$RiC{!cG|kB*1z}N~>9tH^fo<1K`L`mLC+JXQ3IAB8iCN-G5gYh-EKjJl_svGQOHCr7$j0 zaX3EI+&j~h2P%%q#m~#~q&Qc_7$o!f8$VlI-1DZBMLLfk8hI+EdnuTqv?A`3(&`V= zy}qs|&vE=@8)j5>q>dqeZYK48x;fl4E7_JJGqf;GT<{zls^c9?ntIm=FJYH&IeAIW zfU=I*s+?c*)po9T!^r7iRvFmrpx$P)(RFv3w<%NdM>U-2x2B-06QOB2*oqT+NP z8^Bd(JmgyAymYhZNvmC7rnKc7lrI(@b%{*5m?q$x{YLAn1}C4zH0&=zy;rS!xKE2> zo}|IuS4-@JA^f1z&1UB%j)i9DG?Gh1={K!8i(j96Hu2*`LXb84028OeW%f#5C4Qz* z6EL~0@gr1$uK_3yi#JTu0#A6`=CwZER8o01&=zwQlvn#R00Y-$@DVE9Xsw-iI?7qi zk3?PY%zq)OO+HJn=IG(5#%2XN43GU&Jd?g$Du;Pq5U=ihH?h~oFAC^=_CmhfVa(h# z_T3A{CNjU<+D3U~$bPDI$hCF(2&|0N_?MV#(7oQ3=~RL3Ws}$|SUX@}P5jY*GDoQ+ z2Box$vty#jRc-awP?CBB^X>&{%{&$ox5XunX7z{sW;JM;Q37uNnc$feh0%2!6q^Re z9UE?!!(O0F>;#~C+Z6h~f1}Rk3EJBm%WcznZ?&!T@_0qQ>TUs&t#2xo=nLjo~xO* zK2lnhqL0m{nz_d}XoK_2-OlWxEc(7}S}RdA%1fZRG#=*yL|l*{P+Ow>wGV7i)xx3@ z`QmL#&7^AMKM4Mj*qLUQi zDWC1)sWxrX6`K=&h)ZvWT4em8hBfOcZ|$o0iOnYwbTWw*(e|6v7SKRBK2EiCPHIQ) z=uOPfzQ$V<5}b0KIz3-uIwDfl?r%aO#?$@TL^s}hlg8fVLV@A9tOfi?dG~HdbzrCv zx;;blqHyT@7Z%MR_d!I4)R_|1m9j~2Jki{r9e+U4_$u@KeNXgQ^36}Xjp)o~68 zW`rHXl|{^Z?%g!o`u@+jwRLtFix)qScxsQdu-@p9(JJRDfEy!F)|9J^!D&+fY?M!! zfxSNat(@cF1+}5Ods<>knUUXDoIg63s#iLn_4w_mhFq6#M{u4mwa_0g+R>m4LxqZm z%&?x^%i<@P<$)rtx)d{8-Q#c3*PYocwj?XNCK~qd_JHkcHFg7JNA{r#4V4}DT0EsY zE4dvn6=J!h-NBNpUUx>LW!x+lJ(3A}`2gpOtogI$dVF*5FK(J>L3}6ZQxyYrR1TUZ znq-G_Az|yM`-%sdlg7M7EFE6Q?aaJ`I(4O;#yp-n9yiOi_D7(TSX?Txxlv1^J}1t9jh|nf^3UpT~oOM~7=QJ7m&#LqTbFkT>5) zb@?#^%{ob%KNInFZA+YUI-ea2oW?P35~Kz{va6QPRP`hS+Am_dWoJ1XvQO*>Q++!6 zqzb8D(K|}Fjp0A0zTao})U2un;&DArugd~<0-}au_O6%Q1b9qU-{FVSDnfa`Y{hYW z7&{9f_KMfcIcF}{e5&)H|FNI?iCEgu%<=oD$~@@=X^$6%R9}kQc?LFbfF{g(KAa%sR7# zfgU)2v|8J@FR*%vi_dCOMlu$^f<(umF_-&LIH+Bo;ElfXE5TdOr+E8*Pw&oSEi0B$ z6U6O(7W`&cL9wPlF0UkSMrLX7F3(`#TgaPlsdq=He&ZJ-V}*5QDs>7b zce_KjMh{<_wZ1KQUzQ1kxv{ecdWv7}YNBS0@ip)K#saPW`=lR~d- zw{t&$30~;U;>(Wv3Pu4cDN)4Z^;aZC);?CH(PYi)F>)gYHfW)JO(^QX=jAp}`3|?h z8${uT_GgQ*nT+BolR+g)ITA$J4J)7+HNK$;IHMfV=x{CAZ$px>FaZ4T+~k|eX8#!} zyc$8`cvh3wDYaO=G7=3O{8)lCur~rj;v2z^Cjvpq(9 znsfGRH`;fi;}e6s8HE0`P3k@OKt9rCl$$3LVh<76=Rh@(#jTL{Rjgf7*`r0>beW>+ zRi}NSa{Mi;?K9cakIOlI-zk6OE+he)8NGscM3vozB(v-`05MDosSC9SD^M+hfPK{B za(80$K6NO+>-PQoa7&Z})b^dx^?Ycdu|Q_?XX(!$gRWcMTSgDPKhDF6Yd82*ijjwU zqP)Up)S=_^a8hMzml@j7_i;Yszi=w|;KkZG0Gk^J0@?&+A!>jlThOMxICa`OxKiga z!bACNQVW_(U$QI0@CTfpNC(c(dhze)eyaw+&Nf%=-m)_76Wvaj-~&h^9r5|qN~M01 zL2~)bk1sJ~wpf7hu=?$<&_L?P-z;B^g=g?ODd~m7ZKvM*K4eKVP1Y?HnqZ@H9OVY) zN|txH&UdUpz~O%m-BRcu>bk?fF&^*>kL8pl7|0~6C+KjKkh@}{fcpULW&5r?OZ8JE z7tP9G;fydEo8Zm z#A0%n3=|Wp-1E!9@)Sp=p5=N^tEC^YqN+YrTQsZ=ua#M&vZLEnR#JI!O9_Z>3ZA_O zS^i|hX8Qbvgd`p=i)xUZD&(Au`P#meW518=n>DUfVuTrF6xQL+>+QQ&0rT&%HQ#x| zQs&Df#lvF`rl}4ZZZ)32g>Qt7DO(B^KaEXg^$c#+=3IwHd%DwVx^Aj1)2uc38@39V zkEM6z-@cocp7VoAToImfC6Hg?sbd(!{|Yk!OC%S8&+c?OzgT6T>+R}6qz?`-GZbu- zR%`2pP|^T;cHJR}wOXCm=0AT6K+F9CX9AWxOg!?j>xW-6z}j`GKr1tLTg3r#au~jc zTBW%Nvp~m4FO4=&Uz1P8oOZ02UNYOV!3u(gr{}n=CV37cqeZJZkd?2MIoYNPWYaWr zP+Qk~BXRSaau{@YPec7Tg1SMRiKe+2M{4j zI&P1gx&A1VG2y?0rALW?p7B*ybMltB?@(8%SZiWJo`!fWn!D`oT2FmY36NqyUlR zrMZ<7>0BjlEcHMTNj2JwN2NEmfhVfRm!!bEM_X02zh-< zj!$MaI!4@z>wwKkn(bEESRfuJGlty0&kd=)-3O^NLo)~sdpwuMo&MivIam!Hij$4s zt(4c=HjH(5uf*%$&Cq46EW#Xg7tw*c&(6joD*BHPgpZ%VFBH`GCx zZj2#~lYwabp{;yjir!OH!>uRiS2wGFLPt}`I;JR*HFWZ&0MA&uJNUH|n^?fT-_0*z z$7RV?ey*g!(7qjMJprK2uoz3HGVakr;sAWMP!cW*VvYxm#FC`Nw>un{gDB5_Au%iK z2GfFktc|tMCoTFXIxdfmXG-P3Uuq$mQBkCOOHtm(szy?q6>yM7|eLhYI!?rR@e@@h?1%ySN#_OGfV!kiow z+0MV)+{0(7MD6)P6RYM+(};B3=DV zTP2ciVv%*jP-lm&^y`F+UH2-mg*OBq;C@{=Jy;-`LM8in(z@*-EE!Zvwf8E)7Bd!F zdLni)J@;~iyprUU$t0`jLp;;K{?$@~4+Y_voX-L~$j|1q`VuPXa z49emhHX8Jyxngc8A4G^jZbcHlB29$*#Bg^W=?o?J+3CEdiSK}|P*n0MC7he{8wUQ! zJz%h}3IwwEcp=eYY3;^F68Vy5f`Sl!?l)*_I(fO-whT-Av#x*Q=!8A`#$q~J z=CjVZOYF7``n9#XZv`M3DcFX%;;A^MCrz1-7I7lK;?#v&brabKJqh<9o7gRWT(UJo zoC`XOl}Tj_`;-%v{srsd|g9t^g-hPv6ue1vs`2}s=KIG{mU!ZL33?0CKyzewW zu6pR(5AMae|3B=#WmFx@)&-g%2?P?{-QC??HWJ)}ySux)TX1*x;2s=;2M^B1T{iZ1 z&b^0;Xlu}_)bN^P(UzFD(pe6Z)K z25qRh6J~K@oEyOFyKP!ei8xr{HeA5>&Dr!`pnrgIj;t9ZAzc*53lle4U)M3)N1|3a zTjS=IBe|TX)$CB-7xH(zJl}*wV<)hSb=P(mBH(|jaHdP4RFLt^Gwj|SO;*r39urv! zCOqnGH=8THIP7vtzb%#s#$?JLvtE&w88^&YTb*ZFp^#H}&`xr_NgCN6r?$jaft6-l z?f%m4A`&{%yAAsgs-xh}XTNdy`A3iU1m5n0A-ujNaw2_RxztzTVRHylvDyo*^EE(DtPw2Yz@>)ZfE9q`fbGs~ z1qY}{KJqT; zZ>o&YG0B%uTaV+gZkAgAVNqeZ8ZVM%! zd2KHe>o1+BL)C95DPe@h()+GlzrWozzkYmJ0U0YKa85TKTuV%bzBANMb^lh$& zvsD@ zuRWuAlLBJSuDK-WC6w$TeD~xGYK$n*p*X)31gmTvBi!HKd<~_s&Ed}}lW&RLr9qSa za7Mn}3FW`D=J9c7g)Sp0(!5#z25$(yKLf`NODH$+9wOCD{q&2!0kXK6UGr~Fe#LJ# zc={`_#LrI1a&Q)_L*H*79sYUh$>Nt*UWr)a{>QGKyqC~{I1X6rKABTuTxsEC61`v# z5>}~TQ#@|3pVWrSpDfJObQ|?=cWpkzk{g1dsAeBpL?6>h*uI5x_C2*-amz|D^)H2n z^~+!u9XO|T+3@2cPS8Sn)8Qd8;>fyBd&jMEACADkKr0r1s5;0~6dyJB2pNZC-(-nT zyza=~VG*A~%g(DIDd>k0d@~b~Dn6M_7pcbkAzmWabQ0I_;yqNJW@wOEmW%4xizyiJ z!)ME24xTKm^~kf-pv{XXYQZ||-{pK}LSV=bF=UryL;Ik1R%Mkyqphe>e}&23`tvK> zd!w;DmSw{-p}OPCc&ShLNbhV()J4gMzrR!!{vGJAH!_FO$naLC$3^%shf@~@mWa(?0}^cfVGsa2(jM~)-F#} z@tA|-_Xx9lwaktdP9WZXe%J6e7LU@xb7JF2t7r@o5`VoJNA=T z@RrIpLu=HsRC#0zTZw!!=sn5hI+w5A*1ud-C{r!sGmc*I--N)pMhNe6j6!h5>_t zm|>UGA<;=13Qorq(NY>eLd_OAH|lHnq^=)fysfZP#@x|JXhkI2U>M4-TK6NHgZ0-5 zHw({j0$GDcp08BSM%VK@pfW0qN?^%Phh%jxFrun5yeX-)s(%R7ym6vEzh_?TU3G_> zcU1ZEpctIe$4LEa0H9PfKSTh;XoU%6bx&qo#s)#^!!3R@@6Tf)QfJIFoK!(3x!3L&rc86!4LNiCWZC9Ow4^E;w~_t@S+igC$t` zDdNt$z>z>m)c*kTj>i`L(cHu*DZJe0DeI#S?OBJCHk_1On1ld0<@nc^abv93AbEU( z>n4qcV(!(0{lLfhSYxJ&Coq&af0YOHEG(ffRy8Ebtb6^rBE}1YX7|y#S*r!o)}i*J zj^Qc)y*@(ObFP6^G#WkXuQJD-VI(T~+Ay!Oj^?;-*gi$D*v2&%qj6w5DF37F<2Up6 zi^ziNkL`o81XG$FMRQ&STZUvdl+o^&xSGvQ@gkT+qa?g_V96Ur?OkODig)Sk|4O{@ zC5&VI3_p^^UnaBeB*{*|P4}3@8I&nf`-GJ^Vw`N%w)r!f+IGy)z1!_mG7DuLy7>%6 zG)4IK_aqY27bVydl=2ffqeG7FQY57>zAML>IpxbMiTjJ_kR%Gg97XL<*OV|E;Y?gnW4Y?E8LX^4%#J-+Qb+BX zf5RCKNo8Zm(IRi5tq}McE7=g6|E^ckH?t!);E*6F-}hZ-nLrnEbgdlx)LNON>rF1^ zFYDXLOIq=R_u}V$!_sb;L!C5zjfkK+%SHU7BBUUUDC*$Q+u+EQ((@EAA-s^G6U5rM zoS~)9nY=~8Df%Ms4<9KWJMk{z^AHe~&U<+mdOPO(OU=MR{TY&4eN-dQ43pPZ$8K^8 zxfincKHQXn0%s*x*n^4?g8>gL^SNY8H}bz+Hhbr2%7O?;dq15oc$7N4RL;QGY&r*c zdPjxbx&%9n_E$8o*DP`9ke)3y$Z-Uovqbd-J3`pogkm=iz?l5f?iMoi7Jy|aE*evn z`bb9Ed!B$0I4CF&QzgV25G>9r%=d-n8RS#^&PyXi%z9WPNMXv3j;NQbS>K6?H@2s| z%#JUN>ReCcK+TR8OsN6|vpHPlPOw=n92r**U5Jd^-x;{vpHE9+wo*Mm-pnvaeS8Hw z1>426XHIIh>e9rr?N^v=lY3oItvLx+g+s`5nP|LCj?3KQ*UnFSo!S*(^wWy2sf?4!*^d^2*$V`|#NDMB=f0(kQzxVGZ-KmaooUmvDP?oCrt^2hm&RkH6Ex;Y z2iWJAgN;oxyBS&B?hzeD#IEIwrPXS>E$-fF4x3zJlA7`%xRSk8rnV*95h$9J>0cQ9 z-tzj#RbuCv=~f`tr`Q%HP`3u`6WU9Kivv-H=3uLlT;Aou=!j*FbrAc~FchLuySr?+ zO3dG-4OfitdNM6NVklf~?%B^3IZ`|aa=IK-(!9M4f<-A3esEYY(-ji(Gdq8rF7QaA zOdrP@Z266BwFb+ykrH+ z2wV+^wT)aqvK?)TK{9>nLoW8bJ61k)@>_)Sk(7T=oYvk}{Lxiu=L(#;(cJ`ePR2xP zmWtNXI|j3rN)>pp4>?H0HfT?`+>J){EqsO<36|OAIXF`|m~DXk|= ze6VP0Yi-8*^pp43bFk_QVVG2H!e~Y5)$xg@o>0Is(OGv99}GDw10CP~SyBX~ss&OR z0Jx}}Z+^yMQkR^n&`$l(G#nH^iTJMNw1=uQC-+5EK?lxIfR>7Hj$fh0>~@ZusRd-7 zWJRsvTK2`fX!=`p8lhe z8+?T9+7=b{`F3Z^G%z&tIgMMyf%5FeH96Oy1&~;5-dp6H5wmc(N5D4!;5V8?RU~{6 zE4k#5eA5h~HiiG~GmfSkYUr;%&-ZwHmpPn$T?PCM!$|agy#A5>84To?I)-oE6TP4^ zyrg{ItHiMfLB^)OJbMDhQmmadNQX|BivsDJ#CH>fEIxSIbuf}TWb<3+2fvm7trY(}{DIHw5iJ}SDWLDTkczic; zt_NJJXva43sXhIGTy6M}xNvgg3}p`?oLhHEJ#L>Qq-Lp#8SnqTFJzUx;wYit>6z6P!gjk*`Yl?i}JU=>cJP34Po6&4HSRB6nn%o!wB z5z}*BmL-=ZWtrkGEU@)iiz{QCQyGg==ctD*FAP{;*a{jMlEu>}1jyYLhmzyjSjH%C z*rJipWeZb*q|tl?ela+c%TSW~*-U()?>7`}+kF2)9lK516(Ctf+HNCZG1 z2=RZ5k+se*EY=N1{ACI#Jl|9b_1XzuMvXj^&U}3Je(}eXE`#GOEg?|u~dIJv<`gFU9QEpOBeDbg5t&z zhK>(H^C{K~uC z4XQf~Z73i0uN_A&+!~BLQjDefozK~`0Ye&tq4b@NrU*rbeJ(-w4M2%2>i9-bPzrz*8wMT z{JYS>Myr^z6RkPAtkmQj%3C(q(8P&#oQH0Eja7s#X9CKth!8dtT}QfFHdJyXj~8Dr zH<`p7tbV?rSco>sP!Ss`vpHrC1B~PXqV1aEsew&MNiwZrU$@?NfQ3J2y1aV?>B!w1 zfYKUlMtuDDokP#@Yj!n|XUHAL^`3q!V_(5?E_-PjmKM6MTLV;~ahyuS^o(DfO(Gf* z&OyIZemY09H@<6i68QeqPe13T14qg|5VNQo_Q%zL_zOo!J5fXL`aF2kU>`(21gcY; zH`amKkL7RmA@DUPxWQw9Uy$Bi?knU8o8eulggeYfS#00j&gQI^R!fF&M{%et+6S8Q zH!IB#KZ_*i%aidg(KfNEm5K{J-nE`M;0X=DOmf-Zo{yHFJ~KfOW?LTL9h)Ypp(|4U zT5iUHLbZK_op8T$$y&OLwsamp*aWS+jV1v>OPgSR^>SjRb%rW_Fp2H+P4h&T?c-A) zbi+xcCL*17TOoSP>h7@vCGIFR$tR+HB>vh5$NjSDg)W$s7Nk*Bn5>g_ZOJZb~m}AcmK46KUaFzSCwU;grIYp-px^M%Dd9DbX zb+~IYFR|$S6v15;;TK{UiCWl=*9w8+Xa@FIx_e#(bURn%Nj_k8$mvS0ej>K{W+nQY zI1aO4em=d2(D>t7ouP38FicB;1k!2U;(PmbaNbOz^cfD?^#sR;GVDG(kl-1NV0TIf z9NGaD$)Kx~E$3s&;0@A{#$~j_-mxXM6Zllxi)_rvG~kHFnGkrJ>FzRa zRH(^u<$ejsyDX@7e7@NJ&iV9)OSzd&@0!C@B&G~FORmKs4+ASoY_DFm{ML3i^XTg? zSc)TE<75IlmVPJp0{}-A(#8ag46PZ{iQ5F)u^NJu%gPg)`i~2W^|A82Am3jPR>m~F zR+_qEaZ>)Go8gf9)%Skf{`BPC1#I>FuPDQjWFEnIc4LQ%IKD@wjGN5gnah6N$YIsU zGTYmej1l1!@z8xTk`rO#L{+AvJdxri-n<#L#tUg>NYS_qoz~;=>;Y+ja6?-hkIha!$ zRlWyctxrX$Fl;&_DZD}qC2o4r`DF~X;_;+e3tbR*43}qtVaxvsfL2jik}0`(>Nc5d zq~i10WOc(kSZIG7(bo)H*Td}>JGME|%@y$mH zE3kB7p0amIgfqQ-bAS{~_%>ZlAA*Phq9DJb@chn*?kFu=pH`*1zdjV0gg%3BLe7qN z!fv}R^YIz`mui&p_Ie~#Sb_M*7Gz7q75iN}^aOT!3Q}|~#h#mp!joYMW$-krf_st) zlTNpJc=M^nVqQ@<5iI^Y=RG*=Mx#dCP)#xW#%FliB!fNYpaaT;Z`pe89Mb#btMgw* z^xKYVUg zVGN+}>|nwK<{mzgS7R83-;-D<0<@d2!MYc5m+6!#0wH1A)YVcyM!Q-(y-piw$NvLH z0FN1%7oihY(3^npH46sNGUl7aX5 zBEmyZo3o?(nauV?PnjbuNK)RkC9YAw1|z|pv{Ia(c%T%gZenie6>EJ5Ebg+weeosc zcI5*MfoftO@9x5zo1EKU#}-HDu)TVP8C+GD)Q{SKOP%E~^O>^g=_;l4z0o`?Np9tAVKpye$5aYEO49=MmUD zn@zN*Icb}CqLG*oFhIDf);kqAYs*b#1cdGHD?_vNo5@65n9`?ZR(6Xduuf)TB*&2W zq?4z)r?+_M-mWbKk8MBpM*Zzf3EJk@i);3gF44Y#Q!xqazB)d{HEw?4_%>$uRxF0j zHKev-#(4BdmUGHl)0g-{Ie>qMjyWXHYb}jIOy2dx5%1r;30_;MJ`qfh^_aBhe`|C3 z???B*QQ`90A}$di!_dFoX*~RymxXVQkZl|o3h!7NF@3!5S(Dw*P5%cHm7wTvN~XdN ziCy=D?o`bcR302~rtK~&yyx`cxf+}mSn9}Xz0sxATvoVBA-7-D3~#YvH#_vvIO(wBZ1}s((qTNf?u<>n~z9~u%~}fY>31BA{ISR@)YOF;S)YXh5sHUn!;lip{*~ST0_a(B^+LVbJ_oaSHhs2wpEbs>5KX3LsE_`K z1mgD|qAvg9wjUBZr?UB~Y3i}?0+Dsgd}BBk%r6~&`!@Vv0`7I~1}R8O;~O1FK!iT)QeMi7FZD2~*IHK(y(d=;e@e7|-EJQvH(6CV91b%)2v z=h_u6t$#7i!Vn?uZH8PsC@eJxp3N+?M;$wDVR&g=KS*?OAy@_f7g2=pD^vo_CM5t@ zeC$1nT3CcX%{_J`<-+7M<1^DNDeSf+Sg2)p;l%Trmsy_kH*dxtH<})Gv@J zN`o0fyw-alM#4Aizg}q3fm=lta`?QzW%bt*;eoTf(pvw+?Gc3NCAZt$gKVwjcg5#5 znJmy#a-<7ugoc3&42{(WJ85`?gV1R?a2I*>&|AuP{8i~biIkp3$IJ0 zH}P9!w_aFW9`{XXfq6H$;dZ zpa+u>WaF}n{ZE7bH^TM66(L-l)qW~Jqnm?`2QFG|JBAvoDa2bWo>#LHcxf_@*nu7q=-IH z>%(dEb3AZLn+F=)=!YZ*|7lH%w>gMjMC$x2O?qdYM=A`Zl+82|XmAD$iyy*1A_8YT1?nKoGw9!`2l0CaA!oUanKe zmdzYC{p}wv0soI5;&${+oilhE;l_uG1|(+Cpe~S78Hz8!nyVF3E}A#pTsJn^Rroe0 zRF%F+)1(tSl*-SuD4Us#CB#dOhK>C1%KUe&{@Q^eBP@@cH_2lx2_){rGipe?!Ya4h zGpQD?~h z2+@c9ujl>WMF>Wd6BeWo)REEgr5+9jXBmAVtB zgqNr=CgM@%$GhnZv3?MTJra5YCHRkRR9evK-87ET4>b>Q8}|kbfNox2JbZ7xCe7@2 ziR4E4b)*~j$a#kzxfL-~Z%qFrnG?#40#2(E_)(5fE?%tMyT2FaC+h}0Tr>YM%&DtogfG5pj&!C&4 zE^p7-rp2d<0p6c5e>OgGSY@2e11|I^(bCo|XzWf&(jze$&1LqgzQK&E{Yo|KAN@(} z=Dvd~#d2!#V_>P>;>8WO$m&**6nYfsz9Rh@ltJo?82cny`==Lx5K$On-k>x+OvE5Z zXTCGcjPz(=R$?L295|PX_*w$11~M32kz_%NjqnsPU!ewFEt_-9953on6JrCi()CEU z;uUzl#z{)QXbP<~pXCPxKokrn(QJgsYK@#sGN@%IAfK)H#&z9;9upGgMy=D*G#V`? z1RCwH*hi-rMp@YCvR*XnG*1?{lo(PPt2ikE?dD_6#1*@o52pRaD2GXgz#p3=)F-_` zBGl1^E1jkU?!4K=#>$HfLyC&PA?QevnGX1MftkC?W2@y>%UEiqDdWeOH8G#}^>i0; zu{jHVm6cY@_0FLn>PKO>(VSyPD%}P_=Lz?Hk4aY>f46gZq}ny_yUy5Akt|;L z?}LMw)THXgjZt6fHR*cUD{h!sj}uiwfx%ZiDPL{O+SSur%?O%G_w!S3`lMtktj9-3 z3x;7*Z`lq?E-f@0TZUb%!_r~I(@*3^cE#WTTB8P+J@`0i->R@yo5So(%LcG zE@PI5z)RRr;v-F;@rA8)@-M0(>xSa!d3GeBo1vMQEYE}F#f?0c&p6VSuS=3O6KNF# zMadP5OtY^F;^M_p1f}#yfv#t3a$wB;hbz+9-`u@(l%PvYMn19hqIiIQnR+mxdR5zv zv}$QaZf`TWpQ4rsg zAT|z)to2Hx;*q1HBNfpF<9r2%1dq!pz2f&5NZ7ezXSj$MZXIzUAOFjca6_ab{eCy! zrCn`KuN_Ly0jrXX8Ug$xD)sgoV^59wl@W+Ym}0tgj-mpI#Cgare%TA-iR$-ffj6id7*Wv3UK9umA%nODv<>X>C6BlAB@8<*uOP ztXV!sXASu5o-_Ncj^rS~k%GD=Y30m4so7=&kj33dW3P4b@*$)9MW-kx7HnbS7?~V( zk_oy#TKS;0B1b)agW(<@r|Bd|aUP=keQ&-G+^Ot>sMc1ktE zGDDT_KsWWX%A<@lMYsRzDzA+iP4`#Qe3=T>iDU_d{(HB12D;AX*v7L#ftxSr^e=(y z9%qJwb#85f8#n`tCGXE##jKHwW1k*1xZTIk1p(&wKl~#e24;uQfO0(p>ac(J(?T%H zopuBGe9Q8U^)SLLWoGBcgwNEAhiTllB-U=}mz-O}%&}fv#}^b-THMIf~M15C-ln(8jUXF05EytIuq5cYd7H=6@}89y!9c&8B`^Ii|IBuznx+GJ-Clk}@-WqV!Y zd)E_>5exEi8KQfJuo59vytD5fipB$s{am5o;|uJ1I5lkpw$ZqiE+}|x2~5%0w7R>m z($nxg|MYIm42>SK%QvPS2rE(HL0>iLyd>{DA2%$|c7C-|>^N(`A_p0cI3UMjBQXsa z?kxLi=LZEZZg}Jsd$TOuruZJ3Jjchw{&Dk+t;G-(o9zG<_HJ1*s`@_r#ECI)jg9w%B^(}F3Hs#r6;PSZwl_R=T_3K`M+MSDJ&Y^jh8 zjm{X;$RWmv5_(*hrO|SKpV8l2%wBQ7vtCrfjx?Xm@eD40eSAxH%6oaXe{h>olNU{E zbEuKc>%j@Va&|WL7?luPRBN`AM|+!u?0=BGQlqvt3mN+ydd%LwMZUF;g0N8(V`$wS zCU|oOZ20P)zLk-c)o*@Ej>Jn@y3HRUgxaW<9v z1`ZV;4if7U9<4VuShUCIW%SMMI+SfQ(X*#SEEc){8o8kE24Mbuc7jPHzuTv9As)FO z??wcysyAVs4JiR0Ed-HjsNso`n5Tw`@3Oi7=)TUs0uHKe7)|A29=7X31 zf{q2c9#=wW;L(D5KEGmMO%0BE<+bldrm^V;Vk0x40Pf~k7^ZhqWfbPe`fGT-#ze}k zP^QhkcM~WZNBkniXCmegAL)NF-xhB$oup5H*s|KiBY}`~r;8624`(D(Rcyc6^?99T zKt;imMOgKQi+l%tJr4J3d_kdDgkL+-!8|wmFu7u^4Mo|e5rwkiH{lLX@;XE&{il&~ zxwK;-DZ9f@J@htkl3Ex>`p63sQl;9I&1i%8LW{{XpyBqo*D-1k<)RGi1|cY^r+z;$dZ0m_5JhlMhgW(lgDe&iS5Mm*Y7#Z z>E!k22}8o`*2hFmCTkwMd}JaN`_UU9V$PuJ(FK+ezM{`25gr%Te{mkH6v4@NT!8>vr& z+48Nlj38c!B7P=s?Th0)QUmg!0zuTePyct0lS>O#o@GZGjo+8epU22BL_!@I&Pkdd zcT3^U+A9fOZ8fESG`ZMnBC%GA498ZDDNagStukq||J_W_TkpBR8aJuc$8)9YAKoq0 zOcGFN7tBbB>g-$2q%Wj+?g#S>k*|ekK3^m~@9?hOTiv)y*%$H9umG}Ls*zt|gqa&l ze7rJ2{7#b7n*a+=+`TVHUgHmCFOLgH&#_qyASQQ7r+%XNr7>bKx3B9hhp^$BaXDJY zV?`}n&7R!;w8{S?`oD}Xk4`I}W@-X{=v>S;}B)d1tYsQv8g zG!-Z}v%y<9V+-OpAL~#6r}_|cRwvtJ?JRYg)*uTSab|rz?(cj=kS92OoP#5uKDpzq zUI_P7;;r#wFz=Rj_cl5pD~Z&bV#cDTcr1d0<3*l5nS8`A@$vR7`>IvHrpsTl(PmBY zk$~MV7DYEu0-iwh)%#i7H+`LU4giY5TUNyO%yNIw4SaV4OiGlHY7&x#Qh7j0+};Jd zv76K-!Y+Kz)Q6CWQI`uRlvtuF?Ru~I8`n|t-8?c8v;y~^hK!Lh5sLU~nlF05TKxZO zDsP|ykV?_};@z-5(q;C_Ji^cE!5UP-a{D*qXj9ZBl238rL$wz=*bp2=0+kfg^}06E{8Vn`?B-KgLX7Nws%?+>*auWOPm4$|Qjuze3P z0h$BZJ9#{G>1?~-s7dAmkG641x#GBRKV{2O-$#9BkD(zc#&Cj2R$Aspf?$=yE@J^t zhIU0ogC0#^6K;sjl1z}_10S$rsgQQ>1X3H^nuD?#a7~J*9ydD}%vgrLq_bYIqGgya z&^TUaJdY8#eRF2X8yVM12Uw+pmz16_`hV~7hG#a^6uH$uz& zQQH6$jpG6)fynOF>ZeS_H(du@f!iN=VhMoP!9;16CEZ)$DMF1By#6BDH@Ulu{1Od_ zoX(?5w3?d96p76`{DU}FKjYrMs5sD${1BTU#b4TvT@_R1_Zda*&@wOue4+d207BNo zkD+3z*<Cd+z*EvB9T`QzGSBU8-N=vH9E=xq#*oh__#uU| zZ*>*h$-dTwK^bhFkg|A>oO2<4Y~(i1;Lq|}JRZuys9=ZR%~ML8%IdKmH9{gw#l{FWZrPgwjaK_-K$HOY>&}BN;! zRBk)EJ(}BicHOAt?JMjYl+@Vm@d?8X=g)MjuU54M z{@KsO^1JvF#LKkS8RA>Ah2hNg)o!^~6Dy^pv7{nUA6U@txMvWwW=EfQ%1@UKihOiD zC%TjD=DJ@Q+tT^sjMUTj^hs9+NXj}I%ER~j+)ZwHgPCDCzuzh>H-dSTOb-zv_W4w{)jpJc@>cwWL3SL|x zpjoT92*bZbqJ>^+F8=)!L-&JFlkqRVbqjVb^S^9ECPY@&_f&;VRL+1aYXVl`~$L3()Csf<-qR}P0Gg7kAt7}ug*&k=a?DO}V2;Ezm4 zda7WN&X7If_tKeEN+i-eVAF9bbyH@WVVB^2TMnN^-N?kd_Bg0Cl_yQlo0^@)rqu*c zALF*(t4nA$-yOE2M0yT^KP{wngw$U)|id_gU7 zsp9%H=R8+m#PBJ$>(>}Vr(n1q$Hqn6GC^OP${2cMRA=@lTwYPvRR_A7mgyYn7uB0s ze6FBvLo{QoW4ESrH_<9t#S-q@Z`So2M%<6)9BUPvqxRFu(3Re_!{+P>V(nSStKFH;fn9m>#P7 z{|>|TP`(|DFQC%7@RAo&)#BjL{M_U9h^+LGcYiOU(jyv7BZjGnFGh=P4v}Dmd{&Sa zEHZa}Buy>7;&N~H4#6E3|5Ze~Fv5I`AkQlWK+@D8+KYIbw8% z`yY-Ocd*O`U@s3hRaz(Xc%_BAwH|C+ibIQjzcmE;BTg3AYUr*mM zeWO6E!x17aiKZdn+FGkgi&CaY``aCy8A%QA!Sx zus(n@999yKv?-}u!+)FLIyk@5n6IQttn7i=PY zvstQn{Eijk?{kxXS!X%HTNsIX+nijnf7a&r5qUZoP&^ zXU<cp8MqRk%{illygro~xz#@r>fcBFnUN7gQ^0^AmeTvr zs(F11B`zPS=;$&AN#wFFc#+u{1U5v<%9iHJ-8*~c+^tE^IPJfekLhR#ZR-6I`uEd^ z$d?UUCqxR$C(#ZWi;r?qY8{Jf_1rIRyz5~a)k~&f4j4q9{j6wQ+Q=9l+L=W?yA3eF z9*IGcqXD`C$eCic^7|=Scw!aRjgV0o$o?^={41h?Nk%wrFX{-htssxYyF5~<5rZpx z(Y9tYIx$hiUX_tShw$*nmI8O21+4!EymD1(%sia`&}|g3f}G`v?RZ)5{sHR$)nqf^ z^VC^Lihm5Q_z7l_g3adhsf!c#pUp;!0({;{T=GAJe4G=&fg$eJm1*-o1Qo#LQ)YqB z&tMTj|JiKk01&t32ThAdRS^~n6u+-&PhJE5mlnW3t30O(fgVY=<6VKeDTxx%I2CsX z0sXfZ^WT3G#G}Xs(x>|sdZ+g)g5xi&>XwZ@OEQ7+7=M0FK|;>ANKrd-jhNP4Zifji z%3*ee7~#D5vAVnoyIaMu(u0fJJb!)&Kfw_w2zNA%s;;<&dfAi-7)c_G7*=~pY_hRS zis;5As~tN|sZ+Y{OqLc~UPfuPq6l@V9v0sZfpe~gaVI!XRue7O~XAab=9AsG&#D!UzrBELJK1Tg3 zY}Ld0cG^fZ1L=%p+G2q9q`0yuM3i$zje2Yb^I>kTvY!1N@Xvn)hnXY<8?ic73z91r z9U7he`9+9v0lk{R>1L#-@`y1IL_^$vHi=SPhbI za?x8!$UnK7-|opCDj-}uJw{1!;`jBNig^o|SOV`M|7i4Y5lb~8G^0plTd3qLg{g4? zgW`d7$tBa0aR2G<>=5au@x@C?bg2xPnRnz6^3Aap?caT&|&5*Tkh3Qf{z6 z1K84$W3Hhs(GC~&vHrvLxgJQAu^}kP;CstIqT+&f4};%V{V^2x#=lKOux8q9Y}wQt zRhSqTNE06s9hEkl>Tmq#27TlI0|~LJxY8iLOLZ0ubxG~E-k zVa{39^u5uJ6b-fJTccDR-hZn1Axx6W7!w&TT8-$dcJ~)&OnH_+zXo&G5FXLu-L%T! zhWk94WT)K0c;acEYymv~2NL}6s(=kR%7X1yo^8Sm^7=*^i$<+#y5^E0$4SvNA}Z|M zm!sb9;D`R`)2`L%16?P9^9nHHbkI|{Al9y*t=8w+#ibch@ z?`JJ$+tAp|5PkbxH0nI>be5Yu5u6Wx#Ar5~RnL%;kySVkvJBsXWp^IbQj5HN4QT)A zWWYx-i3vfGg9cPeI{keL=E4f*E09f}iM_h-)+H=vPx(A4!Qifk(^+xXhWq}6P;dc( z&lf@VjOfo;9OUwKFV5YxFWRt}cWaLAzS_CLwq4IUZ#!<&X&|kuQ;t?`IM!Z!3|XHA zViV^zeI%HXyzo7r%rytM;(nH+b<-4Un)-s=k=C5=0uPV2WBCLVC)0DSFTtOk7b+e{ zCwI>qLos+_Q+!vXbdN{2^X#u7eZh~Xoq@%lx#YZQq#p*a{_Lj&flyMjW>Uxr@Ng2n z=_;Cr0lSGe#4jD3mLXD?(;af=VCl*0dqe&@;W3JJTCQWZIq>48WO2A$>7rn!*|srG zll82odz+-@g~L*pA;s^|OcuAxkNWq|<6~n5A92_fyxz#Ag9Dp3W9Sj#(cnmx7Ah49 z4NV3(`BX|agDtOE7Q4clxNl%Svgq4S^&9V_=93q%$6Gvk9d@v^OX26NWhGHj(iX?} z!NI`~vu0?Lx=Kp3rAGkmVW3m#Ja|e~{OM$6rrDZDnG0N+{Lx~~h9q&<7pF}li;XV4 z!2wYv6BBalwYIEuR=#z10Pca`*O&3DM$ai7vPokT<1*%^*TRpn{Gg;;wqGS~aN%Zi z`C=?>Hz_z~FBCbm`C{D($3qt-(FLySpc?QBn>7=8yJKpKS0=LaZS_fKQ&G_Wc>DY{ zW%Y*QX)SQK^C?qhZ)W?~nL=kTO97P?Gmlv*iE^s9NnIN|Xv`#qqGyaKwxY z!9=YnGj{M05y3&Nym588>U2ftI^7!t6%iAWpEReO{&*>FpMo^?!%JUBzg zp(_W6L024#58?nXw;jn|e^0v3)Uxj2{iFDJtv$9_#|_%~c+S7M?gz2R9N4(4W++JU z^CRiI5;U7E0AMA%BKj2%VlmGsNduqHPUHk>urSC`xAK5SILdb#^x=CwU;!1G{F>#Vpy zB7u^FAyxDnNbeLkA~I6R8n}K!ik^@;$|HHT{IJu&pyDZ3+PZZqP07WTS$)6gJQqEC zy4;j8$@$FMwh5{*2Sx2Cm$DmzP{iCa9dAXk*{vA0eI8hm^Q3LJ0Hg*9jVGP0{IQJB zE*d)bW40FaDz9xaH(DWivaRP1nr&8G+FsMps06yY)^fl2Bh5$+)@$`e=D?j+gS;j1 zhfb5$o8BW$zaEtu9j24_?l4e?^LT!3oAo!ub=d0~RA%(-K(?nw>FHj74%J*lC^4mQ zrBZ8(^=;4ko`otWva(pP@3Sl>t8ty~qqFajte}mY>MNtHXnf3gQ*+kxHF4k=P>E77wy;c z7+0mHv8UI)p*i=`?RuB3*X*9>k{kScUEzF}KROSu$|wkk!r$Lec zV+5*afh9Ro`#G-7QWdXfb4uUe;ePmLuxkN2zN#@loVzO1?Oq4bilsDtliz|R2QM$L zbwj1|#l!RHr1kLVhSor%3Z2^W0mNJj|Bq2vz8!|NcyGp>;Yk)j=0Nat@%!v@Xz1)} z(ffn`ZZD>=n@;UIp5*l>LxFJ91d}bS>xV;;>)V`1pn8f{l}`OOX4QIz_!$F_WP4(L zQ{kbbq42xTi%0&1de%g*tC!EYug{J_tI#^uO|L9JZ~4C5YJE97TZt)aRNSZOKF(X} zJ}H<8Ph+)KmTohuWeBNHy10md0{J{6jZWN^UeCk^)27p{q~$m7e)B$hMpOGCtkIPD z;ewOea(5Tl4rR^%e65u9lknkttUxfnS83^Dlxnuyj@Q)*0_)kf93aFlyd`Qt4jf>6 z>o|nRQ8B?hU^zFu`BF%1wdyp>dZQgO1L%CM6`V^R#H!ou_ts%SwtEDY;Hw--fohzO zQk1)W_>v3R_JIRBpAG6?5Is4KkGSb|By1r~%uYkA%Tl(H|&P zKjLtI-RpRumu){)#bz>~%;M7T7@chhWiUwsDUSNKwzggm7s!?0t~>R!N$tR4tro z!yBHjTaUwg#6Ynog=ww`K-v_Jpk^GI%X*mBZ8qvx?6g>tVgB7++f4CHd@gP5V1qlQ z8F#dBGMdD^_XP5}7NKbO-D;PcG;_53DFgO&9sctK@!Yd%V@YDVXgrJt&L@)}{z)|CljqM7P7@FER|t_d zk9UuAYf5rFS~qG70#Cz<7Nx5|+I;hcJQ2?(%8P8!&B;VvvP#aFhTC+7x@;xu+;KH_fHgn8Ferj*pXxr5G;?R9R z0{{A%4pJF6U$=!k5uSl3;nb6yV55`B+tE-US@+R)aoXyGqdIVgXaZQ~$gSY?H4E$@ zMcq91p6HVjPKxWM=qQjPSv^fh{QXvpC6{jsw5Dk=oJqos8!hC(>S8spe4q*Trk5T1 z3n|V7hjn8FYG6T_SQ;;GP45Yu*+Vs|N^sU={b~GWBL`RL@?|YAQrw{gzU#n`Wd*dH z-sJa?RZQhN$wlQa&yPRe6L<@on*(VVcBWTbuguQ%azGUR1qk>u*BB&qf#kB8f{^cT z3!IPG?-$=|VKN&Ldf!ibp-D@IsM2YJQU!oq9xyJ#U|t39VqE z=$L|yKpt4w><t}rx5``*yM=G&R8d4tpN9XNxPHeiiJ|vqUXhL_9ShW z5z=!TNmTpPQ)D9~{g7lK3ZFm6U}sZ2it>gHnDAaf@I9_ZIt^*xRh;c-@c_I}SA6~W zpO0zSqHK1C^)0l`cPm8i5&ki&&s{)4tjV2#O%nrAt@AQ_NjJzwvnZcW9QI4+&nv(dGc*f;W?kGM9{0Xop)t}0|03ML z8b`GBUY73g0oO=Wy_}M!#sx)jlQJm_ITkh2M$vsCc2>6G7w@#FjW9#k z@}=Yw(2w2ak_mDneXyX9kapzaMg2VU}lmGC)2dG$*g{!l^H%qNzM^%EaUC*F>()A zhL&bt%74B)cEBokq~J2&Rpnaw$BH@zf@Qp)pC!Q}P1LehmCox6bUumZ^v=&Dbw2{N zWz1eblbU*~dQb91SX$dL!<%dXBef(H@OY?2q6%CLXOlXCrW1{>DGOw86AR?~N_F(l z$-UQ+K6*#u+y}WQHjL7LL_NX3ML`m@9!KBvuhZU7a^a|~~S8?CtH% zf<_0{oXm4mE>Fkx!_^(#6L=N4pSTAA9vZSlbuG`_idATb6<7SHLNEV5B>#O;8}9>I z+-ft7J@3UD{&{!W6PI@PteQOzPHDnO2_<&7G)bj6TN@c=FNp)Hh)`>Z;86>P9uG%o zoafE>UVB8P!$1$ttKkq5zoFGHNyTdx$O2-WITMXcS$)+i6D;P4CEvtnB6N2`inONK zQURDut` zi!@jx+7RqKR7PuSeiMOLLW!p6sUc8|AQ`$RLv_C&%@&~8j0jkcMUt2=BtBAL^wVYM z3xpB*pv!1F{t@+20Bb6t-JWw}(F?)l@xfk3Wdc1%?Qh}#zF>;vg+iPNThG=cP63mf zvx08%SOfb)30yR?;>z5i81c)Wg&9KAH_6ZwG$c?gnrcY<4UNx)B*e_78IjuaI9m)W z>*FV^l-85IJcVcm;Lhk@aRuatIkUv!( z0a3h0PWVR>tima54cU}6*Z@?l?HqvAf!ybB)!20iQV_qM3eEIVbA?|Db?YZw;yogM z5uH2VWIe_JR$sq+R|uOEtBb5BdH41Y%~c@|q>X0@&OKHOB)GD#>^mPD)VE<|ufRPp zja+z?A$*6Yl5|&m!UFi&2U#@8g%UdtjgvEtE>yWA;L$<**#lh+!tmg0#{gbwY36)f z31x5BhHRX9uvT3$#n1L=A1xsn-aW(?3qFqy!zPa;8_v@ij?;ZFoG(wL%xu8sr|mpvJ_c6NZr zp5tRg#hOSl8TTU_HHH&V6NWi2{u~gp$o8@st?Y6hgW!uihHiv{dZRW)bpT2ofw*KA zDM3RLJ^i@MED0rN287$yp2*`u+s!7?-R-?KL_wGMuix-3CIl;>&A#O-gS$sve6^W8 zX;h}IB8R|Ri?5AZn-^!MLWzsZZSxbg`spibjdPs71(nLl1Ou~l7o@${1$YZ}qkaU> zBEiUr#SVARg z78z^j7@9kedPZ}KWqLil^t?K|FGg*!DpR|xZ=4P#+q`r$J?qF61xbrbvlo}koWWy{ zf-P4B<>bcp@2*GYr!F>}b>#H@MoLV!R=mfWuU86$csylNu&^AuJMB#-`@R&jZ#HRe zD3{%YFIv^BlhVp`Qkve5^N5!F%hSUB+P<^&f2#OZTgkvWx~wRq7(z3<395A&Wg`+M z%V|HS<5}SyX-p|^>f3aqs-98GYNMDuAm`cVJIk?=i~UmL-IOo*a(Ua{7c@&=8%h^D z)@`GouQcbIZ9S3vWO%M#9L+bRZ+71g1Ir6`zVPr>G=K6xUggw3m?#O_f|M&=0Q#bx(@A*dgI1y5@Z@nh*W<945ZZ6ByMee| zK=6E>NbxiP#*i~QgI{VU6&w94RN{>9P!Pv2ym>?^pVY-MB-sO_aJdDWZm`Mge06Gi zW32n?cXv*v-Zg{|bELQ4Js7x|ZcjLjluaa@eq5+%aG>|3w=Qo)XuX~`r{H<|^F3mP zxC*1hCiegN&MgsrJ>5AmIZ8#ValWR(<~E}e>gklVu)rNkf2LL|Q?7luUk`NX+nSNJ zvqO%=^rXK$=HP9iORO)T<>5)k_cSA^HC@OkWv29eK1xcv+V^>Xgh1Ltzw?US?c!59 zol#7q>9ftT2IJ*ArO88zu|iV$(jHja?COzB>`y$(f9m;pU7;P0mgBe29ECZ(01uVP z+k=k@hfZ&n+!q{cQd|6n(;Qlny-0Li$RFEhDsZe?nncf3wB3nxPD0}1qed&jy>fVbTny&STie^Cowfp|_tQgW_cpCg+z*`$7bu9P z?$7BgyH(y=J0rn=%w%wGNbs`aRzQXXogOC zw|U2?==j6dlAt25N0-#wexUelL$LL?ic}{CEw?Skt^A84jLaBHDoHtuu_{N|RLcct zS$##1<`ZEKQV!tN$x`mq)zMPfdF{z8xrx_b9G>s_JJl9DHpi1W5qJiaiurmSZy`hL zmCEY71VW?eMQ@HW)z$=L*Ke0GYq?WjEy|)WjGuz1OCNwxh&b8GTTT8wFC|&OX6ocP z$nI&De=_U9Z=OG`1C-4dUc%!rZ6}eKnqfWvaWqxLeF>*j!dgcL*1~5+A;<7$_KArV zUn9q_)$5>-evLKQ%b!`v)%LtAx`N3m;6S)WHw%KRA_oKmDUhwnBTC)j&a0Hf! z4A1r3NGz6&M#mE+ny@AJD`mQvjAZU`nuc}k{RMB?hI1miKVO7hm`!`lR)a&r``ZV^ z-SAriisa`dlwkQXJKA<$L4Mu zEw{L!UA; zuk8RSCM)}#^uuTtA<^z3s9@ap)%|WGIOtUyYmD)P(GVJft3;#mj5J!0sIp|!8A7c# zWajnM7jB@jrJ? zUS0^q755VLl@b~6#=WTf-N^~k;uAz}=5(62U5i?(nWmy7&1}F(vJ1^)yKR&p@3_Wi z#RPTdwT6u%v3Mg?e54{~jWTBTgGE@y!QAN9o9~%I+8I@Q}p?-K+SX$z#P~g3z zrQ^DI>7U)|2+42j8SgV=<&57aTUC|ng5NiQ<~oCd{4T$o)fEx zA-M>XP{%n0t8C@k&#V5|4jJkrdG}8Akeoc)1^;!GF4&`o@Xa5LpKLAUyNyD#+gzuA zfj4zvc%!tSN^bRnw|{|?K$Jh> z#9JO4*_YtE{>cVsl|rgBmx(y81u5K-c?SORj7fNiZ>>|2j}h8GS2WE#M}aTkK{0#d3lFRtuS`(H=I&&%bUoTdbCZjO1b{Aw`m1*) z3l0`eBmfHjH+K&5I|yi@2!_Ab@BeBABgD#cP4c$qU9Gs&SVHw)O}q#I@Ot4F92$pt zsMdq!;xMl(c0Q;}ci75TplE?F{Az@Bvl1O{@?^YHEUZI+iOX(y7?L{W`=TuUar;fp zCfKBM;L&+WG@YXkHs zF!|}$2``fHZ*HAYV0K=;USf>!_rDJf4@Q95Ya#MKA^HFQWlbZ2C)j}#IXmS4Xmo$E z)Y)L{M#hLM_x8WV{;LZn*&(jfXNdj~o}V|%G+y{ge!kXRN(!6h!n+6JXDvkx2)z0F zOO|{g5XqmNE(P2#x3<)n3OmqfVx}fP8`YcI5{ActJYb*xIeozF-A7$EJI1a9rzUb1 zmZbR_EA4IlW^LNOOjR(;{;U81ou4bZN~Nws3HyHc24iBTxb_vwnYb?f>?cq6>E5f{ zv7cz#za8wIx2Gyj0Z%}mW#xd`!YHN7rn6F7dw6$sAGbC6zLh(Dvccb!o9tK5m=XDQ zb_&`xXl^>N_Zd|e@$7w}3h%Rzet1m}E#h0Ki}x>mfDB<2kc-WvD8-pO{ZJj6diE}E zdni>_*$JUcyUDkzy1Gaw6aS}I+BgHf=}}{3LTrkVKRBrUfmxrIFw1X71>Bk}d6>z< z)mdQuaaJ_gDb49jn*)*A+BUF0ztk^+e6e_PW(IfDfRhg%QyLX1!D)+o`U$= z|H`M6@%UmB;f9iqMZPasO?f8*q*7A@Pe{l5%#o5MOea>zBfD$Xo@=Om;Ed+Pb}?UAc4(n;eNKn|^Vr(eOOH z5gf}M-CDY@-sY0GmAc#Z$(9ah$nTnG<>Zm%(2yXcj~j_ObuVRaAda2!sp@14^Ib{v z-H~R}Nivgs8?J#}X(-kn_&;-*7ef1q6UJ4hH_G|KsN{&`h`iUvxduwL{V7XuZVb<} ziT!PTQ3`kT1HkaP_eA?w;Q+0{OL zX4=-eJs`!tKr@Lkdat;!tz&r3u-RyT1Z#`U7-M+O&%W+B740ysOow&{{n4{L!&^y{ zq4-__}cc$gm{oy&6v0@k7#35T#uV=aM{Xw74 z+sHJ}PJ&&g*4U24trS$-2%<+O<;acEarG9~%AhBB1T>W``G?mM^?JNR6Xk&4*}-e8 zM3d7=?ljo{@haW@268>`$*w?hAbu(0l%#I;!iAN9VKkbI z$*S!ywV8XcA+rl{^2cDb^?{G$i9C3V=q&d!T8KoMOXaMntxgxJ_@ z{lbQh59HKDA}|iAHk25IQUQta!7Z|*$Rw#*R0GT!b?o)MRh)!Ix0!)#dz3tZTY5i8nJ`Xx z_Uk*4{WPL3~nJ+yEZ+*U7iD>oetKc!ZxYzL>+>hpA zxExqZ1Ev=Zug!ClIX@ac3_%^VJ&^gOGx%MubhLQ`eGFi)fQfIWdQUfKGZfd^pE4m| zM23fNV1)PR&`jEX6l$bJY!o9;d2%F9udZDQe+U}PtoGAlxY&_=WK!@RhVvL2Tswdn zc`Z05hjfE?b8T{@B`P6dn9WeyJ77iB^0?P?vNt&)%@T*^N)gISGcuV00Pur3{L(yZ zIVmwnv(8d8k1SVv=-zR~v1< zKTNQz7Ua>KyN}hkZTYQORA@iss96D#|hmx3I7i72S4(zNLqp;!->`oHL(4eZcLz z<>2N9KD702pYkVMjVV*r!h`N@aWONa2*`bR9HGur zqONHHcX_ZNKxn>D5QxqGL0cp@2>wH;{l_0pjZRqEotZBm-Nw)na;Seu2EiW_C^Eh! z^hSOC`z^ONuHCln0eifqJ2I-NSV%BnV%66(yoM7HTmpZ%($vfT=Q-jJnElUBGM<+= zTS=K%cK-!Y{@Sa-*zl);sq~?d1M=^%zzfL!pQ@S_zZ>UYnQo z0LC(j|34ppxHaO3`-h6`?@z~cpZxdnrN;&ZpImTlH*1AbC|;%fApivc44%Ta!pHwh zo-Ac#XpRSlt}^#7mz8F1@dGDf!@A~P7p&ette|Aqeu8#ZeZ^k@>F?*%M*DOf5cZD{ zQrO@eq;-j-Tens=_x>@)vZg=J0pqf{AAXP03e|q1z|zOS>EBxz^5*}9X{Lp8NdGc2 zR6ip(Oh56P|Cc0xdh24SQ9 z3rHUN0G`-JN3=EnV(Ui{>C-Z9R$PFlrtm~~zib`STy9rCx_&9IxQtX4Fh#WQ2^&jU zzG(RVw>HLL2&)3&sLfiXI=e@q$2K;$6m%6UWmk(g@~j58B|`EqV_FWm-)y6(C}kV<@BvgV8^r&)1h%OSb*b|MrPse2$M?Y7}CqDxzgIrz+ubCFa9PR^zI zjQ-;nj_}=6vt_C7D+ShYTjn<>xi!p!!zLd}&RH0yNlXQ_v9H6g>#;zaD1Djf3x?fq z&j<0GA6g@{tBkEkMYg56HL!TRU^}pjTU{(;gXU2H=|a=Z zoObNkal}07bIGh;DdFqpu}=xl$!9Kads9zHL{#DC&RyCTZtE>4H@|Ip9xkkOk8EhS zLaBnHV&z)ZL0nhRRa^LUN>M5|ZZ{9_zeshdQnq+x;=6yGt456J(X$e*?@`MfSY2C# z8UkdBF+0-;eSGfTNrmvwzp2`qIuN);I+98xOgU2H=%Dy7fH)@#`YJ;bzhd z^Y@;(@}sH})Z$NYowQ-2$XyH{sHD7pXGE3e{c5hy2&^VwL|{7}#BY;+(i(XD)?-=D zh2(KGvTmP<6>p5oZ8gc&@ZAo#DD*Rkal_Mc{WaT+7Ogks$^^)o_qb;$IW2ec@taty zT?SoK#nx;hk7=|V<*Gr~_iO%r7rLv#yy9WQ@-dI{xE(QnqYYy<6_TC$S|;a*eg@so zL{{N_2fzfPHcQuKIl8Ndh0dA+^+(CJpI@|so_3;k=CVc*P($=iCdZ`bP;3) z@M9$CIN%g$@SZVqPy7z6BX-QZNt|KUknGYQ=FhPR+JYT%`Wt31^`GEziRN#$*ck5O zg3r3>BRCa5?c#?moO7#9AYf96&^O>X5j!hVhRuQ)bS@%z3Jb29L-m=TlMajA;pzi5 z#}bx}AcRgi8Ph0Q(Ds7;{2B@(5aZ*a(I zTf8CAX~0aHNr=(pC2z6%vg^4LcQiYE4zS_mf=x8lqRCIT1l??p+(eov0~J!NkwIFn z%@(gDMBY3GuQNX;m8NuCU%>9y=BM3cppx%kx$ZKVklQ^R-};pnd6Y4lc`&4nAE1XV zJdmtBoDBhyHb5@Q2WOuLxH~;mFv-7X?)2_DKMok7Yl?Zx4FVblXWGv+RBfHw7Cd*F zCbf)G`5n%wa#rv^k%~L8_gu$NTr)uFOcqK@uCI*EvjtVu(rJ6$tl7U(yL~-doFs|Y z#&|)P1Mh+O0e{KJqJu+_GPx2c#4XT`XnQoW8O)Bpv&r2#2s@y#oOOe?NQ=^D{x&*y z5O+&?KsZ@OOx?QynmPLvGwHqU;(m( zvF2s;@m^>HSpi$Tt|vYfW8%Lw)X2f7(#DcH7$Z_{>rKoFMH_l*D|PsAY{9xC?? zee&GUkwgQX_v#KRmDF4J?CnF>YkNOJ&{wotBkm@vDw?#!9ZV8WwradITmeECJdf1d z9A%sh(vk}npLiG?w~a|qW=KiH6JMnqReib99FB-kw=n0vvifkhC7nwgGt_Ywz#+N1 zQ~z4is7akMG&WXfa^bqlXCRA_reu^y=E%R76z2~xJULVNt@0?7m-3u?QER5Ag*)dP95o4Y6- z?r7kuxgO#4hufix(3Z@Etv;r}k?&AL{n{evx6~;&@P)-vF1l!@jfdtFJ+#*%oF64K zKneB6WaV?W4;uZF7wOeu4t1P5o0?rc5p(8shvr~IK9`tYHqBurqZ5I=dpj9}0vWqz zXbe|2{fkuU>(X=5HnCeSEGCYX9jwy}IK_1Kos?UtFpO}`+N~$Pt3)Ie5<7z2n|>}N z0uEtG*FIYfyBjxZLo6n85v^;f8X-=P&G}8E3V4Q{Hw|?#1`LKF;J{VJfuSjOhv0?y zIhEZ1I#eo3XD=zmuCrQ8OGffM@e$RJyU5<}6Y~VAqjR_BCa!_%rct{%CSBi5xAuL9 z3TNQpg_v+y@5zEB);Wjr&YSoVjB$kA9V=4-H0=6jquKE`PFEYKj(;$cy31a*H8*R)lu-7k@)eS9j zG+&cLz9W;T&D9h)WoCHR>=T$L#Rv{En(8qOIbVPB-mo$`AvIOnKo}vLd?#HDUzsdE zZm2n(mPO|C|HWp%2yw7#Sb+C|-8`VyY%cAbRrSbE=P7G@2q^pcGvpCZjOf|r0hkea z-}FN|G&C6O&gGx;foN`Fo*x{jM**V3eAdTb^>wAD(|5|%_R=*vYX3Yk*V>+V0X0{0 z)GjMu`qJ0`;vhr%F2*Gv*;p>96^jXmiT*(@w*h{ugswyGr90+hgF|4Lt;1cBL9bD@ z+)p(EF1zFq>#`hs4|iB-=(b(~{uR5p0Uvo(U_K-^gPCZk?iJg@#NbzuDR9t4ztbh; zDEV8IL;d3?TL*Ln0MI?=bImt$N>=^`w)?&r7zT*BxFdH;@JN8mFc3S4M!%pjyboBFw zQ)dGNs3A=cwY?0Uc^CtBoxyZ)&5>*qfR3=ElQs+jd@3mXxoF^V-arjAAbk-onV~)- z8q_?j@w=JW7-D_|PNEsR&_x?>(OKU<`1#MdNG(F6GIvliq@*w*Q0q=}dOYk%G(*M% z4u&_fH@3YcDDVSkJltJKQx1`OPDX}^NrUT0sW@1oB`y;W>{xr1Z!{c1kKr6T!!L0a z{Ac=@hZM2)Sxi5)rlFG9vfs=r;!$Ou;8`reC3Hh7H~U#I(Y6BDy>08|Z{9oA=`s%*LvHJ zdo`#2uOeg0?kE}jbIC5W(G+8-5@P^|DnZN@h1VX3lpB(^0I1X;lxO%2WZ7Kq+VKv6wgKboFr4hWwh`oKOI<|7C-pCxT zt$Q9_gi831C)P+0!z1Ct1`gDQN-hf|D9T~g&uuO9`56Ffl9Sy$UUb`$llZYn;M{yO z`1W8NQGtpCj>E$I1e~7Njlai4^O##?>Qc!FN#4FroMtx2M2`bibeWyw9@d_QyaK$S z8Yi{Crf)r{eQ1Rb6G0wiK{blZ4H2!rc4f8;WXfgI!y}^{)b;*Giw^V;5{+@GCeagg zIZwOj?e+*(y1-SR&H<@0T4E!4Fp8-SRQ5J7#8u4H_Lh2h*#w=j%|@sZk@IJyOg2nx zavOISL{txW(OcHtdCY&tKqTcM04C3N=vzxVCH1Z3Jw#2y8cwtnUqF~e*nDIt(6Tv( ztg>@(90D^JdF!|hAlO3lB&5&v3nsj6+BJp;2>oj;-anwOResFYTuePG&@imbrH%%L zv<3m^d*>2J?2e&=B%p1DGuXZlF!-o`uA>sz&WG><8NkZLfOud_6Vxh_vH>nP!T8(7 zGF+hX-iNJzSrqBsO+6VO3w(fSJUK0V>LUH|FuQQ5?Gij-bzqwP@m*Q?u0VKvJr2zm zHlB|WoAqaN<*JVH`ZZY=8E-v(Td2$QD+GzQx55)1nr79b*CSA6sYZ~vMYe?M<@FUS zr3|0;hq#3^g`>VWy22O^D{$&@^3?G3BYxfUSgYwrt%9Nsyhou$JpGCSeYjcu`0j4NrlX~6 zDt)=xY`CtjYar7Bmk|rjXRolmWu6bbb%6_d5z}AhVL7n1B1^{kR?<;8$(10J2X* zK@0j#v0!sX<2=+H#f)@!J7`Wb(40oOkMR+`{Zk@F#OsH_&&N}5?cc?AmD==OJDYOm zs;j!ZIv2$B&bc@TX;8T1R74%{F{g+P`t%vfWxzMOUt{%ZF-kPfh%@|n6Eo9EH!(e( zrd}D}jf^s~FP$-D4G~SZuUO;Cw5}0}6y@e{fc$QE)mKOepo(A4{MCD_$?CO*CYwsZ z*=Y1sxmAsi%&>5of62=(jBh69hh+VsjK}2*Tr+&e9QKjSKQd{3S6bV!9p74R>A477 z7M3tQ4#Y{V3m6VlU6^mm-fJ!IOYr1ytnTO; zpT^y=PI(hh|D}5j-t=kB1fTiyXq?rm$pq*LGJ5rPG-o_Ik{K6 zvaN|vo8L@~EX)G>>fZ(?(!!jGWUAxDF9gimDNX9yeJ1!cfp>lmzd(XctZkdl*GIy+ zEgiZm7SFIu)byRj3%@nx>nZ#$Qby|1p+Ir4_0d+vJSMCi+e0_UQm0KZXH-*ChqixaFHQ|zx1Y?@cfXRk|bz}*t4lEo3?2cbvMA7=P<@lz_ znQD4-bi`8cU2%QfjL{vs4qBp7XaQb^YQK@*_rfjqjlJWql@6aKZdys2f|mjHl_WzQ zh(q>iOQw}f{9HdUDTa^pRgT88k)l4&f)hbBJ-lZgU<#|-Ijz}+lF}A##$NV2w$G59 z3lzW@&_Z2diKxBKI%q!VPp@?eVnpATpFQ}2XZesZp;ujUlPfnY+S-bF&!u+LnB0YL zkL2-;G)r6IGC=usZzN)VH{6Gq3`I*Vy=~V@?1!D|8;eOS z4Q1qZhAWOku7_7Li@x^u{%6E?mBUvi{#`d*2Bi~1(dVZ-r#CbS1M6Sq?9Upk63fV` zO+Sr&RVCUvotQ&fd)gGq>1;oxE=GS9i;fo=Ey~WRf6fd9$_$T+5i{)?pa~=Uh=?fpQ0817gmlj zp<}6_IWsI`>`}Vb7X;@8<|X})DMq1rP!BJ_5y?K^1nR+#2_yiu4C22@1xqg4Y|YO8 zkXk=YuCZuMc}!gL0%6xPYsURR5)h-(&38#Zl+!&-j_n6oc3XeEh#c9_;})a(flpUi zQhnol8t9fZzOK2kY)Egn38UhE*EJP9jtxJ~QaLzqu3?6WFOdsECfBekhUfeyigoTp zqlLf8IvtU|bQYN!Y&{I(U+cH7dD(R?mXKmFZNhG`p55>=99uj5c9zY~jLys5yV>9k zwe5xHBy@ii^Y%gf$o&^sccL&1C9=V|%ziuu&^JoOh)j$c?p}=tDiO|1mLb=iUVlS* zK>>$&{HWUN$xPEc;s0ukj5?xl*#P z5!@-oH`a3d@HO(kJN^|5U}NXao`2GP+~-ievGJQra;4h_7!2+mtQKjzD>4zxbmJ6W zHIg7S=ItbZg`2Kkv~t{jmRaG z0bGOKv$kI-jA&x1q9pqX49ygel5e6W0XiS498Op!3HlUA(4=$IBKJP6P#UF#V4)9b zH{csljbI25^|`jhQky8dK~fJf?{i5_(z8cpth%dIlk89;Ne`vrS055PZH_>+085^-m5BJ2RE9M;()YieMJz60D5*)WaU&WQ29SYj=KoZF%*!ZJa)% zB`d;FywNtS1$L@B73JgPut9W#4hSfxol#jQiX}y4U_#~&fF;UBEdFp+_?U|w_#B4v zPUg_efU2r}AL=ERzSVs3st|FM?1Ve~Yabsv2v|I=y#hp190&g(D-UapY9ZN0-+JvQQl^v7}wfG-f= z+Q0V5$7(A9X+Ox)RytmgivWPYZw`jCJesUw8X5Y##L|ARa3>OynC>4G5>qrUO|-Ua4|`0{2h8h?W7IKd zFvYWQY4VrV<^SO*6Zm}$h{PkxB~e};EO8=97y{2sloFTZC*S;skeUkh`R>zxM-%XI z+P9UL>JL+)pTEN!0;5}~9^#wB`WLO@J1^6|-nUKOv=GN6tUrXsV62Lgu{PxL-pru( z1IB+?ri7ni@z2lr&Hk$R^4lw$yH?v(X3^=?HgcaQ4g z5=%>kyx~nvP40fL<9HWet=vl{Uu@b)A41w8XRKj3IXgKyUGX&8J7u`;X#L+b3}hnU z>=*q=+%i(X&&9GV3C>=@GJ`PiM*<$+|9jV#s85F+TgOh3*gsNU{3nY>I}k>e^?Z9? zZ|*^1)sadils`@_}#=&Qx=%Vnd$<%vWd8L7X^ z9O47nobm#k1+W|s@GD2`|8_xtEN?zaR1^UZjhfnATx^P3l&Y)QKich3uB4{0BE$>Ym*R;)G;L6sefPVy{|q}p(45n8(Tw_>wwc42ow)C3+mgY zizjAbsaJiU9P;~yi)Ov|6(`0uG&C0D=Z|wZo|PWnn^x=S5Fp-`wfIiCjsE-Lh~ce- zRxB4r!opYKt${?mhpN1nQF$RvExI&j#(GX=JZHKr($siQ;;&#o^MEz06~2}a7v zCuw=dtLV8XV9TFej_KG6EW`({XU`1ex!$W*+1cf#(s=TFh(k>gI{Ms*`@K!irw$*Y zP^lpA{ozg`dApvv9}uuHP44(Y#}kgTkz}!cP90F(fBkK_KOcS-m=09fufm;aBAD?r ze|v^V<;YFuJ=Ol6I#QH+3+N6fqqOIj$sPaXmBA%31##IrHDRzPyJ!%M?pmBy~oL`Rh^vN)s2hbj`4VK~-G0!Z^2uW!4ty|}1H$EByQmI4M|r;Z1`(|HW! zI3}tE893rYx2I>XDM+MVNcNo6?v+-3~WdoCqS052iFZ=rA4Ekr{Jw~HB3oIqk zyhpsu&$JWEx6bmYzL8u=*gm_($;3=EWf0fUVckh<&#&elc}E3%T*}d(;IHAghHqs2 zunr^DY$wz(reE`1jQ42AaEz>bd!%eCkOkOuc#}E%Ens7AZZ589I1k8dDtidp=BfE+)IDnAxWpJ&&2jqqTr(^W9S+)n->V0vrlRtYus3 z<6_0d$j4#JuAK2@hOdfJGBPMDtGY!w>5?)K^PG$BeE8f8y{|e%O>gJTc{%X+*BCr$%qlz z%jj*ZcyW1Ts%9faCStmvqk)~66q#jSFVV<}>n>WXQbG9iGGoSZnaR~nh`7a10f;}d zd5dRnHlVt=7$1jB%zME0@ng-m;7?lQ&{;D(ra194_JTNacWKBdC?93Ls|EZx?}sut zU2`?{roB3RjWzl)iG{kizT#ZEte#BqDY0f1kCKVe> zMZ%v^P-_WZy`GLYhe)Bqn}r_b&LC8DdzOR5sK*X*d8Gr3ov)MwU6spfI7 z6CGLx%ZuRl`+)uO@=?d*?ru_QYF_N($&^a`obKedDy`$4;P0-|3~hxn09`8tJc_OI z3L*wv@EHK1o?>Fk36;aV745IW`s` z2oAwiUn9F#EPQEJWw?Ma*YuoKV=*n5%4AN}8^w z+7RY?OHN)?r8l80HpzaH=cqwj6(w!iAA?phBW7mSm^PJNHd&z}B0n+yS~)ryqm|UG z;{K)B4Z!e5#+Y6ff6>MjiSc!fTcIzYp zKIg}=dj!!6`(fs%BxX~b*6VgY(86M=7!gAt6_LOf*5JVY`AcA)>qNJ_kS8giITk+_VRXBxa_#3U?+qG-t^;c z*-GfmscYq0+~K(6CH53%cD{Q9^J&XCdv_>AQP`Q8rAA;gh;0w*e0YP1 z2iR@7H|*F<*%mPwubHzGOn0DFZPxU9vWy6W?ju=heMMIVj%C#{ry!$LnS4V2-bV3t zcHhKKZ;cvg%g2fH>R@FSW3*I=n0L16B&&4ctB})C6f z&~Og@fVCyL`G?RW=&bes9n%gL2aWH;IU@SSBx291uJJ&NBJAA}o02@g{iB zgkO+1*dB-gv7Qnf(r)ph{`%Uc>D@B3eMW0A9&!jg5%+`m zfol?O6jx5isnJ}vzW&AXtNrT)M{Qd(&Ci$fk?gWSUC4Gso=537P70rgn~CYA7wqA7 z`N%qghGVfkg%da+=%BTivQxJX6$|_4U))*BT$ztp)JDR1P zN3pV$rz|L?_RS^mtSm4I@S_nCq5BjW8xvJ`zs>lXX1q}eq@c=gq-6wIa}4%h7dV6`BB$g(JAC2fL5VjEecKAHQGdkPHd3a}$gqI&`XfG%1;<<^xg+n{ljZh4HW&*{h1? zNzRSrv$Z*6tL988LkdDDv!*@!(C@SU6mazxW1Vn!Szd&2P(>&FAsr>tR-YjBPVlwz zmBI@WVSH6IVX33~3$bk_~R7qWIjh7krr;E!5+hXm~JRM7Vo%I3t7I?H zAm!)4-bvY$m=;{dAIZN)Za20#YYy7I7$0eS5_2n=s}@cf{`@VqFGs<57~1RWC!snm zAfWpKk7V>+_@~z->SYSL=tHZIkPu4m+Eqvj29u42X^G4flP+rD7=&OtK5;jX3Iysn z@RMJ-dwZfQcQjJG&=aqOzn_BmW}ABlp%ePJn+8as>|lFVs^EeP5TZAMm#` z+9w@?!uYbnoZgijywC9D5hx;j`}}Z^=?S16cdi%R(0(zY>F3!0YJ7vOP5ydF0aiaK zOyI?cbzYb^G|vOQxK*oHOeLIFon0BiO?oDeB@6^%L1WrPSvB{GuhF~uk%pb#*A&R* z)7MVmdRYan(bfyC62=gN%>EuGZRSFgpDfwRA(VsO1ri-h#}WFMkG_1Q7#Do=#9n8u zkR?^fs^VdkgzC@s?wan!%2%(vJ@a}+UozH@y$?=0^KH?L<0}@bVFuGL1m=*lY!vG_ zLH0i+`lgRKBB%JCy>?QQVqZLl!Fo-e2EWKA*A$_8^zd}C--$1Ap>!b(bj0fHz`sL= z(19ULRw#q;e-u{;Q2($Nh>EbVrs1Ex{;qwud$%$^FY2vmlICQO12YkkxdbiVkdLfl zzIZYc*69@2Bnvy~r+l#6D&5nd2P3x#bu?yk=8J;-EEL-jJeFeKQHf7Os%S`T6pf7AhVRZ}#yXEwj*d0RJn|waaf7>1xWoqdBm!H6KBi))p)bc?wBzR0xFPVs>hDKd{V$l(roaFq@o4L#6tve@Fs zwxt<x4W7Y)lHom#Qek zqfQ>h>OkU;B&l+eA!0Ch3z4xBwl2?`AkE4HE+%RzKnXz_pNdwhH{n#Va-!kg_i)B< z$hxn0?2Ak&G4oQGoMT-vCR`;TB#0zwkLpy|bwLbIx75=2w#jbzc@#xN#1si?&Zkm^w?{4qpZbC5ey)MDxC`iuT{n zoJB$9!oiI%ynvO1yK7JMG4KIzj#rQmK?t7PhRJr>{)$@Z_W+XFH;57sgtQNV2hHdG zxS!}>*P=|wf2|=>O0M!du+AcJIJFIsZBh={)6|Eo4!dLQ;4WOn> zuz!C4=2_30o|*2hs<*1%st&3B?0thQzD&>O7bRxPlFVdGVNm!$b%?6D?+Niiu#kQQ z^x&d5JemCkBCiU5RSaaj%B_svpw0YbQC}C;PE#mDPh7@FM>kZ-?qFHN_$cIyD^^w; zeJ#Qfa>99w4hfnpj@298Hj1!J2EC(Gu{@QJJx_+$=leF?$qf&a= za%hq9dDJgra}D48bnUlZ9^L5zGym9!dNy}og&~mj`ZC5ig(zd)VzZ?VKQ(lLURHB$ zo94Z^AkG!AW2vBD_bZP?WU0=q^^Q-O;Yxg~v4`Lg`Gh519p|@4Uxy^n)=H>`)!~BC zuP&XV%hkZZL%^)#;c{N!1Q|ekK-(LwMZn_6)$gF;Bv|4~f?S}k)rfphG*atRj)LYO zik>H()zW5Su~b|*jTzWoS+Ha5i45B0qH1sNn83V z`Do9mqte6QrB2m-PTW?a))jfZ<6X$UJcy_ZID{Yk{_eCB?` zb7Zm#5zOfXgLb@(u1Bq{^+kR%GBK8#$~O5cwdVL_#Fe^t^aS8*rRUH`Qf{&bvA3auXnU zT*+G4kN`eG0}{a(?5wE|eoyOJ-TL9o?BcR{@M;?(IN4Q&OGqVMobOH)PH55o13vSpHG0OM{R5iYtutO#Fq`5t5$N=>lrxw*3{@$@zix++6V1+VXQ0Se~e zIGmX#wtbA?9c8?=!fCAuJLVv|fbNv;+v}&PHX7wN(PX0(fW*tElNA@?TVJ++-AF+Q)OpCi{ww&!)k##|BT7Qq#CK9Kd|WqIADfIi5@!TT+bgOFHxWkKTyZms&90%^RbE(FjEQR# z+887KB!WSqv}2B3p+7RXSms#KwPX@Ye;^akRba89Bf{_W;48;B5HmGx;qEyS*YRab z8bBITBqGF{hTN(Wqd^8Nt~N=0jgcELF7z;L(IT>)vilI)Us^Ed!Q3tyK+f>T_?edY_}SiVK5o{3}<@rWm? zI2ZX|kC$FNT&|?>S(+KE#vgwV0qz0@z&l|w^4qUwk3y|F&vM8Mw)SbPz1-0;=Dc3@ zfpHP6kIj$4uYC@4M0Rutn&03_q_M%SXPv#VOM>uQ_7s#0X4%f%?{kY9tps(Io3*Q% z6!H@-13B~_aZe(DVfEfdhm5gi{}!xW{U(%cb`-w|Q}{U%3_~WzUxkL5QFC=rIt$YH zqVZ8#I#g+kbMKB@hy$QHn#3=!eCi~LiRHLMnwRBu?u&BiPf6WbI9G*kGD@CFe{$T_ z)*71W`+*)WyO#Zasmd(hY9D&f9XWA*8YL^8g$21tt1s{}D+YoTR}z#+r8C;-xW_1u zA454IoB)C8RS1m4QUzu`#P;AI=|OfXROg485Rqj>8bF=qXic%K<(ThaIxEen{&zf( zz&;&Xvf)?s=C_x0`zgoWGEhZ6!cTfN(;aUi8{?bFv?~WW`BEsh;E~*RT64~5$TZvI z_i#aWyirK;xnA;yeyQ|ATBVtq(J>bhpBYq*`AK7&uyZ}bYZ6BH5 z#;b%O5VL-)f9(zoFF}N(!F8_$oD1moJLux+OK2a(gxfP6Ze3q)B7GCnnL9Xm3_FE- zG8yBf)Pha;k{Dn`fbI?UOLA4PK%(fZ=2NhhF|%85?u*=fkPKDMR$r}1bKTb+f`n_v z@W;7*!Z-VuN0vuDI^_8lvjD_YJcgHgvFjFlPq>{i+D%w-{((LddbSwrPhg-(tUZLBBSuD8c~_o|6~?f-;%GUyCyF}&L5QsCumwVP z{LV#C7&th#3ryonL5**Z9a~3;1GLh;gyJIIOaRV#>B5x4^3UMnqn-~rI)GnOYg*yZ zJ2=Fb_v}mG>Dt+$;<)r&FP-nmD-rUK zbT-30BW`d(OpVj2f@{8Y)zG{0;ZLo0-B2wh&e{t+B~MWDc7ow>n|%22ay1S|(8Qv} zGIB4%Xcw%4#^?h6(x&TX&;?aU=ZAR4Lcu!QADTgl#A6xYMGv=em`*h}fE#C}HZ6hKC*L2-F?-@P$~U=i1;bX*Yiia@e;)=~x!A-pGn zRm>W6)&KoHjL+)zdN@Pe02U9EVi;Ji!Z0fuBlpq!E$qt0K~YoiX}W%bdg{0K7!5WW zvFf{5H@aCHj(ZhTkJI@(V?lCNf>T16#=dVdv1C&icx-|%$NoXU!2L)FClN` z>Sbg?AFap_aryP}8;C15-qa+9kxQ$D(BW?lK40%Ck?jw|qrd=u@&xa_NB2I?Gqk{T zNzxn`2UEA>ptoj@va`ACN6@xtLY24{!o5_#I{wr?sda_^tn{PRE%X9p)*$d|`!rZP^tOkX4oUQ4e?-vd2Mrm*oA9W59) z<`#zzDe7m4nK|yyq0om#DbZk^8*U#i95t1y&10O(zY!ofbt$4yCuzOFw+n4NW+Cr^ zTo`Ph>4fz%1CeX`2Y$$F3GHJ#*{7(AQ19{837z~zWvYR-Ch3QrdV1^drY{RcXVUwO z43WG>(7)MkVT_Uejf>bmr>1Xu^-eQT)Ek1Vf5Jf`f7ZfP$Plnp@ec>bhD$`)1SAr4 zrJ{&3VMBz5I!MnnH1!dEW#_cagCxaXvIJMHmo&4XE{R&V*w2O=i*a5AzbZU370q>p|2%pc(rn=>>! zr4UO667mm20DuR6Ngc))MoP9QT$A>tuyC=@?-S5WV}X+@3H@YOqB0wfgmm8uFGd@P z|C*d^&m{kX)WL}>YgxSC4}r~Us#U^yfjTSLKIVUmd_t9%Or*a0ah;BFoM4Q|%o7Cs z&5{Ch@n_(h*$g}`A~}6p!_tC55)1C@X)=p>qQwjNH&}%<&qn`J$)k&GEby-0Z(bb0ABAz(Uw zsZToy#r%F}-$f%LW|A$xe(C#Hv)AwgwO+PUTGN#n9k}6yr$6>;T@J-^&b%>1h)wN; z%3RH=`~!~-V*F+=SLngN%o!-C;u-o_f_y4dcz=WF8Rgi;r?MDcW#qu1Ao?!ik|izP zpX#jhq_)TgUwhIMH07&FB&pTCs;rbYhmF5Oq_h>-+P4>9mu;FpX2+-Fs>-b{OZ`KE zdwVw9536$yts84vZ3Vj^d<+W~3;w_7W?rIL-e_#N#`TJ9IfnkZ9XDcw9VV6)^btUL zhQ0pqH+&zlKYX9bVC~9(zW;w*WVZZ?Q2l?sKNWQ&P-CODrcRmqJx+`lV>GW(ZL;iV z+_Wg>j*$0xLtdhSKw1-Nv9t!_`$U&mbT0;!a91}t{SQ~|1ZYdJ$?*4xs`-R7#AtGfG_aue7TFO>j!bgJ+H^Z44-LQjR$d z$pNaoqtx?q^F>5OMXiQ9nch!>@`C9JgCxV(U{T>N=!r;fXh!#Z&-K%?eZIeN4I%dM z@DLFerjwoR>gpn)peQJyZpio>4p7Gju%x7<=;UNdBO@ch5UY=&qQ-V|`dv}zVV3F? z$O6QH!oc%&7$GEpt#psr*w}U_Q6fB^C>KNEBW&Du)%&; z5UA?67WumsJ0JjiU_wR_6(TsFR_C=CAX#Uy=b- zDF}yKL4*wa9hVDK2rL{sG)yRUMyo`_#|%7WwQtGHrKCBxzbne7uMR|kqzg4Wl1h7^ zZ%HTh%Gxf}2HQLac=)gxP%WbI_;fa9GV8tBtGpeBp+l}~~eqVPp`stETqqrzVR zTL3hNk>(e%O|G-QHwFmI#|)4f%)NDLZ0+{2YNJwjo%)rpVqbALz!j*i;~LS!Dg@Y{ z0&2pK#xRwX(#yuW0q@Bp&?d=31K_yram4oQnomkv0o7Fpsauum#v21{j6z-mr`|llUizeSPS4)QlSg7bFzEIRM=`8yg!5 zhGqhsTRz+f2etl6`1e=dk_?qXl(ka4WEKpscVQf5@*^ujN`|^K3GM>l!bmC{=-YRS ziWh!%1EK+|jM0GqGhzRXT>S5aSB%|9A+q1vB^Ww47^eIYl^4Ph%0KOwXw10BmRMG4 zxKP7Q$;QN}AUC!v7^;@S!z)f87IJOym)}=Npr{n7p6Kq!sD-7{z5ox8H&V1$x7Xwx+ zrw$s<)VK@qc)i4_pCGz5wX`x(!c5u!R*EI@KtG+XBW=nkD8#d&z&j(CxXa>BKp(ut zr&h=hX-VlzA&L<{XGPX&<&Go=M7_HQL11FNJ=0$F?67sUYjKKqKE{~=Hz1&pi)i-b zBXh9e_l-UGzu7Q$K&=QcW(J@!;ZhNz+wB5Xu_Y=MzVZb0uJpB$+(-9Q@_%Da{%dKx zWQ95;m8&;pLrO3CW_`gxHncLtcf;!A8(c9}$mby}= z=u~?r(I0({{!V=krF+hpMOLdjOk2WoxRu)b-wTR*P6I|dVL2oKs3NZs zTenoZqv51Rczg>8e3zj>`y)00E2G^Jw%tyN}%9Mn3H(9uaB&X$tit=ff*s8-0$mMl@cxu4TSlsW8`Q8?Xr5HqbU`kVZs$#C4p zuRf9K<4pXoeYGA+3?fGCe@P7Tnp?|GS*oxV)f+`zG>OqckJS8n?p8nSV$uG&`|Zx# z^=*qFIwggwc+Ncb1?ne|zR^pjtoAHJTqf}StU4fRr|zU#JefaWOLw>~jcjbD(q@kq zC6JP39d}rs_QD${bqdo8koduHSPmn|OBreOg%z8*h_?If?EAxJPW`qwckA{;LV!q_ z%4Z@VQ}G|uV0td^_SJqEizwV2bp ziLITha*ycJC5NFa+E!AXektaQ@P6~PN>vRUGce9=R33{cDHbbMM5dgtD?sADhkZ2cQq8i4m^0$ z5Ozs(S9S)LL)i+2TBYd+Fd-CAI;>E5KgrkC#W$LTwBiuJWHGC>ihohKY_lgJSb}9a zlgn`Zz`9)(LL2#IYIi`0SR>*0Z{(l?W&XOe-J)~3b`5nM1YeU%&{=K*x7)N8+vb*+cp__!Gn?g8i<#`g_??Q& zg|F((wz17;4TI495u6V{+obY1-kf}D%vSiUr-ux8itHxa)Tx3dbkdy;wPVg)2;4}I zSWTnM)j1lukQ^f4vrUdjUXz9_dNjCk5aGuqrfhlMz04nI62{UN_Y9P)1(~bEjSO#& zz9-)t&J@9>N*EXv3pu>N!N9Ow+;MTOis`$Mb~ON zt%;VaI9{fooLJmIDc>dW*!60X$rqKAbJY5T_$g{gy=C;skS|+Y7Ons%06ubV&2z%J2j6hKm#@A$98{Ijs&M~rDuody9WCZ-Dtg88(SLLVk$r8QR(#IeqQMTY<7igus| zN{A-NHzTIB*Sh>9C91W)A6&6tFF1~%T~S;oFI_fsR1(~2r7nxNsiKsHB&9ta6mzAl z4gS{f@c8XA_z^pq+ojF@C%2+M_p4`+$6@8uP7H0EVsVzpq~HsjUIFP3cYxLLS5|nI z{xR+i-KW=P3v{I=-gPI_eX+xO@+Ckd;*WsYF{}H-1)&T#D$HB1IdO~O@u!~PjI%ZN z<*%%W3U6YXpN0!>*}s+nsYhgS&G$V0k}Okwbm&N9jXY16P%-WU>r`n{vYRJdhvWOJ z0U5JZxG}&O%HR<#0`uH*v5+Eq`0T*ToBJ%ZBC9$Norg*$8Qosx)VY?EEpX#(Z}e-@ zb)l8SJZer0tt%dSvVRS*!}#zHHt_7&EQQ*hSxXQdJ=bE3j@HgpTWXHu$95c}na43R$_-l^WAgFPLTk&(eI?GYjq{xocjC;I{m3UCfb6a zO>WFMdJmhFl11dlWOc6m3pVP0#vJg#T7XEG_XiSEVpX?O{Fp=SZ_%mgq@zc>jb#h@ zv)ubWEJgC)BUzXz#_`Em=+zgQi@vmxhZ5oz8d@)4W>C|^7zs8MVn@#j(H=WG^mCp+ zh!f+g=YRfGQnbiiVkGHTWxn7chdo#Jd4S;QwvSY7c;N5{bItn0q1^D*)-h$6PUA|c zgNa%V9NwM|5+1v54dOmtB7$Bm{HE2oU=aZ7K=%h5N@-^2ok(8{Xqnu4~Ze%lemrGF?X|4``Q*_(( zd;2mKUOc7;0!i^00)p0MXl_74@s?FdvyN%o1SRas(*Zoz-7!d^CZn3dF((O<2b(Hc zE1mGJ`7(L=b6roMdPDeL*$L6ywXxBVNoibP`TPz;*yx)ajabo2Y@wwsC2a}7>F#K0 zNu1F}Z$~zDE6M-AuoN&tmuU+9OHUssgS)UPwTXR5cTuJ^%w4rX;;Z$fZdEX~JLM&3 zw)e2qRowuZq64!L!wl)@bwYU@wz1ju=t!6!1{wWV+1)n#w4Ni|29s56S}OUDgBkWS zVrc-KA|AMnzooiLoO2pna&>i9;4rk|zaFn~Dqd0`Vl8hy^GESXy_J6$qEMMTWVBM3- z4rWAYPL>7ohxI-|ZQS;pPR_rq)^X}#2<0|PD`C_B_geygiA4att{(0`9LVp0Shmu3 zdHDYF>4m`~WZCUPnG%w;@WY`~-;cdxeY2G>j7aX>oFva+a zMiOeBQ$xMR=uF0g_KoN5IiYV^kSO0>qr@X<2kAeI84;a@%5DgkCLy28)E76gCe-(r zMc$E@DA0f5)%Y~YzfQOe>0os1ZlU_v(?^QipTEY)$e3#s!%4f>`-NmAwWLti+8+Amxqm zY2{XQZg>ShSV)~w=iCHtd%VJAOc#tj^9!G<2%lBB*V!+ggyYCTT0|N`#0^f71LYs2 zfZu^4r(>$AOk@4_CThO-JFcGQ{XISVn3Fz#d^x=6rz9SdZg#Ne@6As?n-nGFyPw7W zk%##!Jol<1S|5EeRoeI{!~_N-B|wa;`D3D>K!J6}1#+C?Q8MJV6Vi)4wD2<#WSR2rXDKsjD^}y?G1SwB0Qb+}3eAjIUBI%9; zBHoStYs+2be?hJPDEI9`0kVQOib5gY4|91<@dGoX2OA^R?`%k3KXT;tJ=u-&tk0Uf zPUrcSa#3fW;?v&I+LFXxDjT5r7p9zXj9!=PXLg$MHvU~^?}jGM2&LOqGQS9`iPq`v z;opVxrGe&p=AsA?55L)JF!<$6iKOg({?91SpT+Znl?2)R?gae_9aq)wE4N<`tg79r z%()0o!~koEj_zu>K#+WIBOt4;=$D(SLQJ!>&=5MFF~%Np9?^df28`}O{VBr+b=G6s zd1*IK7{|n=zTJ_7yGV!)Rbz`zCT6T78`x*IvR-?**{;S>Mdv^wBYAHefE#!VuxHT7 zNNf%a461Vd=|PT6pMj{jcx+@OG7S7k$_JIcg#;6OQj{>lPd7#~60bH^{09p)!C&qs zxds_r`?w$M^h!ty6R(Hy%!Y5A9j)J+G_M#K#}&mHYisKr9{!9d{ZUo1xiYR_s&^Vy z&Ns<1Vj7@7#||b`1tVU?Ca0!$b`&*YxMXC|GpD~}qD)N%n;3yl?Dr1i4BtO-Bpw)j zdu{XP5tqlwx0(-lKJWkc`s;sQud9Q%NCL3wH@O}j_M$GQq}GO)*%)(m`Vp<4@Ek;d zC~_p3OUV9s{tFtpMEv%)E?WK1UeP3aoxF}FC?a;}a*=?W<73k#cDv+-dixXhg$nJQ z#A`+aEP5y-Q(+MiW}~~~1)WPFS0Lq5JejE%*I){m)ENXGhpX$q8xQ=2MnX@7U%@-UsCKrb1(6zjW72tH%8Z9|rg3Ps%lhl`YqoR8?k2WFjIWQ5hRqU7p6)1$F_J;7>Gm=jRMkd)Bqz|cHP)>6*2YDK_%@o( zI9D<*=XJFCiaAJ-BM1jx#qIVu(0a#BxYZv4bhF3Dq|Q#LEPd5eK}>S;ZJkwQKxOA4 zJsJxObpIz2`5(v;724gTl$47->JBT& z$}NU%mA78Z6ZBcR_l+Bw9S^P4z9MkjB-C| znxE1)8^?cDVN%TMmr|)UHmcLs+s0k29krRn&3z5jQocS7yc?CPHD75;Ixy38zewuo zCx{B6ZBxT{zh8^aL~KC`#~bEIxMEAY>c2&k;>=T!z}wkXHIGrXXjac(JF$!#G1)Q> zfQofna2?_n+ZFyV+W}mPbu7?w)Nso$K2QkKAsIY7-AFtv?x7f8H9prGe-|E({|c*S z^mKbNaywtS6*_g0#AqP8H@Mne{}y;rQVp`Qn{^0uPo81z^~3 zzo&0Oy$KlqwA((>wirT~(=?#=b9Hsqy^Jknuw0*Eu^-{tOGE73UjT?CcDMcIyb_i*sw1F^~F9zv*$R@v*Us_FC2P zJ)Xa${ZLe0tTFn^pW&|HkL00fiC4esd?nKFXw#$KO00LC-0(RhF)^LkVtn49EmJ{h zb0c}T%AOfktjq1MGV~9{xx?E*&O);RXycAgO8O#^!bD+@@~79tJ-?^L=jI;vHctP4 zkl0;~UinwCu&_{3QGe`jB!2e4go1!T7fy(YG0Dqa7!Z1WNmO>T=;6p~@$%}ewiJ<) z;wgl`#ZDXccjU+&OP{j}I{L;|3Jp0-0oW=F+<4WjAKrsQ#89>c47guh_L;Yr+>VX7 zU3R9vpx&sAf4=$&_O4NhM(a@A$`ay*^D@`2g=_Ye)UJiyTW%fO9Kb+^u*c9p=I#d; z#+->{quh2UMCnxUMG+JBznsph$ALOEgg7K1ZE9C;f1)fh^fkFZ?o-(P5sVt4Wp}+8 zYnar`HQ}l~%0Aq8&7?$Jx|H#`n&9L((C_HoY182AF!>tZ;)@g_1Mc;22K~#SiAe=_ zSHi4-0bW-=N?yL3o7mCkv5u8@M@dI6h{X^}2id61kLUoJe%RMf?_wfK0?_iOB9$Hf z5vq4!#vh~+ECS>r^p>#3|4ow-Nj6{)lzd!zsef9_|GYqt0rATTR<@x2n;t8v)ZNaB6Nxpx@bYrGq2L}hyk&#-j;dmVO`rYd@F}$RNz}_v9q9cQYac}T$ zg2~Jlz)0|Ir1^@=;Ngr3ESq=pUnhKhgzS9L78Z82Jvf!X@wRT6dDR!wQi!}%Ocn7AMrc-$m zWQcB+njTO5ZBM6A%2m3BK~%>)fr23zU%ybp+yQ&h5U*<PQwKe*k`3);RUHPw`2M`NE z!@$tLq#XIHQL!zs<-L6<&I!X|9n9?H2k)8$w!_}JU3&X6&T|*)di#)Yd01RNtZZP% z^x>M=<3Wa|A=AQrL;b{Jt}G26*ES5;kpTw$S6>*;U^%e|gBvsM{RY(8AUk^0X~UKI zLc0aFLzc`2#=kl_yn4hS^w30<>?+)^+l*M8j+vo&j4G=h7QU{} z7x~eNiMj$g2^!Ad@+6Zey4T#rqN8K3=sWzN1xWW^bo#?%N=i(VSnQ(3)0tM`*D>{& zZC~v8bvvII7}gJqLuX`xrg?jasyvb=jbj*&mNUUavJChJ&k{hFYmHh`#mBqVP-gXc z#vN0Q(Cz6KMV$5O>Wa~50_5@O2`k?s&~Ty3FUVr1D))H4vb$u9$HYA;B_)O+AkH5F z2Rim%FbsRR(+3Lfcnk8cdO!{mY*?{`E@48=Kv&@Vcafvn(h(MYJwh#5Y_Jj!Wb?KM z!N$iTs!{e=pByvrJ>a33X9*aiDI!1`!gQgmYFBr+?poUmS*hJnT8ip#Hir-lC%3vj z32d<+KcYoc=RFdWS)S8S%wx0&-0&NFyYb?D|41$+>eP^6*rP1>^SKVQoA@lGc2JUr zKE!7iioDdBVnwOGa6DQos|yD0O_GC^=KASU)j5)fqjLJbd=%)z1bymS~{QZ+xpZy2lgXs#)@|3{Nzp#6xWe1S~H+KK!nlkAu7~YzPessm*0Zk7~aU>UmdpaAgajvHtl*p#k74_c)kIFm( zzRI}!Ql%=jD$duOiUvBk(L{3^vg?oPCjajiOKdiy*`hL>T!5yS9*Hpo&KC+c9xsibaYwSct{|CyX za&>=jn!L$27}z$cEvPx~FB0qsS`ejPuBq3;sGlvYsDH?qcdU8#n~fFY{_0)4eQ{Cn zWqz9SRDxk(8dd_tx;vC4ULyyuj~h;81D~1-YTf(;Ewfi9w@gC=_pbu?N3nDyNp_%x z5I+Lgr_S&--b2E`e>DK(H3JBr1mCBL0fzwUh9q~W^T+oIDIEwvXkgw!_czF7%oa@J zM!q&KC42n(asB4~hj^-jtt_(IH=oHJpHpt^+iE@+f(&uK|Gdm9y8n64e@mp=M_2OOiy1^FSwi+{LVEkFMEB|!ebt$pr+p`rNc zX<)|H>c@|&?cLp`ud;nn0)sQw9R50NY~J@k@0vppvoYO@N#y681>*q&TFj1;WSMS$ zu9&3rge**bM4H~@PQj-@nV!a;ZAO$sil*Dy$u|;-dK}mC7w?jW}+v*QbTIsim*vaLxdfQ00vS@@m zy=1;XyrZkLVXPL{xaGEs+j}K7AvHStR=Lw*b>Gdk`Dt=1Jl&RH;iZAcDzudh$0EUR zwVhlktE8;UKcjE3%AsxMk;-vxho^oB#m>)WUh9^WB)zy-xA>A-_tPA&rNYvY72ksYQ8%nEtsR>AE;+k?nnmapPt$t`Wv9T;~FcHFC!cB=#&qlCrA*f6v2$mt^ zG#GTzQLEc{ct}+e3E`q#>K~2o;d)AQXk3<@ubrc-_N_RbmuQp4yJs&p5h_V=*x;kAJ?nwpHjt6?IyhKuKRu1W*{AT-P(~VYj zymAJ6ymo&Dj+TMzR>A8kg9DXN8Z(peW*#lk@S@DtVjp5}*&c(y6Z-Y~_cZ@S)!|gs zCMGTgjt92I7}slEK7>b#N1E+q<^J@G)y)Tni?O}!@y>`uL?4^%hA@up{cs8+S8b%E zw@D#}w|0VE(h13d>j2FJ^s_g^x``}}tKKuRj9Sew%E;05q>!*ohLL%U!xjb3KZxDJoE?Y4S&g zw!8hS&#|~FtqZ!f78{f?Ebd46)vYdvU`eibAv97tXWF)1xISV1t$vL4ES^W_^lQ#*rA8~i zJRvO=JnytY6mQ>AoyL5*YyEX|8)>hFw0+h+bbDblQrVn0LW>-Mkm_D z_MsaqqV-q80O~Hbg-pO3f$W5k>*$4LX z0tc6{v4=(5v0(}~dwnjp5rG`HP8+R7?=v_HxYVmomP7bEsv)@5DSTIX203slBVPP~ z+3gh_KRg^{jL%mZ&mFeMX;WT|w_(`lnT;8ONX|-|^~WrO;wkxeBL|4#C;8+jSch&S zH&UsFIQYn%IAfjaDLJrh)U$h#cd!8QDHaU9Zt-dskfWnq!jA5SMAm}5Kh?Gf-A z(Cr>eYu4-bm)L!Ny4|t+4D4)RIu^pzd?zs2C!s2S*&U!Gu~2Werj5ip2SsmE6%RN2 zT_}Pqk!fJgABdRY%KGI4lN$v%eqrfj-kUy|2zu!{9)zV=i*qa=U6p;+{XMFzvw{DnlVP9CMc6cP~;aiCk%ejwUtzG<*~R>C1UX7O^T z^J_=`MR%Y<>X-geUX|I1c>h%5&B_#}$P0d&czSC_YduU6dw%S@BN;+`;@*8%OmbVdb+W>VqDuQ@2WY`k?Jv4LD0-nF z7aN!7@tR28r25yfV1}SM|#O87JEkBtIJ{tKkuq<9-WcbF4oHt z>H3H`^PVyK`V31^Gq-7sOOlOU0MM*9+nH)#J0H}PQ8K6M^`_+xcs8+M$~DQ~xcvw# zb)HRIJ@g9)lIAvq8fE&KlUXNS=rxRMOjA8*HJ)E>c1;83c5++Iy)R!x9uBLuA3k@2 zqD#Y}Q4mI_yS*Whtu?7H4(4f+r#HaT|Ljd3_dt7#%zOs)qf=eWo5_vSF9!Nx388Lh zJNO4%c)H9vYTKC%BFMHec?_&?-la1hjnZ(@^0b@jyt=>MjVP&6Q>8uosF%#|by#8c z#D6ee@Y7ntuY@d1F6RZ!PpNX~0tE!SJx zwRvtN(x!jsw18IH)DT!92A41XY*)**xXo9i@v=~EEilZzo=^{Qvv%H6#-Qm(f>R|s zIr@@OVUiKlb>(@Ben)zW2EN?ws9&<~3*Z#1u;zDxB|9;{?9+&0B=5hi{&j+N zS*txa{gCI@cw3H?yTt=l6bV>79nx$vvDsufVVYxI&)3dNX9y=dtY+@V-76a%9G6oh zq-iw13z5x}Cy0n;@u8)^yXZaRD$f4x?K+&3!;zzxU-wYw#`gqKTp);*ZP931u|mAZLL%n1Rop6MiktB5gV@|q+}i7SO?3t5BCChq>rVR6Ezui^ z+COt%fGwcSqtuH#TdY-uL~Qx?J#L4=^#18`LntF9we9{=)#{7+s77v$+t8)vRsw_K0+(u~Z;Pq%5&g?QdK zl(!AAR+LIBR&y`b!r$>VHQQq@#}vM;PxPl_Z2O$=Ryc&wF?n?Ue7M-tlvsv)b5r+k zeQ8sE+m4~tn<-+>NWF4wF3>zpy#r-Uz z&-t3?v=F{lH4KfruSDbagrlPl4wu7BP91*92&e?pHl9oyAKqOf!^tujYJD4(@=iE@ z40s%EH zR5>hpy;eMUT$fm6gJ*rhPIag|Fy)W5f}i)e8Sj?+ZMgC2tE!Mx%;4R#`Yjko8cRIl zn;@$FX_b&Wfs)>wV8tNQI08^gM0&+H zLyfe{7MEqSC0T`vw$7>DG$-49O8ii;X>>T!?iKS~W>J|o5toaXoeZR+5L8NjY*KJO z!ag#NwyRdSg4a0dE_FTB<7nYO<>-pmimUD0W|L&I&;nk{!mIVZaRUg3wg7QRce9r$ zYhr72_{^V)CzqZ+^6^*MqcYaUq*rj1=S0}>F-iAM1b*8u>F(0q3ES>Jgb+{ z_G@X7Qi*za3wT0+v8Qy!b!mBSe~d%2nc|x|@Z`10$7HdQG9AimNPoXwa@8W)-^psz zvPC9t4X_R6#mr{XT9_q;gjMR#uUDv_UL7D1Zo)4U@z!t#*jU302rC9^7NC2yQ z15g+~jQCF(m2|$mA-52BZ^3sSk&WYF6qc0BQpf}@+;$00t3D=j@|8!*Mh@LPBaCz| z`y5r5IT;d9Oc&vMp%hw6B|w}PBcJgcZSkPX=zk7>lph&*=xyY|SSztquazs_a^$p= z=zuhPw0iOD38TLo8!=nc@FaN?^P{1_vQ2smv1V#lYn{y|r;ddG6Y@>~>DcibJnQF! z4QADmR)SKnzT+&56a&l$?}-v}f2>Nqp%>aDhHoQXHr`(>{j97_dBhgeH3!NF5*Jhf0OT$cPt`u_X7P$SamGARY)Kl;H zxTZpxirFm$IQYO`hC3mF$8|WhN{!r~gmxZId5lUe@pdQFDR-eQklViP1x1kH^`R^p z$D%<&ZKu_0N`J+}Z$u#rYWCZ3d}*v0O`G&%>Y6GBwGv51tf;VX)SlikY)jiCDVNVhs`pnAaXo*0`;o{$FDlm z0hUA6T-FO#(?oae+aKv5k}n{0nUtl5Qs~B#7w(>KZhL z^!Sh&A75hVynCg}%RkCN12?6-Bg!u!06VeE4%^esevrhhm89j_)Itdz2R-%a^)9`^ zYeiZBXiYD0Ue26rxhT`(UB<8v%@UhuI!+~PEp;oi*w;9Gsl9rK-kJwT%wHE2t3g;J zL(}Sr3xSN^&|`V%K)KZ0J$ruf*0q{l>n^!iem}#6RaMAeOF{%Q4mi~hQX-RQF%(+X ztWXIm(Bh4&6T|*w(gc$^?!~TQHuqevl3~tc^-65GgX^+!JM+~PP5W98`dGdGPd;O?#YJ5faoY>ITrwQbW%^)`E)dm85(l4=)@ zqo4DYzI;CyIKBK!1HBy(I{eCB+ax>o;9&#|JstZ(no%iF`b~l(Xsv>>2 z*qREMLr2HY9@$}d%FT6scs;AIs``UbGg}(7gHh+Hq|=)?aUS=j4e%vK_xBJ6Dz@6~ zjS0^Tt|FiM%K-+O0^&kaT<7)FsyI2;lzLW}LtH_!iQ%&=PkKo8Dn!hgI{V438Lm?M z=_9Nh32ynCb}QtLbQvY?2rKWPZA=T6x%aT8$TmZD9ue#oY!(3w-@n`)WvmKh4MvE6 z#s%NOL99(rnsRR%g9EOkDv(Ns+W7ccFwPE-Cvz?VomK*;s9eH0)S70E)-<5Cbk}g^iz* zYyk0WW4oL6uo)O6-5heS(dGcEb~!gJvs(77y+7j;_ZJR5r$z-l_X93~C&DN$0I~Da zc(zN*#>t6wG@avhuJuSwq#4yKiEh=Laa4K_$SEEVO&7r_udDc8q>K=J8lfD}Pge{v zFgR8fUo{EKK4DSoVV!;)g%zge1_?iqU8*^M-1z>7VX`3^Ms^n7EP(+qJNl!qw02&y zW6|j7$AJE?lyfEm0b~@nUrlI$Qlc0|An$<>##`K z?S!`#o`g=je9}P$0*>2wG$cHXy)}yV8?@1)+ET=pK+cbbvUw_BO>XdB3ZD|87v7v9 zoywtL=nVhr9J{RRAE;}jr6$sCX?p=N&Nm<{GMBlH{tXg_its#V$lT(seZjkrZNrF1 zH1uDy;O}Gkpw5*T+9z$uUOo&v4gipayu_y*0$)Hmnp&DysYKqJG_w8+M+eEfU zn9p{bq=I0#^TOa0gB*tprk^;kYbz}Ky5)`6<vif2q;(@`yXKY zA3;CA_%Q|`j6_TR9wSZbH{knUnh&( zvnh%5+l}Qf&o{p@JuuII`_zDvUusKN&D{AJHJGk;ssn$RtJmuDLaJa(M1Z{wO6-gR zarQUOTCsWoO=%nOYbtT7TEh^~w4V;X!3OwC{bT=WpQ|BaAJuA`=;X2+bZ1&oF;Zur z2z3sg7%`~dN^t7?wcN#ofc;$KKx7qLKFO31@ftS5<|FYHS}9bF{C8awi!41@p{_;| zUH#r#n983y9b<)agNLf^@O5-9*zq7UIlcZ>@7Ouem(V%}%+rtmn8T3TlVmvoV#TM(e9zF)Fp!37s_-!7tjr!IC2` z_^~j(jY}Ad`I9#Qbp&KEyo%^_%$!bLUnVRrCNZ(C9~xaV*vH*l`V==Z&sN_UrX#>BP-|;o&z)kj*{=eJg|Zr%T_a{TZ6Tc@uNv~* zbTUu-><1x(=`ZYqh1z+;rxrJN2 zo>5i8QLi_tV&rV16-}l&z_s!v+7QleZaK+1y-}SG_aK-0`6&y^uRBw{)tk?u|U9)1(b#Gi66k3tc=^mpj z1|*!sn=a1uOl3cL?(yCTN4@J=I|Dt5#bcupql=-6BY=I0yx8MmAWB1`njib;Z8rG3 z8GGBmpRSO3Rbo=TEM3YS-BvhI%t-hqVi*;uu?DG6)9V{|TB>N77%#`2kS2q~K|Bx$ z9~jwjA-pC8ZUG_bR>^vQYMm1jTWi3!)HrLQR8(lAP^i^@>m#3+RL!L=c2gV5>CmKH z<9w3^)|R2f47`ukSdbqWFTKgHnEV`l%sUY$q+3f$`%ajn*1i$3i^e8&BYh6pFpPqV zG;!Jl!%cV%DlzANFA3Ew?N$5Td#pA^j;7d{eusCZnxLg95X$F`8tX!4_;+fDKZ~TXNFZxQZymQMFX!80{=(cQd(j2C@WX1|ELl zumrlRnHqsH(8*A%aiG(m8}1LW`@i8t`>?&1yY`iK+#hsY`chF zfLAazC{~SuMP6&EM?xo#CWlHSSt-5zb;S(O%B#gRBZq-Yye0LMh z!5RTeiCQdNgdy++bGr1aR&z15h>cV%&*lyqncLA{DU zk8tR9(?AZYMK-y$z_yFDkipm&)|KyV6e>7peP2D+*gQBk{ndqA?aua1KTBS>oO@?h z-$Pw;W+7{oRdBSr(bkoen};$LTI68h6UmXwnH!c9O{pU~gYuSk+Zs;_m0z6Fk)a4NAGSG>N-QsEU2B!vR9HLSIGOUhu_~? z0%EwOo+_wvjac-5n)@}Tau%;+1F+k=Gd*5`@l9;<+reVZQa8*HL&j!JG}5^hx8ijH zk4Tk7uf%FQR69lQdWcqF_;$C0)mitFva3%#m=m$kZOsm zaL&=DP|w?8UGZ^IvX+J};|Qu-Pb!*z2)MvvvC*PLZzPEU6W~yt8Vx+YCeDdH0TWXs z3jAMBOP~dTiX!j*m#p#2Cne{;Qi-RVNI8sePju*7Y;f(nkI4n|H7WFqImn?Zv9J@` z2=r?;>DL8VqkN9Lx>HrN?R{x)v8hQaws0Ug5>-7e7G)-m9c-qTBDFl_a(1B<%_iOG zWb|-i;rZ}Mg8t@^dY?&s8lyiot{LI;w4$zHZ}JgBKW3&}^Zq#&(Et}XE2?H*)SMn} z;oB_el?pLIP*!Y*69aYaz};-TFHN5Ec*KFwTO0o1%}PE8||=M=#>r8Rw7>Ihj< zrBKnJ)4`5UGEpHi+vBOXp~kuf6nAYB)O#Ie;%ZsBt~z}m`=c5wu#|E>7!ZLJLByCr zHN=qLGjoG%1!932w9&mI$t=wObFP+L?BN2-qiY&K{*~_|Q zmR}jGYVw1wm1S$Cg0HEj%85(1sJ|`A$_t8>g3w#V*O+GEDd_~nMm+5zho&C*{UqVC z8-LyzjY$NzZ)%xjLGRXkc#XU~`%{fwi3F{n6QIN<{gjwoUtgE05WW5W^D?kUAk7`r zn_LxAcp&~oe|LEL>g)FmDlUDk`O7rn$+F!-oB=7KLv=KwIALMc2K`!0dBOWgY<^(K z9X%8_TvSRz!uN%NG2R+%3!3US>Ak+>sc(}nBOCQ+tCe58xv_<^b#`89=MUp`?!g{Y zTsS@;%_XsmR;CoT)lU`H=r2@O?C~uroZ(N$S42r`*=4+SQym0MWhlk{f8C%k3q?Mui;v?|l%S^AupO~twmx~n=$+kLyZ z!ME%?af>6ioY~nqaIlQn|9c-*O^M#JHgEToRxbO|&`xcEJQB#w96;EW17D5-Z@f8gypb@r(U)Auyn-$X5V@Tp)p>d7NR;QeR~@$-Z>GNT5if&crQzd+%U@&%J{{;=GpGYwNdoWV&F`V(h^ zPWVu3Ql1F8Bgi|%{^J^KA>mtbqz<#33Fn3UezZxUTHaF|zi#*J>@Nt^8Vx|0JV5>> zZ=*q+nQ=Iwjq;auhKmDg<4Nf3iG=LGZV+#gfERjrvnu;9nz=rpxx$eP5FbwH{{51- zP=d@+PlODAS{Q*}@*cRtObST}3I?M>&i8M3|94w%gah1&0?mFA&2Y$HOaHrH)iN0H zu+6u1cm1{ce_cN~G$-6mwu3D)YIEvnTKb*}nO>JQ(2W@FuSNZ3G30v-_(~j$px{Cd zqo?6pmqP6HVdUc8M^$(9xH4rc#3@UrLjCKy7JOnpB^J(ion!CQs&i7_Quk3p>8F^e z#3?97Z~u4b=3jScodd`_b#kEME4$4;1rRy;dPlDu?3*|Bg-cm!pOdQ%>%wOO#RHq& zSp@Dn%oqZB2os5x8A*=DFn)kY6U*eH<&d3D%XXu`-!e#n8A<~~Ll}qqO00f)PfXYw zM0wF{KvJKNE6Bny8p@grPxIp_bydf$(P{a9V|sSUErlrG^XCdr>r4(2>6*XN1-OS~ z^(zr+Epa&?PKL2r=xOR4*1ZXoffOAllUlpKfxmX|p}&9Fep=bwG|(J8YJ#QezeNz3 z0GA{4la=tlnYcaqylA+5Y*(@!pXkaf1?FRZbbP12FxGv6(rUbZ0*0NhLUQ1B!}T}g z&feFWv4f?$^>~RjeZV&7#U^@uoN&U1_)xBVj8%Xa@~Zu|t(UZs*5&j>tl+_%T9;P6 z1K!S4kkyGzzI0eGggNLGGp< zSZSuQ48RJCi0BO_0rP51;G+hp?S6df#~d6f;pf&+>jS!~{1x)2*5)eBIF46Z+vh07 z=8hIBMR4YRtgUs5Coma77!Js<4W)YY#M99&9p4pUhO(N^6woBZ(W-%g;n90M8~bBf z?>oA4s#|JH?jmTdIGylPZ#+Io7B3Xgga``G-QAhfyyE7(Fr!%s{Ggu*tP-mh()}}J zqznN*CLBug+z@UtZ)kC_Fv?xn+DS;3F^40)>eC&*ZcLV3hqsZs>1A z_rmqy6-jzw4T})PFaLtS| z`g`RUnIecRlXq5K=IHV+#f@`tFJU}_0s}=ihEm{3M4rCzdL~0rXqDMRQz{h&rf@oC z%5@_eFZ|>gO63ml^Ar5W;URpXf&3fkY%;BycX(z-^c| zdI2+PJ<$(B(6zG+svopcnMtW%<}P!u&|<`Begd5HZOJ{A1 z&%NF0h`JDtTMBYZt_dn6xaa1A-x!bMK60^Ly$^fYgNFV^j*68?F;oSBqt`KXiA9BY z%nzq}hg(%%d+KtD%1~MF;~O@vp$c%%GE<#K^ z4TAKhJG?4JNY^5DdhMO3Y(cr6;+3X;;@v|DZ0kJ#iY|b$?#oLkW1Ec^ATtGEv-scN zoa>DZ>z$1I9iKXfnsQV{8Z1VWOX)7wg<{ixM{;c($ET&8EwQ*m)F{+!oKrP_JJO64 zN2BK5((;1YSQ<+qPf@r(Ct1zWpH0&Xn&c}|Yaoii$-b4G!wnlr6JK|{)_}AUV+V~y zK|l~oVPR)xu!v-9|8TcFo02w>MW>BJ(HH`SM)V8xG68l&x zqmM)$;+(~8(Dsj$OgQVDIJA~q51-UZ{ZOfLeH8k1<`Z&1G+7tAU^H5pNlt%ki)9(Z zPq7#~MF z)N)@kZ5$~%4-5mh{t#>4JBuQb2U|$4DVq zUww1EQ9b)(HzC}1pZewT!T@%V;!f z-Yb=fQ%*%zf=)w$o~07^PE+{;Wl4CNyG#5L{))kU!fQ8(Ek}aDYpUrA_=Ffzs;laT ztl&M3cki;)Z!&oA0|D6SpH>|{&(`X>U=$ka{56jAwDrH8f+{|&m zFF*Qm9AHlL`m0T*q?#1^D6dKwRaaEl^zq($KJWk{TC}CahvgAWao%P0{yUGtLf0~v z8WSp_Txt&(^PgW;4#$^%D5`h9M-2wSHPBdzyXj2LAXr(V?GuX~QC>!M2&#@g;nKVV zE6CsMTM5lywi8Btkch2o_`aXiG?uj29#mXCYUP@IIgzZ|lXugK>uR=Scx+WpWMywR z5!B0!==67B^q-$sP{{wcT1!ayxjdjyA(})LLn5(3qkR?`Q{aP(t2T?3kL@xZ4;AK_ zFkbbLBU5SqeNisL4x_av$yk4ho|F^=42H9|6L*7dbV=Ow1deGnOyYK%Gds?@-!;-~ zi5K7z&XpxyO)qdqSQXmiNCtCmd4OXhqbcpG0#8X8{G%otI#qi_bMpvu_eG=F(!tjH ziDO-Mvg?Z@B>0C)d!*gu+56L@Rg6eBiQ_gAMewq5c6J*xkr$3X2&euu#QfDQ>J0J$lp6h-(ccgLf)UPNbHb)LjVs*cZC!P@d=WsSp5mM|DI*NaRC&)s2!;@ zJl>Ac$`8%7!Srmr`~pP)4Qa37hOU^n{cHm(A7moXCH5X|WaeKzW&eAx!1d^11pW$J z?0?$4|5_=KpZLgN>ANEFjYmu2I6gSqt2d;mQrAsGh44+G`Ud^2YWZvBK&ANIT0fw$ zL1vJ=7|W*-=iNMFwwoq84n`p~_bb}w?H%@a|H_0dB*I^cOdT=RsYB0P+~#syGw%5L zru-DOuY9?>|8xxhxmlbi0G#a#t91Wgc%7(KYm3p_+gm-^onrniA|lXix@fVNb{6Yj ze+gJqiv{3}uup!0Z{m47pmNW3>J=vM@N=m7X|StJNE|=UEMGPtUaLKo`_3F=`TYJ3 zE^GQW=kfBn%`-RtJB3>SkI=ll)i=Y&LorQXGEm*idVh;f)1=61R89jeY?i@@vHc z9(CA#lk718Hq`lkv;xsk&`_LB~@w7rjO9#qG z9dyseW;dV*LWV3-M58Ih@sLyR(Wmp;a02Pt_yoD8zJ9$q(GI{DUb@RoFL%a`-f`JF z8_SSsc8brHJMqfO$}(7wCH^#+5Ngn`wAXZtbS+q&$m9f(YBdjZ|3{Wg##hQcJcKu&0v$gj zT1^et`^R2H>qQ#%1Qw}YI53EKZBrJUC+!Jvb-T2r<{sCh+b7c7H;%q?C zCllO=GNNPZYie4i8eD(v6mGj|!pZ6SUmUp+zSps!;JGvZY2tcJf!KV1o3oq1`B!E4 zx4Pjf;Kg!YoY;|ge39UnX5Ko--4L@;h8-X~{W@O8eR+4qKkrzq-5Z60&Aj8W(VvWk zKUX~&C&<0D=d<1|r43-Y1V#hAGNb;lO%FV6Rfn9vsLPCo!{FANl-8u6l9=5j)kso! z><~MHFgnjIQahpgyD!K%v5eL9{NAq4 zGW4y&WP_7#0BS`LI^4*Ev)i`tVqIlS?4(Na`B~qt|FUNaEIGMxsm1;Ev2^K2b{a{r zDCur8idM^bG=DD(g6A{gv;}C$UVR^ZBJlB}&3%VhNE*G$)kuK}iD=7i%L}g!`$2 z7HVx*>#YKk_r$fJJ>X48MHz)o*mJf>+afEQsQoiB`jy%T7e9OH>gruI7!D>CQ|B!F z!iIqGzqpYvQb*aJvg&AXfoE_W5Y?Fa?x|ruQ=?M9`vz|WYrX5U{P_^f>Pk;TN9X4c z*I2Ya?%)xAO;3HcU0ILT+CKf0BmG-ju;|Jo>i2wAvKttf_%X=-mGE1=k(Q?Snhn^m z-?!>@_OYlo0B4HTmSx}+yE!@92n#y@2=o07=u)l>tEU;vcZ}XE6(RWe1kHZyoO8gH zu9JrwNa23NP|6UYM-jgP6>PMrOTMzEeEi9XA69IV)6bh6mIK zRN>`mH6scm5c&Y!hdZ@mn?|ZU{-eub{E`MV@>LQgmRk8!>j~_3w6@M0e21zm4hxwI z`O_JOgL~Hcn(Q&~Y0X|lgW_9F=ChRJ4IP8W00oe(cl3`000g){{w)v!QI4^jEh>AtOa5}SR8EO&_9tmy9K7)P zR%qy&z1ptXS_+ZFURddnz=I4W-KgOX%t?{bPgd>n<&QkZv*W_M$#xC z^QeA@4Eldyffl6XDvC@l7!1CpQ4|XP0S6r>A-43Llu!&vH`#>;pkdrFm_4j)2k`haX@_jLgtc19$+ z7&Xbc9@XTy!7OU%*sZOjD05U@mr!JpUGPf*=BR_gF-aQCMKeg^x4m=p&qSXWa0`FX zoF^B*tc9#2F4$6MO}dDmd0+sZ=s|_r$%`Y6izNRyoA*b|cS;3#NY3FH?*9iL#l8DY z5cXr^Cx6$&6YB1q&%M||$0_yi@DG0$9=+QKPCC*z3MHkllG3Dua$_2b)Q+l7ghT7{ zD6<%bQh^C}r;3J~YYc%2WVR8+%+B7qSokd#?7%C>bz<3PR(FN~wsL zo&6?kn3jT-03Z|VmTQ9Jy(i?yvBf^U7q7w8mIG_cY$)|!PGRb?tEL-q+qBhR(T%7_y)Zh5XQtyFI zBdBf6fXW6yEI@NPeqcNOJo_k!t8Po$pmZ|KuDq=rAab39Xuw%ccM zQEfELv#15s#Q>ftv0g=$c(AJ@P~(UI`T{ML(X;k{-L|G`84~YZOS5aC7Jg*=!Q1(&5y^UEhe7b)$cDQu&FjGzn56KVA2mL|@1JS%P^ya@ zyxU)NdI8W`qnlN?$*d2&2-v^ifVl^u+p3k_xd8lWlW(wPn+Ko|Fc4|vAa`n}lc<`X+pCJY5v|d`Yy$@v$r_ zl$GOf;H<`VReJ=E#xV>+_Y+{yjSg2=_!A-qRg8f3uI%+ib?#5utSFOT7*tljoDSz$ zCW_`K480!iuDW)awxtV{Ww@5NR2s%|U!HCB`XV0AuZ^sd?3^lMEjkVzn4Hk5iyqJ4 z=AD?D6%gFxJBB>r61X&4DCj-QTeXmg{)UBE+{DC?O-<1DW>Qj88-vLr#{iYLb`6fk z=G7_Vv7D+vp;dUOGLd&~)J0dGF3pzcq#E zVA?5RT?dfO*R4S`k;ruV}GMrC!-=kuG0gMABrjs1_cE+21 zEI;K-8;aFMcvRnTB;U*x&u=g@IoLnL@%s%Z79+IkhFoWX~tS@K+G3fT*_pcNxyrK1h%m_5Z&B(7L8(|Cc@Zr_A?v zbf>QYum}BhX8lah#?5!$^Ta?hsX8fTcy3$2fCH{aD$JjotkD-F2a%9oOks7;!kySV z@Jl*j)rV&4q1lII?$kKfUnq-0hQEd&8H%m3Vf=T-*7tx!%CZJ!j80x}NEC+!ein9f zTZ@G1y6oNe*S6|n?_)M+9gv0E7<4_W*FGqwt}Nif`M;Z^L~JG9UI9%!lQYtCDqQ*P z=J{mj>#}{CesyA{A>&#ygF_5vAkF!<=H@jm{h*h(C$>~+1Iqkk~yTW4~?wfe@ z?EuM#;|*OcskuOv=(F;cYZJcZ78 zaM{^foMF&O8mtS$fJ+u4~M?C%`P(T)hp%V~zH@aQzpIu)&n=dzsS2}d-g#%qN4roiY zc5H7aiu30QI2|ENOG`y1B>cj|k?tShbpRfrCypEUQ#>UYja-UISmii|3Kzl5ZlOen z>9v;CctvkCGF7IW05ziD=ouSrnEK3;zf zo*cP3go(h$_id}pZ<(YDIFg%Fhkql|+TGVuuFd0TzT`Vj#Y&=;tNVVc0Wq&(62j%v ztGzn7X&fc# z=!>72)s;c7GeF`xbIIh(8oT8R8&dJUrW!q=lt<>l_|!_`MUV-v&gG6OlSi0#mKDL& zG96>pbpDQ>ApvmtrsHttn-S?Q%hc4=uASxD-QF+SZVUcgJNh;#l$IGC9j)uBH#j&9 z{>yXwdw;FKdasj9>Yl6+1%gA{L%g2oS#GYHXR+DO+-hp;S9`x+&|hah)(t2&r{&o5fNWVM8b$PJ7wlF{fNvch>1~)o0jmmW04&`*LGH#E#sVHQFpOrdbWt{ zksWO9eRW#iFTRVsY!?n`YkGhwH9UNEf7z{7*B{RF2uafPD0%#Jy*~WOc`jFo#?0=1 zEgxm)elJbT|XN@Qi_ zmrxh;#pTXItn%DG70VWchNtVMA*h>tr^&$UZict?v7b3OC!Yulna}}RhJ47>EGuQ2 zChYQ&2UpzH11$GxC284#p7wwz#u9u^q;|XWnCBavDQgwn`D>%$%Zm57K zjSP*GxP}M+vg})2POsFW;r*F=W~zJy@=k<*1TO1{mh02#k-~j59MdPD5>7Z>mLEty zM*_^3A;7%A9geCqCi@oP8gDnrmb%_9?zFBHeCv%hH0;Wa54X&4AD6Q`kIc6X8(lVk zwGE?rwgBURWQzIi1cfy_2)*t@Ef+Q0^Ha2@*xIl-8-j+48~o77CDQ@H(fDcYe2%zB z0n7t%TAk_bA|vaCw1$n@ho7{pE_Onwn0$C6acvSb$==s< ze6{PJlV^hJVYTb}d@#OO)QAmVeP|k({|^X0ejW#@6Rx z3~p>5_!vuoAJM-3)qfaFx=$3xKttvQQJTTdXjWVGnuFfL+vMBpVy*y>PZMyanpmW& ztkP4$oi;mZLU~Vy*&GlK`59lz2tLW`j!xQb$HtCoT6_kT20-p91XxYl=2_;w<)nk$ zQxq`oAdNAd?Aa*GMkiwF!kf6Mfq6acDgiGqt*D%%WHG8J$`~P4i`paeZrMYJxGf@D zJc)pMpyA$qTQPUhslck^I1j z6)MOTWnbNq2{3W12M2x&wRXEUx-rQ!Dh^EMOATNiMCl^as3P@RpW>!_C^^p?{NYN| z@K3Q+EAuk#_KAgbHs0q0kuNR#vP5M?Mc;QU*xmc}-qxs$3#%fSj%FlQxl$^(z+CK4 z@-H>GL{KS7s@Gxr<1iU!=H@xcfTiW0zP&=J`-2$Ev{$bwNjrMFzp+NAdHeIKv7y1f z*>+`Z&8Ou{#rc^3-hNWq07|Papt{>D7PVCHgxK1=RGamRL5)?9sJ2gvAotFra^mS~yO{o7IBH7v?-K3|YmGVJ)hujdiQd!i@Mf={^vx47WR+NS{J;WW2Sz z^DP!RIQ`y0+&KTe4LRCX1^Fw-cJJmbGZ}Hg9MfY*Mk~WPsgSVA?WB5RofPu5$RFiq z)$Iwv(dFkJdKu()58??p+jJQz$xf2$@bJ@FwfEx-`-0eZHiJb%Box5;dyVjG7ezJy zP{O>`h~KRuQICsVblbyi>wso?J@28o78VP)a4%|rBg$))#yYF5p_6 zw%}7&|CVZ6X{tc)G8T@_;u;dBhYkqPM|-PU?q6Wu90ad*2B&|K@|0J)=*Zj20n42X zFiGuJ&B`C7-Cpc#7ubHzS15nAH{l7n9+PVv-Sx(ywvycyXtWTJ2g)bW2ul~c1%Qi} z@S=&h5u*m3>j(0OJ9weIY4#+%iR}z^I%OUe+Y>tA&R7n)JeaVO&U4U{lY`@-u~3tG zQdE1<%TV-mhf3%$DKF)C1`Wf%RDx-@no$*{@@vXS)%>>|8k-P2AFZW=1O7R#?Mx`| zT#4bHAM<&7m;vM9w)E&QR`dd5;e2DtfVd>EUL{4!%~YH`m!ww=>_hCDm~-%{!nS`C z+GZOSIOi@$f4Nx_W1I^x3+bcw9I>D!Mq8t^@s`%0_S1ps9B%Xj4arM02cl2n>j3?H zj3IC6m`hu8z~dhgxkeA_Qw(fd={ou62YjVFRJ17|=Mw*sbE&+kN$?u!JvmwNnxRmF zO);eGY@!PB#G7K4gC{cGuz;Ek5vcPXJ_af?U$-O4hopP|R6Jyu_^J73Fsb_E{+|338Nxt69e~2N9^p|o8)Nd=Su-7*?#9Ul> zXR?HFgQd2Us`5XT398Pl^xm`n{s4WUzO|8L$Zpc8pLG>w2#qaR;q7YqJoSml+S-;d<+OYjtx73j#+L7)RBdSjYw*Dn3or+*-=5L!*>!V^;{Rdy{ z)^6LSim&FPn@G|Y=7P%FHpi%?Tw7a9L6wERd26WEFD~8oCAdr;wN*4KglVH6r6+x9 z>~i(H=T`fo{qh_h4Xzk|VsdS6ZlO!x#XIlpPMSW|s2Rbo`H6vkG=(PRvjlQ*uU;YH z?y^`)eFMaRCMwli@T?w<*z(xYS)uuu@IqAxHvbr_LLpfBRP=zX9~8$$95^d}roBg zZw)_ejrXIrMc%+I?maG|hFLlgBo;o+?OPEgqp}Di;QDa6jMXO_R7q92RlxS(PD@g& z2cwgk2X#h5F*;pI2G1LWLiv-J$tV2T2>%->zlXRHu#XCI*~@!0HcK_%OrBl^Xf3`i z*o83bn9&eoJ3HvHb;lGAV=FJUX^Vk_@xV6}8cpoYDaN-jgNG8jarLel z{@;4Pw}|g=e!IZ#ix$B*xJJKdBB0N_G!Tja|2M;Z?DKCLVP*8)iS%ocJ(^u;8Z2=RftTcm1f&?=>d5G!C0(`bQ`0r<#UL9}|c4##@j(e`tYVd!Ojmk~^h4I^ot2gUpu8)qYr~CP$ z@K7}QnhK{|^0VaVxqni1qdq%%X3;mZPqJk;#&cc5J++!F_;vx${9VzNR=`DEDN)ot zRme&2cIoc^v>2$iw|drVr+m944S24I*ojd7)1VsuyVO76McsDF2FqlrvU~(WrNNF` zM`CC@IkEXX&&0!l1$xm%LQ(ng75nd6r>4emx*LGA>23LimD=S6SM-7eTt(+p_Lly( z_-VZ($Grx)B?c38Cp~E}KwFV1C2zeW=adHnA~8X&+xEwCLKxuKG)=kMgP9~bdu1rh zE4+-(_vBbc6HjN{*NQX&KW3bPz8nOguG#Z^i95kph_oy*O^)=;I{`icUZB7_OPzad0Z(6&YfM8-%r8Zb97S^bLI9~{t#RpLw9t*NK)Lj-6!$}eBVhXv7AVhA)$1P8*cJYHt%@t} zR*hEl4N(YSXnJY8L?DhuQrT7XWHrBs!HvTSKrnVnNapd+&uj7Jp<%TKPKB|Z&li4khAKH54bg z#JCXP7_~8+#H4vG_ARrM`)doMVq|nV)3@?0tc)M0u}tZ6u@cmNuN5f*EcCM1uG=cR zBC-oB*})jmKgRpo@}xt9=JJdI6LdCfS@~soY2VtO7P^_LTCm9OYnMNSy#M8O+VhD!d0L8z6oB$j9J9YTfk3*PiHEx{h1@_an145KC8!SbnN6l^uR5L=7rbuR> zJ$3~vSb9m##LLHdx(L)vpO z>oAWp(GVunxy4$a6F{3kdc|UJivB~796TB2V#={a5}3dOpr-fg(-O`J!>`buI6rLq;J%?; zheFx3^lH2Mu=`g|{eH`M?6P^zvSM70J_@>L24mc>TiH&uw*hBBac)jvsk#!m-XmaQ zB_uwbXv@GP3jkPq^UUUC2L=AB@Vu7!;vHffItp5d@l;p2aHVr!HhXQ0ToNmBCO_Ov z!t=uVAWjfOgVL|3SL13;SzGI2XM3UM#JcQ^Idi^lJ6~oP$x(7m5qC1`kpL1vcaVAg z#;NiP=*Z#GAuj=G@Siooe={S@moIY8g7T)ix*49F0BdP-q!>0pr2x$v7yd&ZgKu`G z&-3F)pQGXFX{Bx6g8#JDc{}D10{m&$=d{i+i+yBVEcjtOc8LpMr22|E*-^sliLgMr z*#(Vs_m+WjhbBA6czcEbSp{CyfS$-7cM3ky22pGkyUK!w>+EyI6XIb~6CdM7i!&{< zJar|g%eF=BG??c46$?Vf)~IR|jEn$(0X`$M?!N|C|JDk=kRfAYzG7oztEsDN>+BTr z`oVWeD(3w5Mp-<>MnCiu+4+DF$!=SmF~~sovp$VVzmq(3*>bVl*Ht{g1*$UC4_H8d zvH)#d8tUc!U;hneexq&(Qb1;4#p>!N=XE?(_DXInc~(bz0mtPGg?H}dda^Z$^AB44 zPwWDa7zRL;I(Ttu?d7< z**@idbXjwG8x95w0sKNErDH!}ZxhJa^L_TMx7Yv=0ME!_+EiFrIK$I7hw+!!<303m zFA-qri%UPSG{inff!O62Tkn$U*q~@Oa^*-Cc|Zb^qCp)u?lXRjmdrmyX(evz11`s- zIbT41_}$b4$O|1u+;_m~Ifj8*5un*mOFsVM$a#CX6dW9?)w91_exjm6%++gRZEyC+V;x_4U}BfL6m&8F|U+c+i%oU}o`JLL>~YJt~XpogHgb zg<7>|W@^Adw<0jyYC=y~P;ie@B%GjojkMoED>pZ{?w~I=zmA&mEaT{Sqj!8Jb4cws zHMYS;+UruNQO$phi%?~;oI5MF)#@8ybAJs7z$W4H!0_$_4KhkX-TGLLAI-hYW zwtdgeyq%6U2T{fIuSv_jr2CQoL)TYFRkdz?tAKQONrQBEiF63k-3`(p4I&NF-AH$L zhqQoncW*j29pB=3@BQvMulFCuW{e$cul4Np%=xQ{fgea?L4>gC|No;f1fEslAmb%I(EO z`N^zd+_OUBdUG_8&-HxuM{tbUd=BdJAsDL-?-ud$P%x;s{w79Jb8zbkX)l-ze66?Ata$7)w>g=`!{IviNlL^g!iG$4zz(Gd`t! z3BepRT+Ngt$&x)dHhG z6yEHSe!IC7D!&rmQ`7#Kza771mRCP8Alc-NjcvO0Sl<5BW7GZ=T%=61?~uV}N0;}= zPPm4O3h?lVCO=bsg#*Jgm5bwX1Ps_rM(^JPn;h)sURkK z<(83(*n1AGp<)Mx!;>G^dYOCeC=EVEOjby*8*lbv*l6<2J(dB3hyh@pIXOEQ>Y6e; zcybU(mb+HUDEDaGzWC9*hQ79b6w?3DSEHv?bBf;NDP@2LU}7X5E2v5?Timgpg}H@< ze^TBM(>}|b@r#qG$~k+(af3xe6ZALVQq12ANNPTQ!@Y~a3U6|BRn}|`q6k+TBjB=y z!R{i96!i9zxphVO0Wt|klR|*cWeh+N`{J!%V|4|`VKZ0cPjbHJw111IA+SWStm_#z zRXjtIfR;2}x)4OI*h}JG_i124rVuOT2ERnpx;(Q3l*dax?O z3YSgg%I(R#WE3c;_tMg0(SghG5>SF?UkQKn+SJt4Gmgvy0YExDE-BcbYF-?k6ooXb}5J zZvf=J?|aH+2fdsuM!`q8k&rM~wC{yCqZyW{=CXZ{8t{S8Ge4dIBfV= z!3ue)j_S6}+XcTGF=fl=ztU%Wf49vkJo&%MQDRzjz?5lKV*RDP-TH7ZNm!F$z$XFC zVI6(l>9Kw=I{(~qiTPnA=~ivA`-f98V1l}x1x!$+i$N4oIbHtaKTrgXp~yQk>0?OZ zG2X8F0wnI?Bjti*j91S!Y>aZd&|dP7x1p5O$Ovys&LJQk>_8Xltxr=483mX6E@jH@ z2sieAXc1`RwQ;`Bg4uyN$zewPZ*9+d*}f3@hwammQqdwn!ftqXsml4OW0zSMikf~#Ji>V5r@Xst8v+G1}S%v=LLP&Fm1PTc9V2}r@)zYo861=~ft zxu?5Ex2>0J`HBnJxL`|b8d8s9uI3jHa4_?1+$LUi{;A07hkN!*3#JEV87?D5SX z@4ajHml)e0^+Cm64?H-`9x!<97MXqMGfmsWc!nob!Y4ng>0>q3lbcf-fRS{krjzBW z(CkFzWprtelMb{>{m90#`{_(5b~5YihsqoAG)a_sxQ5VBm{X_WKhPu%1WV<+Vxq`p zqqp#<0Rl;=KX0jjdpqPylQJri6DBrt+86J`zHcT-HuYgQ5CVe=)Z7d&`?kwDYIz$S z`J*?`ZSCh?yWDPbZv7FNsG^xH#2v&lN?M;FQAm^@~QBrCtSnQKYhWQ zD*kR4V|sDz`?@Wn%cRUyK@xPe_rRfJHUkT=+8@iTe40vO#7OH1+m7b*Sf>)4KfVAA zu9?5k<7zFFbiro;*~Z9sw!>UBJI9kWNUEeo23w6RXGmRpuA9pvo8wW6(!$k8k!jO5GFONU#`O^s5Mvs!4dGy|~qv z{hga({#DduRG2Qp{_PNvT^Ng8mxR_N0w?=TBNHuYTWw?ro2AxhsSBww z+2=I`$DhNWtiW|hWohLyC{;s^nkD(hE6(fll?O3@(ji9TzS5^r3Nw3cAYxD^iZeQ& zrLG3T>gC$YevRL_xXk>@z!k`&a@fv&%QUuz1wg(7{ceH&?D1zGUtF=}#(Q zWC&nwB6rj9cQ|+y-{D8fsmP5!@Y57$kd8+D0c2xAk+6x6{(e0+a`IQ@f02#oW9mHw zxtSbg8ThCA^UddH5pwm{mtL;6lox2S0V4B-e^uCkEvxZ!;vev>RedU8TF3~Tn_B}Z zS3xGwTKuu0CH5(i2Fy(A&Q`7_{{$U=BN4Wm&$lZq0gnGM1@^Dkf1c*x;SlQM3JDLb z2YQmUwW#}y|FAp(p6;J1?J)Ur8I8qc zfEvUMVF zZGc#Q{as3(GyN`0(<)v;<3FJ23Sf9{6d{*$)-ITVzd~;fO)IpG-)F>e+>Uzc21F#>8rn0> z9xRZ&y-!va4*7|RJT3l&LzahF$;%T(_<;`ys0_y!c**7dr7JbrR2Ih?zl_@#=bhGf z8%qA0wEz}NWGw)W3~ssw_@@ClqI-p5AH%^y1|<#wzZ*el0$pINq(;3LnQR)5FR(#( zw%h8bnX30ic>#mF0O|_Qs`i->&dJG1?){aP>?xWN(Ge_ai3$MQBZzY?(D|yLR9yFM z0haAqtJKY!KpVAzQs%GqD_;0HXJ9iL3?x;2s7IBCre;+DHlCAG1uR)vSsq%TNWv(R zm#>#>z{)-cs-0J^y9t2yiLoplwp#N|Ib#3PrS z!dyC0afmZXgehC5ZoF@7kS?c6jsTJ*JZfQR? z8Mrl)rbqgI9>~AD(E0{$z@+T@IG?6nFPxFU+!q=sXCc<>6q{3`30&lYa(GWqG3~LL zY$j%1#&gTz=%fpJOM?7)sc2>(>E?(p_9M+qcGR~fmiWfF#I0IVaOu_ncP=?)C%?07 zisjpLj8JlJK~!I(kt&;$$hI(7zoM0b3sWolob)Xljk2&Is!_=(YqA;1b@0mfk(QqF zeqSn-EV=@6Ttmt9s7)=S2lC{54im>RWiv+EaW1bjMPI$9+ki4)t6S>WI~BA(2C&%c~bX_SC@3&sIzJ3;&{m47i z$l+yiZ`5Jr7Oaeqwj6F&TSNHy3V)Jmc)0lEOV3uZBJhYVW&buG2U13X_SI+ER3vpd zY*kfd=Py_)at!MEq9X<$EYw?7!iRO!)^6?2M}lLdId^kUsCCCdc!O@R-HOtj7M;eU z<*atHu$HQ$+8wgS$|`4#RNyA~=G|YmLA!2lTUe0ng<%jASfsbN8rQLk51Kb0LSg6} z;4M_d;oJAya3TOq*Yvg(XC_!pc%XJwvotlzcplGKc0^XKtgJqB+X)37D;QuKgsRXg zFCUo2R}@8<;vKkI9Fnl&DOS`U7({ntJx~F^r+pCt$+YtlNp2gn$Io=jpvTu|+rz%c?L-ORWqaVZY^%L^oI#nU z?Av$c@(s2m^n=4d16l?mnPr*6ABS&z^>86vuZ9}C{|n7MPC5jJpWEY%^j$C&DNdY1 zR`gochweg zLO&j}&G_7i0qyHwTDrWF{N}Z|WL|;N(z%lcFM42vq{^#|}GQh0pG|NJ+ z)tJ}A-rdwI4y()!x7lncGx*sBZ{3Yz$2z@)nVC`WJDs-3Cky5q_+cpn^jsRtLeKV5 zp>BOo`Aqu?tSAwLzX&g-X~_|9ppiENrsMR-cN4NMTux(g8+00KS#~W)C*90$-(7CY z?`*u0nskxTp5d*TrBV@0577pw1VEk>L}@TbJ}neK6H?2EtNKH&al`?e{uB7h=x6Er zera5XI`UJUn0M#U3hzfIBIu@gWyh)zr%bdw&)F~ zj*qq(eV$34A1kw@$nD=d%6xnLnaoafB=sW_10`}hIS+YKRpwL4Fi-g9#?DTDGrB($ zQ^(3K&k~2}mvVv2kEBs*$BT3W-AyFpK~CTJN_eQ@)3l@}qsS}2{m}SPt>@)QhdeAa zY_acIse&3xD0%j!5gfQu>mCQF7Bvc6)EnE;Dsu$wyq{xmjVXZHjpv&IH^Su?^wuov zaAa6~(IPmka%ZdURZnjzXc~!a_6(XNv?6Jqm3)G7s__inkxSi#a^VN%MAO3ycz3E( zI03s`gcvVw`XTCJEVd;RK79tJAjH$gKj{#4stzimF&pq%^>N1u+LJBa&(Xe zDKWsv#*C3Ksf@4#6dYDq&O1l}Ndt4^pIc7{Y0w@RqWHbKFs;t-kz3(I9CMtNAU|b( z&4Brur6ei>Neu^Q@Df!N1x4PCQR-L%dXL@@OX59MoS#iP_x3FBQ8db{#z<4I_Ms8k zl_%@ZpEF!dvEtQE2Sij?*CS{tr;W^n<4NoGQAkziX40n&5Juv-#gpK-;|$ML7pilI za?6HzCyMl1A>fG{3WECG%W4x7^)0Zq91p)VKtP|u98H2s?YjlveWsRv%cVx5ACgyW z*`ur$(868Yqc(-i3%VbB@M(ZfPzW}x_&MQa{1Q@{u4Yb)_xkA-d=0Hrp1VKR&Xf73 zZx3ckz)3N0Ry_m$w9hu4yhRxjtM^er3QAOFHx>9v84le3GVokmP5n)$aof2d*5@7$ zWtNyK6~At*=`fVoHN8fdog?p$;%CTz>l7m2Wl?sphp(?)=O1Adf(@Fdc`I5@A z^1Yh{@-(B5FQ$At%~mbZ*B2!i#0Z_njEYL_E~Hih>|g{Hc(r3l70$|F#*?3lP4^gs zRj{tVdZj_e9Nr%mqy^|~n6!t2gycTEih?|s#N`l&6ejB!P(iRthrM{>qfg8VQZ*I@ z`@KXhN@O<0Nh@%4Un*>t^AV9_a1{IW!oD&_ciO*xh5Q2r#$X#7o-W51N8VY4k@{&H z-tCY|@pWBd@0C%4Dxf)x!sj!UEAvPScA7G4;Ih9nO3xwu474Qs2esT{om9 zwi&ZT{x~7v$RR#q*d^NUJ3DpYLUo_PYk^cC$@txlnFfJsX zOn&#R_}nB$-z?XD?RGeB$yeF(mGAVi_w@@UBUzTvd5&vMJvV+t=B)~OvB?lCad;0a zV_mAZi*wpKv}I*PC6-W??AjwGw~<X`g+0;}EoipUf`S|#Hr@MKe_NVE*oh!T=`;==Y@!_hm+vR%5a*4?4kiCHP@kJenQPc(;-KFZ3lrwil!xVS^@HL z`k<2|u3qG&mXkBA?@qmP+Ra8UvexrPG4#>PyWpqUW4K24ZluxA1nC(c!A6=`B^5$* zW0O#ZDum`~w zz6H$Zb7fU^PxW;PX3}5RM`se!44)#oOP5t&Eh!U12&g4`oN7MQDr4r6b#iCG`z0A_ zpAVQNEmfs#$Mo#z?6br$2qeCnl`PHE9x*!&Uy6pbvg?8TIwn>dp!jPIeVzx(QiI;* zP?*9NQF*65x=zLST>v=4gl)g1G>GQPEMncjaoTd2kX0tC%itHh?wy2OjPUo0XuBDt zl8g)030b#8YM6BYxLU0dDydyY6b6s%^0p)5$c82rE&sj3WM@C6$wLqU>0&2$FkKMq zVdBIsg!;5`rVb8ayy(%0zp!inN<$tNPGz2w7U2=1@jOQYfz*rM+IuC=h1#(5*Zb|J z0^Tb+juNYwp2LQv*%qEFb%FR1%}+w;qxAo{Nvu;u(h;8w9>TCYz0y58qjoHxT|4^4 zL=#3tfYrsJc}-%L$!BA;!1kIj3Fhg5Yn3W)aIyAN@GyyALAg(ED=WBL`}SJG7piKQ zROJX%8DOYyqDIxBAA(%Mq$g@?bR=cY;uR?p5MT)p5d&?`v;=P;gI~KWD`o6itr^Uu zrna!eMjOM~R%hm`PKxX&@x=%;Kb}$pc%8&yWX6$(cd5E3sK;?MjV%qU;_j)34yWOa`)}{`xobK>j=aY~ zy3@8B&J=hgBkFAL6XT#S!&vuQ6Fr?ZO?9RnkA~V*%~*CilZm|i#8m?&Gf-qwM?--b zaBTfmVIS^3*uz)=E7h-=rd!>J*?g(C8+Z@5$(_N*CY2I>#P)qtO}6Fexh3uza=U$} z858Fj6u7P?y_EKQbsEP{=*L;8gU%-5g*jYessvHXpv4Ddjfr3XKoTPxfVjf9$>%-t zr)H~6AL~Uwp;`S5L`+Sd(OjWStM8 za835C$r#^$BY+J=uNv~#m!tlM1#)8u8El%TOm$dB5Wd9g^51uABXi@ zOWF|y$REVtAi1mI@S4^kDA2&qaNEB)$2nYum*X1M8Uiu%6!KyGpO5o#A?ql#7|Zm_ zmA|Ch%a-gc_#yjZCjrYi_<^qK;s3&<$j|Ywfuhq8Lt!CJ^r~e#;DUH4=s*cGGqayo zO`e+X8MWj3D{KR6I$tJG9Doo5nX1t-?WA`a2{DboK3gUJ^Bw>5paJ%9+bLX@(%{Jq-*+6`jct>e{(XY~cH}6m0A$U~YM<1#^7IvOOUivdoF}_OCKxvlC*&pZeyWy2 zF-=|BW39SBj{C~hP*K68-fGe+>std%rstI0nl9CBYH`}<7zFQg(BdhMu91lw1AG(O zn>YG$?Z6(PN}X;8o;l|)9ZfkNqob9C$+;sWM-GA9?GHN@>OsAYQv|g1>pCjQWA7# zvXDS~bNhybj}OR#dpy6s;Ii)Y_hlf&E;V0C4PonG!YH9Q>6lHa0DdzX?WW5}ZU^iJ zzNY?u&GF+Hp(@8Y!Dwy=lSA=2haLLZ&*jca>ti2oHb#?*TAz0BVRS2OgHr);?-m)S zr28>ZKyi6*Tw9LS_XUSxua?i3i?d=vqj0IGLubQW=Zr@*T-?Hi(_8#BBY+{bzd6I) zTh1>3hR-R~8B~%`ay-UY+5f3Rbj!a$ZSO{GLYH?P)YU{lDgO|7ST9``l4=lou@tvbu*O;Ij5l zQlpcrvqUA=;tfQSWJu{}ypQH@D&%p(5K{n{y4`0jHwgSJLzvVd)O`@Pm+ zECU4qBN_n-JJB2;Eaj8i7dv=voKv}gtV;Rbf)buEgsfE=`;vFtvlB_Q}G5(szL zduXK@Es6^z|B%dfCKf2m^9JDeO(Y&YTnXphK2I>f&N_?eTwB(Z+_=Q@ADzkT4k(>i z&wsmV-5MWLj4tnusWR=N8&Du`$0$)$=RkZINDurUC!g$HX!hO4R%DZeJ`n{PMa*g> zN+T{o31lY4Sxi+Htjc4FI*W8^@>FdB>wWRqJQ}p(!ndIe#*9l~2;MtbX6&t&DviOz zG|f=4pf4|+E=Sln6y+`t<{Z;ejO8azy={Ubq$Whw`JLR`Be3E|Qn_h0>+Xvefb3UU zYL${8+2eataFJ8AOzpxDt$!J|)f0Gznn(AP*A+DFDpps_0>qM#nU1E%0Q9K&_k5it z(;6`9pvOb4YOMd1iF&O?X-IiiemN$CmV{~okCVkfN}pyz#Ftl4pZY=z?YLxNL~Xfb zH{h`P%UzaR$(fCdxJe5!sGhSDI!ZDHTk)J)Ws8jhCFJ^?PK6qY$T2p(B`r7RLqy9zp?m% zVxCF&Z7`m`G(X%wFmT8Qy>EcEwrHruHT0k{q1fC~ZNe-UOqZ@Xed@ZV*uhLmTU#e*M;_{Osxi6XP;dP zX_KLWm^K#IK{jp1@s^1jdMhaZ&)C!oYAiI;g?S(F2EX_SZ#M3vNEazlN6Bbn0R%pF zp<;8qQn7}5o=jTV#10U7B9*{ET_N%nDCkEX@aMa}2y8%;V zbDm5ta0)fc8_`@@;OP8-jJtxo^t8Bnr({r}b)&=1_#8mu>y(=s4fe+}$9%iJg|9^# zi&lyPyarXz)gHXmeE3UykGE@wV!s!4wjzXeR5$6xP4r+(Rfb7YbO zl2UGsqV}~@^-~oFH8F*b+1oj3%=nTXc*!;p=2ixB0Bl>jN|~p0s|&}t+IeQ`AU`h` zT}c=d&mU?6VxL4gE2_DwGz)e72RYu{ zIC*ub*zA+jx>D!N_-pUS!i7-iJ*~#sniUz(>g|XRyrp|f0u@< zoj}Zzn{B|A@~gbW!@1hMwli`O0~z=3h(E4f;ytugJn^yAy9;v#>0favV>x4fMYv9DMSSkZ^dG-`nW_I(}-tJuYX0lk=SI z3i>}E z8D4Mr{(L|URIBoeQ>iIQG!#zsrj(0+3~iFofCLPcXa`>kf0E<4smT2K)TINNWKpIW zM0I~$Nnaq*z5$m8Qg}4vZvuzk%M$q8Q4OrjvxD>Q>Hi_t{)&3C+f!Mp^#1*Oayq)7 zc;vV&Mv<7bDw5p+U~5twvszO`F9nxBTQuplFOd3k#v0<0AKom^09wX9Wcm!^^BI0qB5CAR6O_yo!)Y%bT?K56;H@6X8#du|SfCvcoJ)IvUO5pYW74n?` z`SaoN@^FD+gGg-kyKtcKuN`4XMt^4RAZ)Rkx1}No_W=k`40)d+VZwki8#9@x|$fh;*-X0lk~ZHNWB2kEO*Z* zzISUfn`oAeY}v%*?>bhFy)~fw1%ckcF>%@vkR_>HtP&-O?mbp2ofi<%+M>{Zwc<$& z>|xXTkB^U44m;M=Y878%X;oDM)t|IlaEPC*7bv#|<11e;JT!2HVA9i2$)(2v8>K`N zOPyoI=lM#*d@bdW4c1xS~`t891^x zd%B@e`5p?0zQ|~0aQl5mv#3s1+O#j-sp-N45b;SVLKeh`%5~dG7V2yg*aj_X?6>kM zj0aP{#BvU04lJBi^#>aoBOQYR_LjBq(tC5W24(H|J)Lo)wF_K`! z=dd29YdtnY+Q!AWBCcq011I0#%*Z^n`<*M4X>p5(t&g~V5tCQhL%Ghd&F9r>a+G|0 zaBemlP8Ln~s9NW*&dahq?hcn$>OvKJ{Zq*gI9rl>t5B}RhE(e9*0o-<-ruJ}o6u+L zteO#Ue(3@g)u$cj+Y4OtUlo;JxbKMb7`U1lw3nw&lUR)7fquMycZ_W7W+y$)G{Z7C zMoerudQ{*}i^E2P3f(KMU`j*AlgnvPZU!I2QF4dX>51(T8<)?ep!I{p&diH?{^pk0$m;d%$2x97D^Yxj>kjlIx4k4Z2&5GCEl+Fv0w7SD(lnW>q zrY5h4WHzh$A8#^9{S)qwOJ&dY{W5aZt0~8;X96`|bU}2o*6y1N!5SzmH90Buc{@+q zXS>G-Yhs7w-%X+y9teeE*ngw8x znmVqzX{)(&ZZ`GH`KKP?QQmao%tj|W<2Px}PM+Jiz>I>eD(fXTLZIuWGoQ_DyU)_e z(dI7~E?`w_58tT(vapqP{4(rK}g3WqOZLUxE4_5;U+K76MZmRw$Q)m7h8U#G{n*|ptq-cwt%No&`?kcGK9h?<&|*L5bh5Y zt#Bhc`+$vx1|l|eEREHG{sO2DVM=D(~aH1)!D7(;U+MEMC!xE6gM1Th!n{I zkLP`$<-h&rQQp2Rt5O8NJzXlX1PcbuY1bD<-I``x_Bl#_xcIG85)sdoQeJpyv)eF1 z8rUv0@7+ypzq`c8y#d2?b{91=lC60^9nK)) z$M5WlwbUsyG`ul5Bac7qg4$)|1Z}TB?0j+SBz!o$y5)2oRGJcOI^v{NDS5kVNg}0( z*u`!+-3y9b0;lL%rcOmL?VRh6>syMD75hh>rf^xh$$`bMtj*^UAOP>k%-+@VKums< zrz#=q=hW2)hhY#Vi{*C4;MaL+9oHKQAwtI1NsadQEE!tUVj??9AMWspINukLfiHVG zN;QXcSxrEm=Lk)QE5lWe1MdqjCSyVhBWldXw2+OwZuh8o(r$b$TLIGwi}&&;@FI^! zZ_&8{f?BFmU}ok!!a&nWAj3&W;I=@nByTdg>CGjFygI>@!o=%5*@q9Tod%W_7tBY% z&(H1jlOvsN9E~zs;vp`ygT#Hdo_;Zq0G+t%UiT4k4UQ(}1B@~g|NdBYgkDaUjCSa^ zkeXciZ4=vuq-1zv@yml(6B>#AU7ZMsc^OP8!|B7|uVp+WProk_7YdixL1deASI!J* z7E)!t%ic`P;A<~{Ny+~_4mh`Pc_yLU)_E^d>l+*%>Qtap|IVN zIDgsxPHiN6ZCecCB%8vuMAdiY)h)8YJ+k^vy-6a&5DPHJWBsw^qUA7#G zvn5-2g41kYS>H#&a_XamN9KuvLDO#|E(~eVQ;VIYCswmWL(M8-6th&y)pstnF-IwT z=kcVAR(A#|oVLpni9d}j-tfSwXkP2WARt5osqirALcV^Tt()Ditoa`fj<=+zQWpcpD4c?PaY?)quVB#U zK~u$`@Sh~8|FRJ+qA}^VIX==*9v!1N>46qh!+KYR;K_dW#?hIxwx;tqC8v*9^!C8K z&7;cvwno;bKxV@T3as#cRR1n6!%U;p;=-($>Z$o|%9k(kwf>Puj5_t&K{=}P7Y%Hfi(g;~m<~A}5 zxZcBPGWoi7_e8GmgL96`&ZXkg6{hv1F8~iscYCzzLR(pAZ}k*VIv>8qGV)@y3mS2u zJebH&@c2GGL%?ZWq5}wlyF^1=T|N1`J0P#}q~ug5UX!UgoG-e1H=hPkWA41XqAfwM z)9ZXsh+v4NigeWhR73+6-+hf&JLIM`k}b*KeSijIBoz2%?`C>t=@74`7o5aiJYBHz zXH4)&Ej3NSyIFJ%G1gCIW2x=ZQ10ZG_*oS8ROq{@S1GCAoR8u%RIBY*QYK#SGgjHI z8Khk?g5|;!2!8`0KG0;pK>pY{Rtu^*pVMyL(z*R8Jbra>IrbBI&1wc9-6?3I=wHmu zb>hMJBA|S&h$qx4#@3j}f(29O(Tk^@1K5{xil?DfcD8uLT(%)_X<`2b{@Y5vxN<0A z4kV(J@P2enrBj^+i~?~V5M zU9=!|cHKHCs~RAuuz;Oi)k}${O4JoQd1GjIidGI5_pJESO2;RZwcy09Vx4S8QX7@# ztiUhAs5zUwCSXKRKtHh$WD*CS8f=zYHP;(Fi+hO>#~4Y`fu7Rz7V za5T%y3+;hu3g@|(zVNrCYWW65wx0-PK=KT9noHaz?LOuxBjHzWrlfzBguvz3Yb`fm zo<9Xynt>;4-Udm8rPM^K>bgoIV~D7R)*@K)?AY#jTF&=P33}e@UPcJ~0ACw|LOYR; z0~6tP4%RjD9k{1GWu&{;XUEh@)F-I01WV+JOV+bLUTb8Y-<9#k!}K+mD}M_a=58ip zh@d^+#aYD#+9Ae3z(1KzvXn-o>ICG}?>%Af;HpSiPD!t4#p&I=;oC6yh4~b-{WSi` zE={c*)A9~&3Qv4Gp!uBl37tDXG}O$G#eim}@y-6D?`Cbhu;5>yLx=V&YQ?;k=8di$ zzMf&xX!ozgETU9+B)+s`Fl#L{zOM+MpZ0_3<-RATZuD38t*OuR8a2QKh1+a4<|z~U z?&N9iMc0+&OFMABL{it|%ixAL-ZGxdvq=dGrr~y_6O9D>j~i_C!){tSI(0%!CEQyf zDaA~i&X{jf985}iDW5h5`IrVB4^xzDaer1gw-zwSNz2PA_0#o$=EMWZlY+S9o^4P5g&0`;!AizzO%f_ zKaSCQev04o;0=o{4{6EkiQj*m%U1e!4aZ-YND>B;%zw6`zKcz?#ZW!^OAVw4ej#;tsY}KSbROz+f|2m z$0*z<&BMoWQaK|qDY_3a_#y;<^qeBcjB zz3#b-7(4bm0AssG{M4$ZVNtIvj6i%0t7xXAN41+ zzoB;I?5|MeWNUJ$k!zI5d0Q=-|SDd;A%79V81jQSl%ROi>Ax5fIi%k? z-~PA=%Wb>&^>PIUIVRaP)a zP5Mvo=E(sCrNBUco%ok2*yi4k2tcOa{b?&!j$bjl+f^g!*4A|#@gXuoE6G#~e2S5R zSg@2Mc_!>N^>@b+qo|2CSooYT5fb}Iq)=8fu3a1fweHUz#&kE6S%$xzN{O$K@8e)D z?!H^y_U~^oG6>hx&pgsJ!Vj$Re*O4}kaSGqATErya}QBZSGRN3Y@MP(X@H)};6ML^ z(o(;(zRS4FJH_#%FCUb#n-P@eql_rUu2$xysZ88_vZysRDyRvqaP{L;4g`l!ak$0P zYN@o7OIV9PHGw(WeKxGiS2MqWcMb0~-4LdS*NM<lmqzCfvf2_DSr`qU+l zFh|ip{^-DYJn1d27DCOq5d-7nSI1UmaG6u5wgAStiy-8s9Ezu(5O=xQm8aHeX~3Y@ zP_hZdq}{d?w@wM#w;K-824);IQIL>)=Q)ka?ju)7%IKXEEtGwB%i0Z(c>Mmkp31D6 z*j7Nko|kzj6(zeLr(iYs6lQk;L`Z8_Jo?+Mt)T+oDEl4qUGH~fu48YZim;JqrH06l zB@argckeR&0_JbY(6@o%>pBFME)7;7KL^);W5GVxq^pajO*Avg85j!!bIPtT-FK<& zAn#J7huqrsHm`>OcAkOjazB4*m(8UeG?}dy42x=u1(&0&WDRponPp6rAHaINYpyJ2 z!Nu~1jAzg;63x#0kRnxYz4YS~$r~Y&_bvI2BJ>1Sb4?$5AFr!TZo&P>n+J{OS3Or< z{-G@1KEsaS^_~|u>qGKaaT&qCZ`N;+d@{U~$jjTuB%7|TCEPBLOo?z3`AvP;EG}AV zT=$%CSuTzetV&8dx{pmE0tZ>0lKZ!Y>S}3G+5y{{`PHrFc=0IJ?epY{wBcg_^A%^2ipKGl@gGLhSiaQ&&ETQAJPOZATT z^HIFm2MwteF9=|}dy1h|wTf2RJjAuE$Vn!g6OHv{W z@{06DoJW_Ra`Zay6|x$#Z>D+E9;twM?|ol;S!fFPtE1LvdUp=)Luauf6;F+E05u!@A9jaR4@a;4%vt`D;%@$MiGBr<0^f)+n8y@=$!N|U2pAz^(OO#%t%s()TV z4M3by=s;M5=vzvm&0*cb=@ByQgwOH#!(J`mUqJ)>dxC)u|Ja~R~ciL>TG%Q zjLR<06DD;Aa!uN_qJLJx98tV@DI@30x*q9(et-YzJ;{3CH(W9an;#TAH4$!tV`>D1 zJdPqd?i*IzK(Z&Ev8YZb1Fc4n_QaW%4A8#p8z=VR+T@{CF4 zIL^+z^6BS-;le6M3WK1y z&{{Ldp?N(DH6w`(9Zf>Sk1FjBJB;JU+K0T3pLw_34ie=x*xqXc3aS{HBo>WgW&IiB z>4TfI!J$&Zu9$Jzie&rG+O6g5*d^)8-}ajfiV0dAl}DCfqU>X^2PYw@a)!HV$L$~S z=CI9DMg-k8~T@v?fQ>MYfuK!#{-J<>ytIF zrt@*gZ9fl0H@;CURxWN*D%6s+9epyt7g2Bi?5jrzRWe#(wIFMrC6WxJh7=a+!>5H} zI2-FIVad^|l*QkILA&O5zS=Sw%_}Wt69qQC676lRN9NG~W{enLs(o3tw7MP^Xc{6z z`k_r^jE9{fu(uzzBHh|FDgof>VB%qFUBSJ{(+}oPV!Q*^R-%JFX<2Z?MZ=g;p(Ap?BZs>lyE7 zd+UGda*IVaY%-`;_emW}uwZ2bY44TqSEJ%4?#?*Xya?OIRtNZ4y9NU8Dq3Yek-Mdb z0YJtRKA1A235>4ZVZiPF*QSByGo$M+I^TAib#Z4DS)$RZLc=F7>!rAlfa|>OS02l# z+I!I3P^&@Gy0B%XowkJfe)i`uFGds<&=G%9+1x?PR!l!y&^2;(JN_qU~-oi+4e*_{v>P3jx)d zn?`q~A=Qzj;j8H4oAi%k+Kpg*4)gC8`_qRCO-hogk~qHwuqP-m?@EZ+uBCDBx;dj+ zjNy!}Vl_O4SVhhXsP$jId}*&0Cbj<9E~bu9Yk5^={qhJb8iEnOIiRhHPa^;H(syer zVPW^BKLj*#;hxPh&%=bAU~ayU;EfT~oVzI@e@-6;H{WKHX39%*8l|`Sc|(UlBux}q z-vu$02wCEcAsm~S?h?1NtN#BZ?k#}oYPN1sAdq0eA;BS7@Zb=fAi;yn#@*dDxCM77 zxVr{-cXxMphqp++bME=he@?wy@71kK?b@|AWUrOg-K)Fjm}8D1OkxG9Hn~-Bk-XhK zct?&?*WP0RL}&bB8c(O3R-A2D(YaK4M}aT_H(_{**@YT1^I#Kg9q{f50zJd|`(IRdCPlEIZGgi4@We}PvV3Ag1N^)z8B&Ynp;eY zVSEC~ud{*>&oNMxu}!MGW^TV^pGvtwwgUV1@>6TSluDLM&No_GBHXpVL^4iJpjW)0fX)pk z9aQ_)CM9BO=$+STp{TzTe3e{@Z;ZSti;<~VfMEP_;Kn#`WBAw!i-Epj=2zCmQ)x3s zIzVHhRSzx=4sk2Y{&f9xf~u+qSgdZ&oBJ9+$F>Uz11^E8#j%;Fx!i}c*mo{Bc~4r{ zQw}$0LBqBaNp)}&Us?=rOKLT*U={ecj4rFB(S2mxQmNacIggvn5fM{^sunoPT6`awS`0j(a0xaoo|Nqbp?K7C>#_JJCY}&Sz-%CG=TOQ zwO+8r{DpRhfWTtN)Lc&jehiNyL+>oxHTJbkwzXPRFc&?u%#_Netr_<9%fz>0Nj&0q ztMg{nU~`w~?i1);K^oT=Gtj{Fo?orbm`47)Bsm9q%P9`|=A6ycu}m+n*OCv`+^%T_ zR~aQ;?4;Qq<8_21^^J-CGjFsl4}8cZM7ymztE!}$QuTY?aZ_Z}Rjeyl0_6fSvO6d+ zQ9F~tItZv7-Wq>k5Fa$s)uDC-SQbu%ru)i5@{JwQ@>+w(eaFWoP31XHWTz&y9%duS6YK8iQOI z_Co-rBWuSy%bS%4RtSbS+4kS`0{|F|SQP@wWn!D21MP}+f0k<9>Zk%$OP3i<YwBYgiwmFP{I=W)cljxqzv7ODu%t^y84YuYq^<2&XheobQ&f z%&6992Y>>Pto5HLK^Rz2seCXuoM)weHn}a|euYX@=+LL4Ms=p_k`YJ*csg1D(}0+5Z-3 z|7k=5j}D%e{MZfw%G2xXX?1q7Xo_gXW;J+joF?dlqk@)7RuOmw==uL*qz%IZ6t6+> z=tut-5bOO5hy|}7VESJ`EXWrlZt%1Uk^j)WFS8K*3;WBrqJ#N=_S^54fI#{h4rbf+ zs}J|hgI013%)h~kfA)GWPvf^HhbKsOs{aje&f~;C7t;83PCm*1P39GV-D&J7{Wk=W z89ekUKS4i3d;hOH{7sbtj@P_eM%-gX`6sAVWfQf~Hv@2PwWzAkxn&Lwn@X1D#~eBz zC%dlu1e}#>B1vM~;;m<{9_{^cZWgz$MM$J?8S;kyp#`At&8d378QFO~r`z~!zi);p z+uZo=H&v(Q6->wD(VoYfcG7=?4v8fou!r!b-}+ggVZXybCvlrD*~wf!R;ErIN}YJp zWJ-xHpFQtV?0EV6 z6l(RtJ$ogjS1&yaTp3`jwM4f4{lYxbL`Xlq30R?d{AlN?z8CxXiEOGs**bI_e!bOw z);p9;01tZ|_7C z)LUGdgnwcyzFLL&V*ZusxQ#SEs`tqBdOWh@1*o(73#d!~3#bbsHDOn*7Xs8tH%4>( zg8%kxdSnU^$OiB<04?q;YXpj!Q0Rw3-P=h<-fL21E8YW z=qJy=GeHRmp;fA(KX03>jf>YJBWD+So5%}~){w`SU%-qC6TD7t)RwcMHqPb8wB3~e zuh|@WpcnP%K}E8X@$)8=BqB4P=gy=^uJ2rQIt(7$>WiT4}7 zL%IIn!0(=BT;&b{InHuGRviYhCO*Ll$CCMvcPxQ2M7R+J@7J#em4<6r0M8At&Z?97 z@dd*(nKi;54Ka2+4L=4FqA2LPU3JIJ9oE}J{Z+=($J`k*2$k!+3jh)bxqZaD0B8W> z?JNl8z3Xci3gP;SiubYP^3EyE6Hi*Nd0c-3aA!rxB4cFm>#_jI{RFMhY4~-A#QE`E z1V@08*xmNRvNu58?+gMb-E}p$T)w`pQ3{VAXl*6Dy8CwQbtRd-&{5r(o~`6dxzDz) zF=BLA=c z*NoSSFhYP{ba+VeuiZq2Ks3hev&x|0!9M{oP(vtX}qWP zS{OTb6HfKu2YKnmcr_r(MVPJHH*+kbCTbeDb=r2l{BD9B9AY|ay+srDooD_Ivtpad zK?D2%pr(yI4ccpg8ab0Maa=gh*x3sB3*T5Gov9?>@ANfTtfmjBYb5%5#>Ub%;h?LY z`C;idHsKiwSW>xk4b-97>_#}CRIuW~Rsb_e2P!x;Z|Ykdis1>v4-T;a2vqGP=X80? z|19XjIhUdXRDDU;s9E2pbqk#}*Zf+lIce73HhU?+xj1C`re=E0U1ya;n{=|Tf@%AHwG&cUMD9DI(%7jJu>;h zt{oPdE#14@H`+S;b$~)2tA`t5YJ$h7`_yu$K_}c$XLkNq3-hB2@BP9>)*+%tA^?`G za4by`!rU+0dH}N#zDDg@(?8#Gx5f8@tiL`OkKx!6TIg(_)1NNp{M9fr@@=+sv^pCK zB5ZgT3KFs}FxnV$imiCz=NF;M$%E01YTA2(w|IM_(}(jl>QzJ{_{#^g=aXM2xjnUw zrYgfZlult(lXl0eP3H~GD+-)|V){L>Yd}VQrjtC%^6G$X@y-41Y!jdNxi;sUW1G8F zhwY)Ff}mm-V`mGR(UN+sqAq`i5a-K1TZrne!)5nxeu?jrHr-32Fd4Rb0KG6`fB<}E zHeDkw?OL>wU?Ina^3!eGq0$wwfV5i6ENR41QD>cIwbyV{3l7OJoeH<#zua^g`jn24 z-L;wk16L7@RsUJPcB|EXaPIO>scXZ!bFz*m(-z(LqzF_k>_gTUz^+%uCK2@o*ub9L zAjLgxi{&X-m7`I}wrujQs{L>~pH}bMSuQ0^-Lo1U8Mj-&n$s^uz(5klCh zq~b=L6oGy^(C-XhsT~ojJ#uJ9G}1T_z}g;-sea1(b=>_5FhUXsD~RXyYMz)P;B>l- z1^@;_se{Wa^L{!K3$ggms?P+BkOhAH&}4BRzCWFEPfq`obUq)XDYsh`yge_OrFZc* z?oW)Hqq7)!_xU+b#_eowAGgZDdh9yN#jSqxe=8X_TA+MFnd z%b2}&%OSC4_uVnw#i>;ylZ@4m&BH>eduPwnWd%-1_4l);Xj>FdK+R^K)h#FW$G7i4 z@gsgEq%JNgk!!ra#t*X#>@B5jni_t`%F-3T4>#?3&gONZJLhwu?e-wx>1i@dF|-XY zBXIKLyx+UGT)W%M@iAE9?z4sMlFH;J1-xxr-$$XLg9uV_teOEW>!goC0MCKI4AN{l zBw_`$dZoS~60oe29KExgUgxaY_3UYig?Xg8?w*p{X}$o{c+9~`{J?4C^~0z zS447e*KFw6aHl|}u1s677j?6LFV}dwSU6h@6E|WNk6ZL@RKuB-vENB%C2C=IlB&Wm z!wnQ=*-&jUWp!VxsKVw#FcLY7<4-Y83%)rj6-w&ul5kg#SljYZcErA!ML*k{H1TR) zn#k07Z^G|}25})<2Lu@6sa&ehuozUi8Fc>(;GJ|S25h%``)RADGE#CTzT5(qUyKYK zBTqSaTY#1 zF&;Jn6GaVDzH^(9fdd~@t_+vFchAN9N-1JROPizAX57I^&TzOivWd)S(X{E z&jxF>H{$h;dgdlYiA6Ey$s#qyFUEpscN$9ZdShur6RKmR_wq)?#6r(ss;*`;y@`G_ zU;tSe&_{uHsnj6m$Fv6Z0Af%S)0@4A>t$RB%L2e}`WMal8?B*v+sQ;h{?2ES^fALS;|^f7mh?EvA$T)t2SXL4z0~uSpyu@0XEcxT zz3ynv>VFD9+w9-5OMB>z>@wz7kM{W-kiMyqX zC4T0$+8X$|XPh>-ubX}`CHIiZphxM}nB{ayvFDf(oq~6|P%&t8m%_Oe=?Qh#@B0>% zd4I2J%l;%M&##!+KF?kgw-4t@!q2hK7F2s4VD9%i7gu2$ewx&~1?v21tapF1sOT9{ zMU@-yMy$)BQMk-VJDj)QMRvUzJda>t=;*Xb`uZ}_Be6h~g2kVt4q`DG?^T`&?&e!k ze#EmT5sjgm8?#6?9siZ(R^mxwR)ctF07nF`)PSaomG5+5?l#)_xu|7$a)zXM6M|8G&^OKis&b7ei7IpDi=aj48O5ro3WVMUmXrNi%r}+PP_u`0iesbwN(@r@*i$(RW40+u1nN_ zHI!DQAlU}~@1WU3NCfNy0!|^3*8bUce@^7q;C|7Y_odqWL>#3@3ss=kE_dM)72@KY z^ix7rM)Hx8(YC#HTH8?LMO-o%EJz`Hldgg}!Q^;a!&`%(s?O20;k(|b<(&RW%IQpn zVYAQ8w_b*09=yEemqVTlo5w%O6C4y3AL&dRmrDq*}X)7&6|$tJsF9kyLlu8nrkSSSH^F_t z2B_X2y`qLp!)xS zAOH)>uzf6E8_jmPJ>RRWwIcwFHw?r)Zm7)^O_iY6t8DDh8Hkf=lT)4AhdW3fR;tRE zWS_v#DvVfBNoG)_iKeo&XFSWxF;M}A`7MR^q0iJ@5bcg$SlxZ=y5ye-gB&jIJqUeL4i7qWI&r{0L>DkyDV_+2@7xmGuI z$IVS_YXo2MAinQiHw!vs*t+*vLZ%eV={OG`tj_I^4Bz0*G z)3GWul&ed88zOVklL?{QSxpHCy~kjmh#{}_Q$eZh2V>G?=6|Lez^Fn>?ZS~mH4GuA zt4%>3-(}NU7RfV!3aU{!T{4#fQ|hmbGLwZZff%Qw^{$h-dn?1-(SS{WXsf3^AyK(( z?^*<3oTU~Z*y?O4?A)J0jQtFyNI}8|60;RJJWoWu0Xc?I(I_mD)OKg*j zTE+TYpa}l+`;0_~Q$Jk_wXVT0Q#6WONnW?#LI-ZKWaPvT`W)&IVJV;9`4X+h6Co z6{Iph3$a3GT5msW?w9!Ph0hp{o5+7uQzM7$Hn-imE+s|aN8PyB9B`m+XpMm3R=jNy z`S{CDV-QyHpiMf*zQimr*3@OUCp_bqc=L~EUW$)Fw1uLO+b#%5zGu^gvtT87!D^Kb z@zp=*4|B7p#(?^#tf-G_Vj$X|`0mvL4EJnbxWv@GSiKD4X$xYJ#fH7+(+hJ`oU8K& z66gemlDG<>iZ_ft6H*lvB5bZV@OgVAmP~cEVM(!aslXkr^%v+vO@RC5dEQ3*L+a77 z0UO`Fq%4`XcTHP<$WCHX>@GLCG-H!&?F>U*!JvzGJ{E^=*N?26B*T-A)^f20pQ0-C zkGV{DeFIhLf_C3yNLm*(&V%g)0bg%1AgdRJM^+{Vbn${_yMjVk5P zNEv$#rHzClh=@rxA5#;pROs41OK#sTLPiOZm(jDg$@a8gxyq|Z z6eN|`?0 znf@S$t$7K#pM+j8)dO#<5_dLvoq_k+JVWsELczorI|y42EW4MN!}+Hzhy-tOOeVxe zD~Efu>XkaNt2_(?ZPS99xR7Yp`?(JCA1K<7Bw~c^34DqJXhXL+E3Dt#>@R(ifo$_0 zBT+M6cFmBxFZ`3Nllx5oia$LCA(_t)JSLUqFalL_k_T1!wP|eJ&%&@IYM4PpRAx`b z%0d*$y-g38Gm{thamf_xUP&rAeV#|gu^^&?)`;frxu*NA z^DGC+$|82Clx<-+ll~E3m4j{VOX|FlvagK8yG| z*|`5@NFrn#u5s{j6i78Ds9m@J8IYygeT+>e@ncup1gU&J7t@nT@vC9!>=ONiIF`b(VX?B$5=Jb01iWSt6 z_6(J}bH}+6T8taF72GswA?U>F{iUJovo%7Y=1C&$DXSm*vCuRE)sMh)LshR=!+5QP zgm!ejQSavMSW^)d4u2GjXLIdOIl0DL06Q*#f-n>WbZ#YU_s}qF@!VEEjq%LIE(4Uz zK`C?8WSzF2IKVIF5Q8|3m9#?&kx$`DFECau^t^+wGo@26z+a0H_R3SUkYDZXP&!w9 zOo?$9iA1=2&v6=h-iY!j17z5acdZu|3$)HtwgS2zzs^t8KfY4%>nwYh!b*+Xk3Kym zcPab&9-M$upd*H64}s)Wy24Z#Fu=%E5FK8nBWCE;8IBx}q(3RD zH6~K1i`fkPxD|iRin(n)_je?v1I?GeE~YL*jqMC)TVn@t@UU}#VfT~c+TLh*RmX*zEt-{K%P3|u1d7wT2klFowh-?Plf|izkGdQtgC{Hafj4rc-=TA z-op_aJC>RHo!?qa4(-h86ph~$O~=Am%lSHfVgUaABwGd7NW9I=FAA3I(%1>UW%<$3 zsO^yWfbAF{zS~8yXj1sKpZcfSVr+K1mc#XhBc)^RQ(_UP4<~%C3T2i1Is_O4n@P(u z4Sr|R!flg@6nc>n_0fY>vYewd?NgiZb&CrBVY;#w-m)RgEdWZm^mvC@QLL7pLH#|P z80jte{w2D2Ib@I(89k|@*I(pio(80^IaEKkIn;&t%mqX!nEFZCwz0e6ru~$l0llzK zI#nOr_-0D^=rM$mVhfE4IR~yfs!d>p%1AnG{zFO#2_F*nCU-L2bv=VvII?ZOwT~CH zgNF(3&xaHcl3!A&xrbGa6uOe|e>~fEf}jenffe zp#$!rV74B4b%RzY`O~G$jFrm|$TxGf8&sdHTL*vq|C(&e>K5WGROe?@ckPTdKDFL7 zu-piQZU5(m|LwrL4Vf0=Kj+B^z+-_#SzSjP&wu+ntWd-2e>>klKdJzhNE;|{zf&y# zNF@F9kHCSn1HC7ZU*o8AG{OGQDM=0+h|QpF_Z{2Xf|d-Q3=v(}U&bu$|2Y{)AdCbZ z>Mv5dz9C!n|4W?U|1CuZ5TIS%X8xnV36NR*1aSUQ;5jM(v)cstFzE~X{oKUHklx=OYYE%UUv;K+2~d2!O`>yGt$Rta$+%-kGH(L4n0{EH=4BC4Ww`r@hW-7jG*+9u`M)t7>>6Gh~e z#ly1C$sF(h)}3EnAo)mq9uDolu{21H<}Bd19Da3HF_4Y=O{Q;Nysg23KbwBynoN;v ziwxyVW*@!^a=XgS>b~ylV-UK0vcFU-_cI<2(5-${-00Dl6#Bi%BDQ;JrF%@B`@i+&3f+g&y$j@7%Tdx16-2lg-&@%Ij3EEO zDEzC!GMru2g7ngj-3^@`YTTN$0pVFZ4b<>rA0Lr+PZC=PhYjl8dd;RQ=bYi(8bJr6 zZ)x8A!t}V+rw3AOwzGO3XpWj0j6+fqety1c5%vnhV^4hX<@rU_oDdJisu9uoixx#e za$%1i+f~neJ+^tV&9ztSl0Hedhi*<0tl-V-AFN4(lCH!Rk$|MWz&vlJ#;CXH8{=z( zmlBV=i&D$K%}QYx&|-7V0{AwGTAQ1VRn}F_DJb?KD)m1fxs+=yhdVd?{Ksu4)jtz< z{QO3O*SXOajXv75xNk$hVQ#miCqK~w3J_&6SsO78GT6v(u)JrUId5+6B4uWxkxc-& z?TW%o1E~B&rG6GZ)fexRWQQR`*)br2e1grtX0Mt9muIGznv@7zBZ)Q3=nGaltw6}n) z6DH@LC{Q9E5`y^lZ8QLT_s&*qZvgqgGs9=ZEC9_zjAG&7PUVXB)3@;tN63yh;P#J2RR^eKuDUJX2~luRD6I7*D})|DTLuoq*YjLX?q&j^7X3qi ztInTCjfi(Bjy3`;&C31~?2HTx4pOE2R>>ehf^rL=30%^Eex!n2(PS`vkUl zc&OY2y6wWQbY_^W8gnZnUx5AeB!MFHC4A*eg0L}0oIbaeO5sSbgSu);TtXy2?lwP`daIGdBMh*}Si$`Nt@?zn}$xG{i17F2!;`#}TJq>;vfOb3uEN zJG03Q)r(Z4JS~U*+n5)=`I{w2>+Jqx8Mko2S&g+rd)w_CPtH}Pt~^V3ti$qb`eIq< z0f>XpZSPa8F#l0Pg~&(n>uE~)NRs;zs4#pS#v*?I?G_M$qdzanqnEjye(%ket>(Ro zRO5WD_M-PzeqsAu%xt+PdwVorqH~s+n(0(U@xnh(eg+uwGxtXDD4g@@0SJ;t?kUOj z%3C<+75G=o&jjR;^AL^8)`NDlwx*c0L0AOY7O=Jm8;^PgSCg|Z4&>$4lK&Gdx}sL~3}GAL-x z@yu_XHtf%a11Cis_Oyeip6&-h)neqkXA(lxvPDvSXo8l<`jFQtuRr}`Z=ix?F}?hm z442NO6i|riB78630yPOX*d-3qDcLKa3-fw90XY8j8Y|!8+1&{IC4eQhXldncR|VlHyeGAA5U7_S>!&gJRvO}>0{ zxKuIx6BfP!huuwDlD9^%#q$Orf-h7QM=%YpA0f)i41MB!+pNLVqpLEF4%%GFDzG{d z^p8qx_?C^wN@$8v9368$-P6?4|9N&N`Hk+uQTu0?k!(&<${LujErq%p3y}T-<2jPk z&7M$jdj?|K48{a(Ig&ujlH;J0uW9Q?bGviZXz`#ai68Q1^N5e_96_L>gwDcy*9Fm(;)_fh8F_&Mh2<*2 za)qs~NMI$nO#>-3%1d7(J}ocBw{+XMT0Zd03nNV=2>55^=1Uh9&COj)qnQ?&9tuba zNZm`I%gxPAp4Ci_DH{j5nj4ez5fc;gA`#v{r!Y81#?uA-)FJIXcH?3=Zn{3YYFce{ zY<3>1TQsc6@2@C|$t0UhX0&9)Jz18p-X6>;Eya#u44@|fg8*L45^1x(jvcAAPb#_$ zL`<$r%JiQQaLY~SQ!6h+Z7Ve&ManSlwEOK~Wl$*y`&EH%>9GS}0x(&f z4jrmYdh(8@$CPIMoVzAzQ3zv4&FZiNjebpOMB=i80m%af$+O~!y23Ml5P?&mH|;=z zuBP-u;R|J^*h;%x=?)kZWiMq&8MXo&Q+scKdkYsi!pJu!qP}ADq@fh{l7(g1Ksd!L zk&^LQo&L#Y*-sLK`2ngMV}XM$hf%c8fmX5OgGp+&76GuifFB=2|JJ2s!o=4in&AeC zWah|3g@PKkUJA1Yt+y!9-@%}Dm?5|ys$weE%&jg#8^hO&j7kZm4#26ZEn-N`f#TVxWA5HhL>DH3J=~`|ix66fW%Tji3*^67| z(Z;jq;LvWwB(sh%M_j^`c7|&ne5htzn*c!Z%jXWihDMn+>!AcDQBNI^`@}EgIK*}{ z$_xfvp|gm-1Md0s&F(Y$){5@VKwegQ0UuW+FmR-vTu2U1xPp}t-CQ-VYu#CAX7>_> znran?#^vF-S3->e*|32cU6GsX zBEv$|7~qgi{^21eP-$LBf+<%$_Lu!WpNVOuTIW}KmDa&Znk!q#nYrBkkS`N;CYHa@ z@<@v!*0DW8O;J8drF%=cWUQ=c1Q>Qepuhmr6B?PI8W;DrEx=~^(4cf15u+eN$9(W(RRJ~-9I zx*oaqCB=J6$+_;N=kF7lO!>vJSi*WkE0-qW=NTHR0o9Sd^SP7vXIn6glt+$;C?cZC@i8YQKjdvMFR8;Y>xeLZof>X9{7>q$rrau(?iD1xO=G?K=f|7Bv0z%WKQ4@0aQwM(XYcZTlQUtRF8Cj>`wV1_70Kq$AG6W0wu zn?3)MAYTyy+ZdwL56Rf--$`gEw4T{XzvfC^5?v8(+-rd$R1JZ!{?e2G_VUpGuyQDh zhI1n(Cr(+%`q1s*&w-I?L)4Ov6vTfJQmxyMF>BoP?xhEsPqLKacmjMHCh$&YQn~<*#AYB2*MNX+IHUfNAR4 zVoj#;4i7mV?cF;eIy$-yP{;Th0${^_A^Mh#nD{H_(is^uv-}*TVrg3Ep=Ik#Eq}(D zLcMzSrt`!J_rw zzVp{d4NP!op%_a(^8e9fzi*?*3X%Xvw~m{Q=Kb##23TbN{-PoRTkWBj_z3m;x&6JS z{_BO5LcqK;sIJMnq5c}7f5z!Q-@oeu=U3{2$@;g`{`HZdAK9XGlakAgxhKZYugv40 z-}~2t7C5qR#op6!$3#^KCd#b&id|$?mg}r9^Wgt{>A*mgSq8}0OVs8oz2*G;`hRxT z%WvjMfxD`H=6a9&A5H;F2|m zym7D*BTbg5EwU7$GIJUwJ$sI{Ns&Lly{;d-cXlPg&qTJ@e>lgWoLVAn3s%RfH+@X> z>ZR-A1AvYixR~y!`dgdvye2LsrFdgn?<6kBmEn(KHjvf9n(i%h$FPVWliuQdtLuIr zF3eBpM~pA$3SZ|eHyJywU;C8t^^s<=K7aW5-Wi=lZ=6jV?ZWtL7UQbFsLE10r9uOp z)DE(1GE}IG?6bX&`*kyAz`=p-c0)e-YF=Cu&hNch*eDDSYu}DL?+^UHjX8Q&56C{j zhgCHDeS9YiA${LW9hM(FZxnRo6|mcG!~0}+K;G?^MBVZC z$_wXjq+=taVYo`}Rk|Zdq#QxnTpp@M`EO+OpWC;xNT2oSnnLh`^BccIhI*qM>s3Q5 z6@Vw6F8dWen2j#oRpZadF%{dikrxtw@=jQJs z)|=R@b6`75-9W>!J*&Kd(IW%(P%?Xz=FEdyxiPAJ#-mRs^Na`i#m@-0I6af~KPQBx zUyF(!?ebHNKY1kk(Hc=CZ$|ofnjzy3=BLE^DFealQfs&T9}hEc{w zp~js$qY;jx8p>pXXuo4o5zV{n^ZZJZ=dM6@lqvU|Vhs-4k3lLxvqd9mOq(3jFwuOy zamQPymQ%I*>oav=&O`|)(?)qw`EbE&#~{!V?Th1CCQa93y?rV$&wf-WP6`cXASLw$ z@Q|Lt$rjK3GU+YElXI#MYZ&NMaV9@?zfmzW$J{t>uAP|-fA-2YVd(b^E+PhD1r>dA zY+bJf3Q$dF%LSb398Nz(f0XhBmUA6YgTv{&h5K=ql8QI3cTT%%+D8qy8^`V^+?Vxs zi?3l7SV-H^(9s1B+3lD(bS_=SuemGl-hTpnX@F8Dh*aA{aUg1N&DX?Sx}GjvXf#+q-&s;Q%A13meGlTsQZ7a|gSn#(y4pTWCmFJqRh2H5L19ECMhsyp*h_JBPX)DGN zh=`$S$?iwl{R$Aesm*`~32*Ck@i@Rr-grOkZN69?$=7bS(@*ew@>7Zi8+JS6ygmun z-os0|c>lU-+3d-+z^OL>2CR2i`1%nLYX9altrk6|d@mH5@@c+ligdZz{$Sy8#@cWA zoa$q!dH)oyU?j?PiQh=|p*srq8`jebo!v3?O50C^uEJoUT${qItjPcEW zg&mOkYS3jLX)x-E zpVtS9HryWKl>U(lGM;)8Om&}=x~kYyShuZ@sse>QorU@?rjNndHryX_36hyFO{Fpn z!p}K->ZNd)JH5YW=HWs0QT#MU6@_B z4Cx1=1b12c+rpfO9Xw_eM$?5%8N{5ARc^J?m$yQn)n#h%Tqg%VhrNb>|KalV_ihFl z74@FBx0Pg%kHbpJ>5#E>+GJQiH3~4Bmdl~UmucUO0tPAqVUA<2?&rs#9sEhsn&mQW zmbnUh?jprHUs5qfbI0~IwBhtK#9dzXP^6!I76e2S2NcuIPE7t>ceO$O%nx4m&cMiP zzbh#58_yeeA5ZS7KZ9Sp=L`~ed3HZp0k^!zcReFd=5$s|F{V0*6Me{VS@6#L5aGJ; z#IP2CL|x|0lW2##n~9S1aE+hYy2sXKc)nExXro-)$OR}Qql;op9xc>mr081O++X2h z?t2G}XHnH~uN0VPK1!(m+J~ZL%WE3o)OYmE>ep{H7z%A3`9h*{YKz6y?dy_(?Kkzh z`p>qeTBxPBH{9-UfvQ;Jfu`eF$2thd zCA>U6Cyq-A*zl2%c$6K-t)^z|O9gX==k(97v1RTh=K?=8T1{f*1DOQr-q?{dUAPUw z_A)HMJgCIXpmC!!?F_p_#_!5Biygx&@Wz3hp>$3bu&2!+Bqr|Ye_IU1lY0xh+H13G z1$%zC9WWVLIL*)}p=SW0mLlBXP2=Sj_8ycLTrk#ca3CGB(0FsA_~nFRIpy7P}k$TfB+Xm(ZyQ#PqJFmNeKla4oF$RTN@Is*~pXFF+l6CJUuPdID2{ z)UeuPaF-wM%fiqj0(nKj>qqT%lQLh*t77v;+?vgwQBc;VT9S1fbDRcl+?@ zmzPyHO~Z%HYNt%L=1SH`S?y0Q5~^I8j&j+_b90 z$_i`b84f+?BesvXZ(i?@EKor#8Lif$coT@~wl7F;X2yn7B4w9p~?xwL@@DT^Y&=2QZha%_$iyqYwn{$Mv`$qlc@ehN7Dcxz+;skA40ChuzhBWOB!!c~! zj}2jJB_|1r!zUHrz4$>+%iC8$=$D(>{1>@uMS+Sd0I^*t^S616Lv> zNS4M?UKTd5M5$ssP;SC_Q(X>1w!z9?B5a$#A2Fdf6CA`W{+=bm#nrWvLJ(x9Ff~+G z+JXPnS`_T1uW~zg!@%Z7WSmE3OM)?+2y&y}cB0g`6Rb-+WUu|Mfft3phs zPX@DSDWimc%8t@aPJfNpIShAryS0~~x{O%QX^qoz%RM~BEptH@{*z`Np-<>b`0^y{ zx0}1~1o?^tv_-AW!bingSZYpcV$Pp5Wz`l4QvQ&6YI5?>rx7^ zYFYX#z^`=Z-J2Kjj z{LBFABF_i~UoaexLb311tq0OqhyH{gl1hyeRw-z(nV%%qyWK=}`(xzIOd_G8Nv>Uk z$!nTjHjRu%8v^`q6XH2R^qAQSviA=^fC7nR|F3gmPgfQcKdwki$ib_1#AZD6dfRc!A=zqst5$e_+MJ#<_%|y2NmS!` zc2FlZvm*WL*H6`Uj37cUe6dpNG)#M|KDnE%0df49mdg{)#8m3#;ke8tDe3gM6Nb>` z@EfK6m=crIyA@tW)mjVTwF^%Fz-(osv7}TMDl7qh{;xY>EYX|fWaHN#-D2V-T1xww z{cz&h^?e(@%z_~#Vz-?MGUbYst7A@kgjpChY1WLs|B`_hVUMtAWXCpHpZ~K|mcHg_ zS(&irpu?j&yioExbqK3nI4NFP7IF?y`4`w34#;S@g;nScN;jXwQ=whz7e@9fwuPe@ zvgXxka2A>SA!dWW@}mIniRQ4{OwW_)%8%bZs9YajtTej(O!tU?5k1uTQzF@ZH>gQ{ z)ccf(pCl`f46ybMAKBc`0F}L0B&1)nTu1$$HS$M!e4;M&T@SsS5sBFY0dqj z-9*&{nyZTj1}AfJxFok>;RFu8KHCylc2W1H-Ak=JX1>zxaUW#Dkc+sTZRX-%9p)(< zKV9qxP8yN-J!svLNB^~73*!NdN(`S{+b1%WjI9QbM8u;z!>_bsjuWY)EQYV;nz+vo zOv67cbxg5I{yK&endAw^k~2(w26t)}0F^0kI=tONSx&4=y%Z5!Wwh}h@=iHNKtPyK zgkhit)hHYpNKn{?nMr%<%dkD6)Q4G$5hC36<^}xneI3k|HdzqU!=?VFW?6_clG#)u z#FfRdFp1$2i*pZqkK4i@#osD-+v}Bh>Sqte)t${)(NWyXf!oY>{f#tIp~|eVMcE&L zczuVna!OjKaeYy4O8f<&8grRtH!p*M9nCv4!C;(xEu6gVT&-jo>Ix3+J4mL{(-IC) zjj2+}LNUJkP|t>f23YM1?mi zQVtjDFQz49gHXB)>ab!Ve;UnwwAfIRm1x*Re3QC$1&iVG+4>RyMTux6Ldf%LMqwD( z54u{4VSNKaPZ6CCeY}QGspz`RH45D4h9cb zei(+tPB2SI>E?Qw{&h$o%KA1Ew4pJTcMYb8hvOkP=^rYuU5mKPZ`nE_!s@Qx_Gj5X z>)C&H6SQClE>+>69e3k2V>QXB# z_9S;VgierJ^&F@56$eli6dyzVD6o(68({t|V5A>o7X|3nnuq zxMNtT+A-LZ=&c?*FF*G%&8)@lGPI28XWLN&0b8Z@3@Pf)GM6~oq=HG78v6fJ+gV3d z(e>?L5k!!XZcy-mbV^C5bV;Xlhjf>ev~(lg9n#(14bpMw=1}jT_wD=M&*Sr~-#U7cGkQ+ZR}tUhxq8k2Jxs!f1BM->Uh;n zY889>*!oD&=#SpwVJq8$h8}RspN%RmXeb{4Uj1&01dr2BpQ* zbx%Kw{n+JD=Qg704VoAjGX4>NMT5DM+~_r0)hTg`H`HZ#@}@Ay^`l5NKsy?8x_>xT)J0;g2p1dUOJ zA%v>3BLOQ1XHU3)jW@(8oJX973Nk1WreNWJ{ga$}28lJF;%IF%%GGHDJ?I{5RW#b6 zh1^h%53Fj%*H1gfttKh%Gsu*MJyANA;)|@cRT8&i|1I{`59zigoHRhZVIB% zAvg>u2<@hIMhk7A1`3&e17xhRXTsZ$BNc5{k$(-p0o=?3|2V|E1yczB-LPOGP;&Qg zD1NvC=kT3%bh23*_xr_Xo5^BrBZ`Qr)eie0!sLKQs<>BIS2!JP3{x+zj6QEkB9pzg5aYx8DEI?$C6(|GMq$@{7+Gua}}fuAIuHIIk_{8-csTS zJQ@6f6(izTT$5bN+P-g?SfM0TU%X!VsmkjyAdei!G_(FYYcG5H*g_M7fbFnGEkDrv z*4@Of%PZ0xS_yqr8~XutY2z4qg?~iIu)zz1A>d^#dxZMsADH+TX>YbB8Rml#)~wsA z2@oFQpur4wpnAV8>aj3v+Jmttf`z=iCGy#l@EnuU5S z1~=^^EI@kn^`yQ%%>qSA4BR7oUO!Ho{rNNWZS+aKI778!i54%L`F70m@h7!S<>=V* z3fjm&<95#=o@QxUN6bQ?yp^~@@a9Co-rnAN+tk2=L}Fn~IYh!*^|JNXQ{caA1|H;q zLXss=g8X-5{)@=*5@7wyL{NVg3OqcN0S4T!-NO5KGXgF7kJmHdOO}6GGrj?P5*nNKK$i}584~WRSbhEZ_2 zrq5DM_Qg4ea!Qvxyx}AQoS{Ix5_A+7)4Oxt7J`-}Z)RO)Pn^dl6Qj5!qsoxh`KSjnhSbeV7xYR8@A>W*iw_c!pLLg1rr!xXtdni8xlBr(<9 zR>vj+=yu}3HgN75OKY7H%q+ROfx^{~^P>#jrp>!SK6O7_YCJ+@kCxx0-^0+m{htj1 zy3QZa=T769W{N{S@>BseVm1)01q?QF%H+hwuc4vNJV<12!vyw}Jk@*ovM<3B6zYyP zF=ZG9R9A_c(K7pC-e0o?d`W$sxPNU!|lcMOsA2W!^E@PNf&xKUtseeg8Iu zk)79nAEuA~N>nse7`7{Ow~l#iGiDwkTQ!uh+pTMElU%AuclizT*PP)5$fq@VPEe@Y zBbLkP#o&AyVtqE6zwtWIEB26YEh6?}9)<2Mym|+JXhd<<{{3}4{(aS2C8S6G&2S<9 zUVS0>9?_5IIEafXd!Mv@EWMkq5L@2UGQ%Kzow4dA?h%ov4<(`4l8o}56x5?!;OviN zPq=s4xVwGR8{T%yU42rITT*$%Q4HpLP-o*}c*MmKI>=s!CFJMd5fcdr%k`&Y_eKa` zq-(nK>7jm@fkjRuHyFhC^$Y(c6?gMMdoA>hK}yCLR!n{R#%9H8T>#StI#w>4!Ag1N z9j-N<1qp7fTDG9oDkM^kn;PF}vMC$PT?pwqVM+g{4*|3eCV7$kG>joY;Lu!D-QC^o z4bt5aJU5=1DV)ZTcb&J>eq#(i$qfqU#-Qkw9?3F;sO6%ZhD>R8Qn zx>rqZ{@7&FJQb*&G74MBl{KOx3l(Xse#3f!mA8~yQx!=4_m#|p3#~+l%cYGlk)s^C zKQCi$OBw)^2!vh8JBE7FcuQ4yBtQc%jb`?tV`B@< zRo&<6M-X)v9OufpkEFQofAd2o+OA1vvz81oV&N*nLPZs=vpYy{y5r)%Ax_Km@Sv29 z#;}n*Iy$o1orbG*0fn9K9%T!M%FZ^paSA|?;Nd6p&?t9ZBHp>I#mdw{pn7o36-`1d z!hBctJp-=CWu*__)}lH@-6`ly6fOUycc3rd)gWY#ce?M-H$8|3~D5Uo=x0Tg0^1y%as*--U)X0$-{4#)H_Z+`a2- zqStO%#&>RLdts(5_l;4D7L)#%)R*DD$an%`F-3IjDe`u>#dDbjdtRq8&P=mcLXR>a(PRtNw zS#fniJzjnhBkEa~0i6tun-zIv+hmr;?-we2jg%s1YFhB-hg_DMlw#EP(W|lKc6jzF z@N~^P0xp-JjoS(j{lR#WUB%+7V4>WZ9kItT@-u`syp?@?BUrADX>8VR(RMY2WSF07 zFLs4Lin?6wfR}A{PM6xvHPt^5xdU;U(K!h-v88C41>0CltbPatB9CL9^vuj$m0Hcs z=%ssl`}RmgfCppgg*85hP|zFoMemKySEqN5t$>U|P-M=IZ?Ur|WEY>MqwEQDYt-HBm& z6Uz*=2IH{A`MNUtC#okhwt}>Semy3>)Ut(2ai0vpeOnN-!z!77kqlHk3JVN;4%EN) zpI=Z9NEwlo45={L)Mpz!csF89ytFVNQ4rGKkR1p9u~!Y?Xu=e^^IeLtDBhjvWs)6$ z&fqTt6A)@X;U)@3!v*D@<3L<-UO~wkWG(;>B0`OVnI24~J8p7| zkQFCu9*T#a<_wM?MT!dny?Nt**|zxv3hF(jg~}lAHPYnPsq*z2-g~eDwSS>|5d}Cs z08L0tj%qjY!=&(3l&wSns@phVVmDr>_2D8FO82$R9m3*#LqijS`_>4=k^`t-4{-lA zl2Gnfkm1?R*{R@x`RL*_J;|E>G@xao1Og=`_<=~UtZI`F=?c^dfsanRh~{!yO^RiK z0PS2L%wW905f5UlrCH->JQb7bwR{vD(KTK()}`MCc-_lCl`obXa32kv9Jmh-1LMfo zc3r0j`f?3UmE?7`GDMtqvg6k}#@Bm?uU67Nn&05V>Ov}xR-X6kXa6tTp9ro|bT>1c z_5$wc9ujD=Ojqw9WfoFbD0zrXhrRO0M@OS}pdrS@M4)yS?ZsM*CQUAPD8gU8<235y z>kQVkIV?Hf$ZP@8MbVmZZCse$- zCBOsozw+&L7us!e>5si{U%h4$SAH>oCzSz$Uup`DMXsYw*i>Xwms zaWoRA@u|7eg?1eyibL|2Y}_l`$=NwCK`keeI(1eXCTUD8&-Ap-Z$~eKqw*#dCzya+ znJSnx2#=f?V+nf3EJUt63$a5lWWs@39bo0q_^=epd|2}ol~ybtS|@jBxXD4R0YL1` z%QUHg6}KHCNjku99lGdTOnRG61;l_ZeszfefxI%g+^9X~&%)>^fP$U~U43J6c$hyG zF|9G^2gU6#0&F$ckgLyp6qe87Nbc zNjfat^KE?5{_^&<#Uh7nCsT9PWb|@$VSMA(yRgpu3z{mX!Mt3+Yurvmy>2hzIJKq& ze&Us*?O9*ZIP=a%3QoL6zi{}AY+G7#MXHAUtj!hf*^)JWYj|?d83W)oHa5$E6XM7N zdIm6Tc1`IvvJzVaphyHqY0_)bja1qiFfAtfl0To=!)!7wx$X~S~tG>BVOPa&&N_pqDg8rFA z^`2kH1?WAMlba{X^Sf!t**q+?H#89$TLwL|<|k{gt^l4ZVRotcNEP;|4ng(lZJt`! zOoI0+e3@8$Ro*t_EMcoVeD(A;NFwdP*=E(j^2;GTumTW={O599_BA^2o|t+AzX^O-M0gPj#B z<9b}hx-u;o@6aAClnBI$Q(_7)E|Qma>JP+&Jj)8XjTfFVL|aDIb7c7W!2+&c{qU%kq^(NB5r8z3b(b2bCSY){f{X%*7qVW&P_(zD=H=lFc zF$La6&Eb&Q+uK9TAp$#-4YbrMtW&$+Yw%$`=CZt$P7qbZc5%B}mTzvpe0kGBWBpK= zb}TxmNo@0cJm20OV%uR1ie6_?S&;=l`ANtCZ;VC#5&MxJ8f@ z5-H?a8<#_^8_O%4qA9v2h1Biw(+2ed$1=OGZ5Y`aWum>pJZYj8CB_6u2<yAW-fV?Z3!oFI{W+ve@IcRaT+h$quVPrQzk%XcZK#y`9Vzjm}cHit!=|N@eP1 zK^XCgam%Vlvn6hs-(zr|>Z(@j$0I=qMrkUzwAHDVJ1{z=}ayAw8?2V!n9cJSia%<0)BK%SCTC8(OL9{RW zj&FoQx(Y=4O=a%a9jWle59b9yK00mt_&8T`&t5MadZ19roq?^bg+2TiU@%SLDM(`M z2#Y{q^0Yy#s^Z@1XXA6D9Yy8VyuC1+Y8pe>y7y`KvbD4vZW1en316-YP>89{Z8*S= zrte3FT$&kQTyy1R2xMs2%%`ZqfeJ)Do@khu8v^C2e>2F( zpV+v$x${FNN=^_+@Ntk-`pTbW1PIO2+Z{2W2jdeY35<(Ku+pOkS1`-Y;eHWP!5PEC zl3*GgOqh!gsk`_1_DN@d?vX z18Yxlp61h}PLYlX*LYn>;|Y_kjDm1;Q}B_oz5Mgdbi4(Xo*oaH0X@7lwTWqry(P(m zz{>E=`iU~`y68bMpXa3y25O4sK4WNvCmQ8ZD0MkFDmXYuRFyolzIvmKDc^iZ#CSLZ zv2{f1hRPA@dS3kjgLq^@bEx4PnP|O1oOkBSJIz;$?_E19sbKHTWPDu3q)kcO#wxtw z+q}sJc&ImeGcM@GXR=tVPGSFmOzy)0N=T&klM+V1|BDia(-`);n_^FBVn3$tL>;Xy6o>p1tO|^4BBeZz2UO9BRWkT<2|ddk$qHW5HhEPa=iF zkS{zddDBjeQkVWf3#;O_+V@SXbd552fnjub^Y5|6|FI5_dhSu%erY=7h98jQL#=j`L3OD#ikB~m&u!pi+MhO?G z&u|4s-B*}L72mxxGG4DB{{;$5OG`&JTD)M^zh`O!zF#51BzzRFo@dYO{)o%U@E`Lx ztSMJ~Eh(W>j!IS(si>}wLFQlMAk+Ak%)NmciDUi265-F-i^t;u13vzHB>S6Z{aX0{ ziUA+BqTC_=fye&*>R8YZt@E57D*C6h_ji-Xz5-Ae1|92v zAb`O4e@-vBqkS~>ACPSBi;`{m30WLQq<=w}y2McX*RT=ld9Tuoig-H6G}J?a z55Ro_ZWqK-soN;mV0(MUK1m-Vf}D zLp@Cl9J#7X#g^FL$Le9>Wgx1hEMM>{Vp5LI#THG6Qv>DeT~wW(t8POA4B_go?bn?> z^yv`#3vwYiuEbqb?QT6q+MWpKR}0KD+Hc5f5QuExXC=?=Z7|=TUlecmg52v&!KrF;F zaZKvXVqL6lm*=8cJOHl~?oPBU|IvwrojsdQNhZNc$`QbhNlFpXXeR)*zn5(d?iT2Q z3Q^aF!fBkwo*ZgiPmhEgjnqPnT**LRS4ZGvy{@IGlk84jt%Qo|NkHsb=_Nw?E~{-L zU1Up1dr{$9!<(C9%>0FJ?4?^Vo-18Ahcs)#K+A%(9NZ-;rS;z(0X5~zs;kL_g@x0_ zo_|N@X%uu_PaFxr`W_J$#$#1#wf1#k9p+ryaf~B*dRnPBnX_&^N*eXyVjm6XF*JNr zQ&%S!g3mTus%Z0Tvcn-U99629O}@11b{c;_L9Yd`LDIa6jPy7Gc@5PD^bAH0mE&gY zT;z}!#8;45hopzx0*4LV?Bi|EX;$NXAz@5U3#fYBJ%cJi4H2*U$i>EniPP!Q|9EX6 zV&i>HqPO=GHa0dHZEa#fK|!3T%?sGE3(9mTeY=&Fm95D_)ekS-AD8v%8)ONPa_O3h zk?>bkF=4h@>8;!Vg=URfhI%{XGb1Q8m(Spj9VN^(6^T^Elx0~}4*U3=)}zWkr0XN5 zH+5Ysxk^20B7Co*p&`_zxi&aLA{@ZoaDD0)zMwRJKEt@VRc#C2PC!{wQiFxzeK^-R z;Y@GLNFGeji-noi>hFgdM}8?M&4I68tf|D`)t?nJ-MLAo-4wBJZ^^WhG(3yQq4H$k z*rU?>#EK(TJJ@&qYOlskW{`T|q+Tf;p< zaV)Y*-XBCAY@ILcoEX@%uU7Y-VlT_ur|ya_@p%0(dKGz<(6GZyzs~ZGZ?@DmTO4<@ zFC|nclt9l8_4d4QZahm~b!W01uktdL4Va1?Oez(d))eACbT8I6e(k+|9o6U&*Pp!L zY}i?W^C8FbbOKa& zHTvA6Q-g0AmzbEe)w|Afe14wha&-)+#NDL*VY<8|vet2x^!99ODUwXeTn4;5?v5#` zNTc~J-%~Nyw5CipG_<3=ec1CBHkZfqW=&CTGkw0)TDVfv<2$Xv8PQiTx&jNsQd*;$ zAZj&+11LWNI+mX&V4YfxR@5>h?+Qif2oP~o=(GQ8{|}T+6RnL_PLur zattgru?}6Px9HDHitV#iT#ogkqh5oAOI5;B9%ge2UU0r@OH_p7XpKPt= z&EklNE&_jcm9+8AGmHl7i{S7%ZyL4l@Ar3-J1?$GTBbgDz^BdH8p{o;v5D;K*&oP8 z)^?GCKr&ay>%sbzS8n*Ys6y-GxAE>_gzOIfdGf_s`-R!vVw}Q*H?F>4FldEbkOh8C zC@Ud=0+%UfON5AW%H}3OI8*xa1J=-1(YB522X&ge1no5>V_gcZ--tP%xKIe=ky~vE z+=YCl-G~fj7G8{~53L(yfAfZm_uO?aQLj%kKS?8Gaq2Z$yt^iu zso~lB=bkB{>gwElZ$Esq#7$s~eL>sP6qApB#zJl~pnl%^O!|N`Fq#IvGzXl$pxdO| zgBpq$NH$$*Ps-IZG#JMXu#QXh>^5P7wImzR;pY6U$M6{=PIRh#&2H&eJjb?{}I9uvKYW_NEKLi!8Sg|c8@ zWCGsDF*Gs*Y8`{SogR*mhS$+`~$C_mD;$261Rot|2pt1O|2U z9Q8Vz%+%DETm@28jfc}pIXH~QRAyJMf%B}~ih5Vm$C`@6@MrWfN=&%J7OC?UGrQ%A z7mGIgyUHpx+8~K!R%HRkMmIvDY$Ai9%)ZJ;O5`{oF(R{%m(q?C5!FAQr9c#gOx`+x?Rj(&|rF6!G-8&g|}Y91{2}BlCy9 z9`r_on*&zKPA}HsbjZ3JBHn2p_CdivM~rYjWwUkPa8_Q@a5Wm*Z#V^yYHoM2apMMu zFU)iXobB#3$8#_5z?&KXlqQ17eicWr4YEgfzT@?g$Pr6DXaU3W+n8E=1`cb&6|3BlkIJ$26$-gh2vBUXIX#rg zggEzMBU9#+SVYloY?4I_i^Wi-YhvC}MGJ$*cUvH~O(M4}`Sl{Vux{k5IG5`@DrGJK zkwXbA#Y#kR1aFWYs4oxuiyYZX_**(KY@YlkCwDrk zakBmPCAsavjQTs!g>mDM1ok@_ea_8W21WOxI)+MjF=dlR#cNK;#|_gO_Z=zXpR-ti z$1GY851kc~-g7#>rBB*+Nrq|MTABTjO7QlkW0U`L)}~GDEI_0hEjTb3J3YuA(ReGP z6AvV03*))Zy*xm52_bX1ywzRXvdAmzG!44v$1%!&siy~!V~bDSk+bL z#nBnz@_l_)F3O>g>b_Yakc_o!0|`zEMoa6@Z7j;nKR&Y4S;c-|jHg5zHz_uBivYr3 zmoD!DZktJ`yfSRh@bafkW!j?!@h@7{a~I%xcO$(E1<9WiVDhtGVVg*x0 zH@A8cLEv57DjK^Isahk-A#JWRkC}hyi^)&5k4?%|Tr;qK+zs*yFE3{dPNMTwAn!|+ z1Li>87egrNMJ`g5^ZO{cLT7qJ44m+?pqlP%A+%rIQp;(yOM{+FP82G}xW!*Hl9Q+G zuoTcvitJ`}PD*_ICK#Z;2Xq0&YFG8SopTd<{YS?0Dvy+oXP$(8Q6iL4&Ue>OR zBL*zYv6fyxRh9h706Z$6q07;9xAamExG%Xm)osjayY_fNDA#*AF>S{B=Hz^PE^7RY zWm}k{+zUCZ=6!sF@xJDph~~)dX{ZCSLhvGuwDFT2KizD=Sb@U=$~=E*gh`ww5t~b-(h;r-CeVe)ibhe0@B$f6%i$8#bnT0T9OOerXL%FjX?o9Z z>)H`9*gtoWge0{Gw0T&5N70CVBbChYseeMg`q@zGD{sTPc>hh2Q)Cr49>pTH@RLi? zSHpU9yepy6$91^Ro&SwMXF60fQSFF4-;vLhh$)u2=j?igZ}g(@`?`p98u`^XPOGeABZughv5*_`DK>J^I5j6&ip-DH-rBD(9s(euoc z@eM-^8fPkl(7I%_V;CeQ;@@V8$dQp>l%aeg(eHa1vfR!HrecaxVBEnUN{`&31*}6g z#f7LS0pR1aQj@t-oi30YY8T+J4m22b?57GUb;czN@ocA$MnH>M#z3ELitDoGsVV-| zv~S@`-r)o;oow`@f(hfqgUh;V4*?fqnq9 zwGuoT9g0c0ozT*@_4aMci6?6E-PI431IV^#xx zx>?b;j8(RUoRGhh7CSL-Dkp2c;puR)y0b2~f#iaF0ylSlK5j2kc3R8tba71c8t;_b_ z5^AFjhZ3raG=j-i7{jGi0!M=1t^{R8i068kVLJCt7<9uq?We=wWBE z97Hlg@L|UmE3D9{HEQ1rWFJvzjMk^v?i-tK*Ju>J>|soe$d(wL;{9ns;(2-dY+7?k z7(F-PM+U>{Xt56E8G1gH<&u`p4cqSvKPu?!?redNx3DyKm zp}rDvR$T7EqAt8z!qpBWF(ZyI3}$8W->7L`p9ZHn7w?~1C4`a}mx*dyJmr5*T1dTE z1t|d`5;A>u=wLqY{sBW3mK^d(70-6T%AY-kS~70BG zwirR3DS2A-wj)h<*R;5ts?0z|NF%*B!oB);j;3itqoR0EgDLo`BByv=b8+9ZD+`>H zH=SFx2ty=4QBj-^&nA+(ZjUDH8A;p|Ly}+URmrb;Z4d2~;N1p|#2mkk!cA~sUQ~C{ zmI5(j|I{fJuXB-RB>QZhKCg_u?$Tl6M}oHck2tD1+?$+pO1wc!OP8w|3f7ZfK?f8n z%GRoGw5#?wNOMOS?FYfpk%fZ4OWnU_X4#@Q-0te#ujxoC-2Kg zrsE$*Dxc07t4{WiR2n##t?#?^3w?M!=g|MsqF z5Z614(NL8+o}MXj*~*YyM;YHJT~e=)IFN0`KaiVt#D5N8=?+WMObqsm^YmlvYMo4_ z@(~}kttE@5*2@Qd{iu5rLS`yZ@1Hoz;*7CB*mfUz2EF$nigls&F^ojHcfw`_9&hxC zEoT-3Pg|vu$(|#`ZO@u*s~~8;th7=-E=r4fTfKh4AlC6xtS=5s#z=Gm7;(3b^Dqaq zvD~3*x8|SlN9N~X(Ud>j?jxDdra?*|)ha&_n3*c^+pPUIHapQta7 zRP#iO=R#6kpu|K`Az z(o6$;4y~)G>R)UTp$TQOXNm(k%Ghs{~K9oQ4aSdof z=G)HzlRN&IQ4)jt(}XMbw+UDAznE}IJRJ_SZJIXyH>Zi?p**pKn`tcX1#GmqeZot}TM;s0d={5Hq> z1C7@Cn$NJt=u}|-{^&ne=83?Uss=}!A3UDrlm11|rQqq)|Kzi!e&HW1RxwNWBTxUo wC4~Txb?)w3%jWkv{*MK%w}9LBznXb3xfh&m*g^me^AYePA}A?P#HZ!;KYC!ghyVZp literal 129559 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 B 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 diff --git a/docs/user/ml/images/outliers.png b/docs/user/ml/images/outliers.png deleted file mode 100644 index 874ebbc79201cc1282f0763c93d95dd953c1c65a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178520 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

    a{to!uPaqv+WZ?Dd?)1$y}`2R-&r3Gb-S6RH?_z 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­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 diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index fd86906f4b989..c58408d85d37b 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -94,16 +94,16 @@ If you have a license that includes the {ml-features}, you can create {kib}. For example: [role="screenshot"] -image::user/ml/images/outliers.png[{oldetection-cap} results in {kib}] +image::user/ml/images/classification.png[{classification-cap} results in {kib}] For more information about the {dfanalytics} feature, see {ml-docs}/ml-dfanalytics.html[{ml-cap} {dfanalytics}]. [[xpack-ml-aiops]] -== AIOps +== AIOps Labs -AIOps is a part of {ml-app} in {kib} which provides features that use advanced -statistical methods to help you interpret your data and its behavior. +AIOps Labs is a part of {ml-app} in {kib} which provides features that use +advanced statistical methods to help you interpret your data and its behavior. [discrete] [[explain-log-rate-spikes]] @@ -126,12 +126,14 @@ image::user/ml/images/ml-explain-log-rate-before.png[Log event histogram chart] Select a spike in the log event histogram chart to start the analysis. It identifies statistically significant field-value combinations that contribute to -the spike and displays them in a table. The table also shows an indicator of the -level of impact and a sparkline showing the shape of the impact in the chart. -Hovering over a row displays the impact on the histogram chart in more detail. -You can also pin a table row by clicking on it then move the cursor to the -histogram chart. It displays a tooltip with exact count values for the pinned -field which enables closer investigation. +the spike and displays them in a table. You can optionally choose to summarize +the results into groups. The table also shows an indicator of the level of +impact and a sparkline showing the shape of the impact in the chart. Hovering +over a row displays the impact on the histogram chart in more detail. You can +inspect a field in **Discover** by selecting this option under the **Actions** +column. You can also pin a table row by clicking on it then move the cursor to +the histogram chart. It displays a tooltip with exact count values for the +pinned field which enables closer investigation. Brushes in the chart show the baseline time range and the deviation in the analyzed data. You can move the brushes to redefine both the baseline and the @@ -140,6 +142,3 @@ deviation and rerun the analysis with the modified values. [role="screenshot"] image::user/ml/images/ml-explain-log-rate.png[Log rate spike explained] - - - From 477d97e6855523c9853628adc4936578ff71adbf Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Mon, 26 Sep 2022 10:37:08 -0400 Subject: [PATCH 019/172] Use monitor instead of monitor/main cluster permission for API keys (#141771) --- .../server/lib/indices/generate_api_key.test.ts | 6 +++--- .../server/lib/indices/generate_api_key.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts index 6d49e2e53cfba..18a4b28650769 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts @@ -56,7 +56,7 @@ describe('generateApiKey lib function', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor/main'], + cluster: ['monitor'], index: [ { names: ['index_name', `${CONNECTORS_INDEX}*`], @@ -91,7 +91,7 @@ describe('generateApiKey lib function', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor/main'], + cluster: ['monitor'], index: [ { names: ['index_name', `${CONNECTORS_INDEX}*`], @@ -138,7 +138,7 @@ describe('generateApiKey lib function', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor/main'], + cluster: ['monitor'], index: [ { names: ['index_name', `${CONNECTORS_INDEX}*`], diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts index f76fcf41fa245..74559dbe84995 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts @@ -16,7 +16,7 @@ export const generateApiKey = async (client: IScopedClusterClient, indexName: st name: `${indexName}-connector`, role_descriptors: { [`${toAlphanumeric(indexName)}-connector-role`]: { - cluster: ['monitor/main'], + cluster: ['monitor'], index: [ { names: [indexName, `${CONNECTORS_INDEX}*`], From b74941c4f97769e76d58aaf13a667dc621c3e865 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 26 Sep 2022 16:38:00 +0200 Subject: [PATCH 020/172] Fix Entity analytics inconsistencies (#141249) * Add doc link to uninstalled jobs * Update empty state to display a dash instead of 0 when there is no data * Update empty state to display a dash instead of 0 when there is no data * Fix risk componenets on user/host details page not respecting timerange filter * Fix entity analytics page refresh button * Fix Entity Analytics Paywall and Callout shows up and disappears during load * Add restart button to risk score empty space * Fix donut chart tooltip background * Update how dash is displayed for anomalies --- .../security_solution/risk_score/all/index.ts | 8 ++ .../risk_score_no_data_detected.tsx | 22 ++- .../risk_score_restart_button.test.tsx | 67 +++++++++ .../risk_score_restart_button.tsx | 65 +++++++++ .../risk_score_onboarding/translations.ts | 8 ++ .../common/hooks/use_fetch/request_names.ts | 1 + .../common/hooks/use_refetch_queries.ts | 25 ++++ .../hosts/components/kpi_hosts/index.tsx | 14 +- .../navigation/host_risk_score_tab_body.tsx | 21 +-- .../pages/navigation/host_risk_tab_body.tsx | 5 +- .../entity_analytics/anomalies/columns.tsx | 13 +- .../common/risk_score_donut_chart.test.tsx | 14 +- .../common/risk_score_donut_chart.tsx | 12 +- .../entity_analytics/header/index.tsx | 68 ++++++++- .../host_risk_score/index.tsx | 48 ++++--- .../user_risk_score/index.tsx | 49 ++++--- .../components/host_overview/index.tsx | 9 +- .../components/user_overview/index.tsx | 10 +- .../overview/pages/entity_analytics.tsx | 44 +++--- .../risk_score/containers/all/index.tsx | 57 +++++++- .../containers/feature_status/index.test.ts | 2 +- .../containers/feature_status/index.ts | 4 +- .../risk_score/containers/kpi/index.tsx | 133 ++++++++---------- .../risk_score/containers/kpi/translations.ts | 15 ++ .../users/components/kpi_users/index.tsx | 14 +- .../navigation/user_risk_score_tab_body.tsx | 8 +- .../pages/navigation/user_risk_tab_body.tsx | 4 +- 27 files changed, 542 insertions(+), 198 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.tsx create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_refetch_queries.ts create mode 100644 x-pack/plugins/security_solution/public/risk_score/containers/kpi/translations.ts diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index bbb2991551f26..5a773d49134da 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -97,3 +97,11 @@ export const enum RiskSeverity { export const isUserRiskScore = (risk: HostRiskScore | UserRiskScore): risk is UserRiskScore => 'user' in risk; + +export const EMPTY_SEVERITY_COUNT = { + [RiskSeverity.critical]: 0, + [RiskSeverity.high]: 0, + [RiskSeverity.low]: 0, + [RiskSeverity.moderate]: 0, + [RiskSeverity.unknown]: 0, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected.tsx index 3f406afeebf40..b492ee51e42af 100644 --- a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected.tsx +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected.tsx @@ -5,15 +5,23 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui'; import React, { useMemo } from 'react'; import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { HeaderSection } from '../../header_section'; import * as i18n from './translations'; import { RiskScoreHeaderTitle } from './risk_score_header_title'; +import { RiskScoreRestartButton } from './risk_score_restart_button'; +import type { inputsModel } from '../../../store'; -const RiskScoresNoDataDetectedComponent = ({ entityType }: { entityType: RiskScoreEntity }) => { +const RiskScoresNoDataDetectedComponent = ({ + entityType, + refetch, +}: { + entityType: RiskScoreEntity; + refetch: inputsModel.Refetch; +}) => { const translations = useMemo( () => ({ title: @@ -26,7 +34,15 @@ const RiskScoresNoDataDetectedComponent = ({ entityType }: { entityType: RiskSco return ( } titleSize="s" /> - {translations.title}} body={translations.body} /> + {translations.title}} + body={translations.body} + actions={ + + + + } + /> ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.test.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.test.tsx new file mode 100644 index 0000000000000..16269911f6632 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.test.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 { act, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { TestProviders } from '../../../mock/test_providers'; +import { RiskScoreRestartButton } from './risk_score_restart_button'; + +import { restartRiskScoreTransforms } from './utils'; + +jest.mock('./utils'); + +describe('RiskScoreRestartButton', () => { + const mockRefetch = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + }); + describe.each([[RiskScoreEntity.host], [RiskScoreEntity.user]])('%s', (riskScoreEntity) => { + it('Renders expected children', () => { + render( + + + + ); + + expect(screen.getByTestId(`restart_${riskScoreEntity}_risk_score`)).toHaveTextContent( + 'Restart' + ); + }); + + it('calls restartRiskScoreTransforms', async () => { + render( + + + + ); + + await act(async () => { + await userEvent.click(screen.getByTestId(`restart_${riskScoreEntity}_risk_score`)); + }); + + expect(restartRiskScoreTransforms).toHaveBeenCalled(); + }); + + it('Update button state while installing', async () => { + render( + + + + ); + + userEvent.click(screen.getByTestId(`restart_${riskScoreEntity}_risk_score`)); + + await waitFor(() => { + expect(screen.getByTestId(`restart_${riskScoreEntity}_risk_score`)).toHaveProperty( + 'disabled', + true + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.tsx new file mode 100644 index 0000000000000..679bbe38c8181 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_restart_button.tsx @@ -0,0 +1,65 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { useSpaceId } from '../../../hooks/use_space_id'; +import { useKibana } from '../../../lib/kibana'; +import type { inputsModel } from '../../../store'; +import { REQUEST_NAMES, useFetch } from '../../../hooks/use_fetch'; +import { useRiskScoreToastContent } from './use_risk_score_toast_content'; +import { restartRiskScoreTransforms } from './utils'; + +const RiskScoreRestartButtonComponent = ({ + refetch, + riskScoreEntity, +}: { + refetch: inputsModel.Refetch; + riskScoreEntity: RiskScoreEntity; +}) => { + const { fetch, isLoading } = useFetch( + REQUEST_NAMES.REFRESH_RISK_SCORE, + restartRiskScoreTransforms + ); + + const spaceId = useSpaceId(); + const { renderDocLink } = useRiskScoreToastContent(riskScoreEntity); + const { http, notifications, theme } = useKibana().services; + + const onClick = useCallback(async () => { + fetch({ + http, + notifications, + refetch, + renderDocLink, + riskScoreEntity: RiskScoreEntity.host, + spaceId, + theme, + }); + }, [fetch, http, notifications, refetch, renderDocLink, spaceId, theme]); + + return ( + + + + ); +}; + +export const RiskScoreRestartButton = React.memo(RiskScoreRestartButtonComponent); +RiskScoreRestartButton.displayName = 'RiskScoreRestartButton'; diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/translations.ts b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/translations.ts index 4f389a4b2511e..dbb5cdfa87950 100644 --- a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/translations.ts @@ -40,3 +40,11 @@ export const USER_WARNING_BODY = i18n.translate( defaultMessage: `We haven't detected any user risk score data from the users in your environment.`, } ); + +export const RESTART_TOOLTIP = i18n.translate( + 'xpack.securitySolution.riskScore.usersDashboardRestartTooltip', + { + defaultMessage: + 'The risk score calculation might take a while to run. However, by pressing restart, you can force it to run immediately.', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts b/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts index 523578777f282..2d6c414090367 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts @@ -12,6 +12,7 @@ export const REQUEST_NAMES = { GET_RISK_SCORE_DEPRECATED: `${APP_UI_ID} fetch is risk score deprecated`, GET_SAVED_QUERY: `${APP_UI_ID} fetch saved query`, ENABLE_RISK_SCORE: `${APP_UI_ID} fetch enable risk score`, + REFRESH_RISK_SCORE: `${APP_UI_ID} fetch refresh risk score`, UPGRADE_RISK_SCORE: `${APP_UI_ID} fetch upgrade risk score`, } as const; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_refetch_queries.ts b/x-pack/plugins/security_solution/public/common/hooks/use_refetch_queries.ts new file mode 100644 index 0000000000000..64e8d10ec2c21 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_refetch_queries.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback, useMemo } from 'react'; +import { queriesSelector } from '../components/super_date_picker/selectors'; +import type { State, inputsModel } from '../store'; +import { InputsModelId } from '../store/inputs/constants'; +import { useDeepEqualSelector } from './use_selector'; + +export const useRefetchQueries = () => { + const getQueriesSelector = useMemo(() => queriesSelector(), []); + const queries = useDeepEqualSelector((state: State) => + getQueriesSelector(state, InputsModelId.global) + ); + + const refetchPage = useCallback(() => { + queries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); + }, [queries]); + + return refetchPage; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index c748d4d9a7545..878ffb6e5e29a 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -13,17 +13,23 @@ import { HostsKpiUniqueIps } from './unique_ips'; import type { HostsKpiProps } from './types'; import { CallOutSwitcher } from '../../../common/components/callouts'; import * as i18n from './translations'; -import { useHostRiskScore } from '../../../risk_score/containers'; import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link'; -import { RiskScoreEntity } from '../../../../common/search_strategy'; +import { getHostRiskIndex, RiskQueries, RiskScoreEntity } from '../../../../common/search_strategy'; +import { useRiskScoreFeatureStatus } from '../../../risk_score/containers/feature_status'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; export const HostsKpiComponent = React.memo( ({ filterQuery, from, indexNames, to, setQuery, skip, updateDateRange }) => { - const [loading, { isLicenseValid, isModuleEnabled }] = useHostRiskScore(); + const spaceId = useSpaceId(); + const defaultIndex = spaceId ? getHostRiskIndex(spaceId) : undefined; + const { isEnabled, isLicenseValid, isLoading } = useRiskScoreFeatureStatus( + RiskQueries.hostsRiskScore, + defaultIndex + ); return ( <> - {isLicenseValid && !isModuleEnabled && !loading && ( + {isLicenseValid && !isEnabled && !isLoading && ( <> ({ from, to }), [from, to]); const [ loading, @@ -69,7 +70,7 @@ export const HostRiskScoreQueryTabBody = ({ skip: querySkip, pagination, sort, - timerange: { from, to }, + timerange, }); const { severityCount, loading: isKpiLoading } = useHostRiskScoreKpi({ @@ -77,13 +78,11 @@ export const HostRiskScoreQueryTabBody = ({ skip: querySkip, }); - const timerange = useMemo(() => ({ from, to }), [from, to]); - if (!isModuleEnabled && !loading) { return ; } - if (isDeprecated) { + if (isDeprecated && !loading) { return ( ; + if ( + !loading && + isModuleEnabled && + severitySelectionRedux.length === 0 && + data && + data.length === 0 + ) { + return ; } return ( @@ -109,7 +114,7 @@ export const HostRiskScoreQueryTabBody = ({ refetch={refetch} setQuery={setQuery} setQuerySkip={setQuerySkip} - severityCount={severityCount} + severityCount={severityCount ?? EMPTY_SEVERITY_COUNT} totalCount={totalCount} type={type} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_tab_body.tsx index adb4e97ad13be..84b4399c1dccf 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_tab_body.tsx @@ -69,7 +69,6 @@ const HostRiskTabBodyComponent: React.FC< useQueryToggle(`${QUERY_ID} overTime`); const { toggleStatus: contributorsToggleStatus, setToggleStatus: setContributorsToggleStatus } = useQueryToggle(`${QUERY_ID} contributors`); - const [loading, { data, refetch, inspect, isDeprecated, isModuleEnabled }] = useHostRiskScore({ filterQuery, onlyLatest: false, @@ -106,7 +105,7 @@ const HostRiskTabBodyComponent: React.FC< return ; } - if (isDeprecated) { + if (isDeprecated && !loading) { return ( ; + return ; } return ( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx index 8f37f8af1ac3f..cc727efa5188e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import type { EuiBasicTableColumn } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; import { useDispatch } from 'react-redux'; import * as i18n from './translations'; @@ -29,6 +30,9 @@ const MediumShadeText = styled.span` color: ${({ theme }) => theme.eui.euiColorMediumShade}; `; +const INSTALL_JOBS_DOC = + 'https://www.elastic.co/guide/en/machine-learning/current/ml-ad-run-jobs.html'; + export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { const columns: AnomaliesColumns = useMemo( () => [ @@ -73,6 +77,14 @@ export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { return ; } + if (status === AnomalyJobStatus.uninstalled) { + return ( + + {i18n.JOB_STATUS_UNINSTALLED} + + ); + } + return {I18N_JOB_STATUS[status]}; } }, @@ -86,7 +98,6 @@ export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { const I18N_JOB_STATUS = { [AnomalyJobStatus.disabled]: i18n.JOB_STATUS_DISABLED, [AnomalyJobStatus.failed]: i18n.JOB_STATUS_FAILED, - [AnomalyJobStatus.uninstalled]: i18n.JOB_STATUS_UNINSTALLED, }; const EnableJobLink = ({ jobId }: { jobId: string }) => { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.test.tsx index fe8fd01665280..0a8abe2500e0c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.test.tsx @@ -19,24 +19,12 @@ const severityCount: SeverityCount = { [RiskSeverity.unknown]: 1, [RiskSeverity.critical]: 1, }; -const href = 'test-href'; describe('RiskScoreDonutChart', () => { - it('renders link', () => { - const { getByTestId } = render( - - {}} href={href} /> - - ); - - expect(getByTestId('view-total-button')).toBeInTheDocument(); - expect(getByTestId('view-total-button')).toHaveAttribute('href', href); - }); - it('renders legends', () => { const { getByTestId } = render( - {}} href={href} /> + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx index aad763e7ee38e..ecd3f31489d6b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx @@ -6,11 +6,9 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { MouseEventHandler } from 'react'; import React from 'react'; import type { ShapeTreeNode } from '@elastic/charts'; import styled from 'styled-components'; -import { LinkAnchor } from '../../../../common/components/links'; import type { SeverityCount } from '../../../../common/components/severity/types'; import { useRiskDonutChartData } from './use_risk_donut_chart_data'; import type { FillColor } from '../../../../common/components/charts/donutchart'; @@ -39,11 +37,9 @@ const StyledLegendItems = styled(EuiFlexItem)` interface RiskScoreDonutChartProps { severityCount: SeverityCount; - onClick: MouseEventHandler; - href: string; } -export const RiskScoreDonutChart = ({ severityCount, onClick, href }: RiskScoreDonutChartProps) => { +export const RiskScoreDonutChart = ({ severityCount }: RiskScoreDonutChartProps) => { const [donutChartData, legendItems, total] = useRiskDonutChartData(severityCount); return ( @@ -56,11 +52,7 @@ export const RiskScoreDonutChart = ({ severityCount, onClick, href }: RiskScoreD data={donutChartData ?? null} fillColor={fillColor} height={DONUT_HEIGHT} - label={ - - {i18n.TOTAL_LABEL} - - } + label={i18n.TOTAL_LABEL} title={} totalCount={total} /> diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index f94c7d52f6fce..d8f087a7cc884 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; -import { sum } from 'lodash/fp'; +import { sumBy } from 'lodash/fp'; import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; import { useHostRiskScoreKpi, useUserRiskScoreKpi } from '../../../../risk_score/containers'; import { LinkAnchor, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; @@ -25,15 +25,41 @@ import { useNotableAnomaliesSearch } from '../../../../common/components/ml/anom import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useKibana } from '../../../../common/lib/kibana'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; +import { useQueryInspector } from '../../../../common/components/page/manage_query'; const StyledEuiTitle = styled(EuiTitle)` color: ${({ theme: { eui } }) => eui.euiColorVis9}; `; +const HOST_RISK_QUERY_ID = 'hostRiskScoreKpiQuery'; +const USER_RISK_QUERY_ID = 'userRiskScoreKpiQuery'; + export const EntityAnalyticsHeader = () => { - const { severityCount: hostsSeverityCount } = useHostRiskScoreKpi({}); - const { severityCount: usersSeverityCount } = useUserRiskScoreKpi({}); const { from, to } = useGlobalTime(false); + const timerange = useMemo( + () => ({ + from, + to, + }), + [from, to] + ); + + const { + severityCount: hostsSeverityCount, + loading: hostRiskLoading, + inspect: inspectHostRiskScore, + refetch: refetchHostRiskScore, + } = useHostRiskScoreKpi({ timerange }); + + const { + severityCount: usersSeverityCount, + loading: userRiskLoading, + refetch: refetchUserRiskScore, + inspect: inspectUserRiskScore, + } = useUserRiskScoreKpi({ + timerange, + }); + const { data } = useNotableAnomaliesSearch({ skip: false, from, to }); const dispatch = useDispatch(); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); @@ -88,7 +114,33 @@ export const EntityAnalyticsHeader = () => { return [onClick, href]; }, [dispatch, getSecuritySolutionLinkProps]); - const totalAnomalies = useMemo(() => sum(data.map(({ count }) => count)), [data]); + const { deleteQuery, setQuery } = useGlobalTime(); + + useQueryInspector({ + queryId: USER_RISK_QUERY_ID, + loading: userRiskLoading, + refetch: refetchUserRiskScore, + setQuery, + deleteQuery, + inspect: inspectUserRiskScore, + }); + + useQueryInspector({ + queryId: HOST_RISK_QUERY_ID, + loading: hostRiskLoading, + refetch: refetchHostRiskScore, + setQuery, + deleteQuery, + inspect: inspectHostRiskScore, + }); + + // Anomalies are enabled if at least one job is installed + const areJobsEnabled = useMemo(() => data.some(({ jobId }) => !!jobId), [data]); + + const totalAnomalies = useMemo( + () => (areJobsEnabled ? sumBy('count', data) : '-'), + [data, areJobsEnabled] + ); const jobsUrl = useMlHref(ml, http.basePath.get(), { page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, @@ -102,7 +154,9 @@ export const EntityAnalyticsHeader = () => { - {hostsSeverityCount[RiskSeverity.critical]} + + {hostsSeverityCount ? hostsSeverityCount[RiskSeverity.critical] : '-'} + @@ -122,7 +176,9 @@ export const EntityAnalyticsHeader = () => { - {usersSeverityCount[RiskSeverity.critical]} + + {usersSeverityCount ? usersSeverityCount[RiskSeverity.critical] : '-'} + diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx index 123f190446255..1db3fd6e5d873 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx @@ -19,7 +19,7 @@ import { HeaderSection } from '../../../../common/components/header_section'; import { useHostRiskScore, useHostRiskScoreKpi } from '../../../../risk_score/containers'; import type { RiskSeverity } from '../../../../../common/search_strategy'; -import { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy'; import { SecurityPageName } from '../../../../app/types'; import * as i18n from './translations'; import { generateSeverityFilter } from '../../../../hosts/store/helpers'; @@ -34,8 +34,10 @@ import { RISKY_HOSTS_EXTERNAL_DOC_LINK } from '../../../../../common/constants'; import { EntityAnalyticsHostRiskScoreDisable } from '../../../../common/components/risk_score/risk_score_disabled/host_risk_score_disabled'; import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; +import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries'; const TABLE_QUERY_ID = 'hostRiskDashboardTable'; +const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; const EntityAnalyticsHostRiskScoresComponent = () => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); @@ -52,11 +54,6 @@ const EntityAnalyticsHostRiskScoresComponent = () => { return filter ? JSON.stringify(filter.query) : undefined; }, [selectedSeverity]); - const { severityCount, loading: isKpiLoading } = useHostRiskScoreKpi({ - filterQuery: severityFilter, - skip: !toggleStatus, - }); - const timerange = useMemo( () => ({ from, @@ -65,6 +62,25 @@ const EntityAnalyticsHostRiskScoresComponent = () => { [from, to] ); + const { + severityCount, + loading: isKpiLoading, + refetch: refetchKpi, + inspect: inspectKpi, + } = useHostRiskScoreKpi({ + filterQuery: severityFilter, + skip: !toggleStatus, + timerange, + }); + + useQueryInspector({ + queryId: HOST_RISK_KPI_QUERY_ID, + loading: isKpiLoading, + refetch: refetchKpi, + setQuery, + deleteQuery, + inspect: inspectKpi, + }); const [ isTableLoading, { data, inspect, refetch, isDeprecated, isLicenseValid, isModuleEnabled }, @@ -107,26 +123,28 @@ const EntityAnalyticsHostRiskScoresComponent = () => { return [onClick, href]; }, [dispatch, getSecuritySolutionLinkProps]); + const refreshPage = useRefetchQueries(); + if (!isLicenseValid) { return null; } - if (!isModuleEnabled) { - return ; + if (!isModuleEnabled && !isTableLoading) { + return ; } - if (isDeprecated) { + if (isDeprecated && !isTableLoading) { return ( ); } if (isModuleEnabled && selectedSeverity.length === 0 && data && data.length === 0) { - return ; + return ; } return ( @@ -156,7 +174,7 @@ const EntityAnalyticsHostRiskScoresComponent = () => { @@ -176,11 +194,7 @@ const EntityAnalyticsHostRiskScoresComponent = () => { {toggleStatus && ( - + { const { deleteQuery, setQuery, from, to } = useGlobalTime(); @@ -52,11 +54,6 @@ const EntityAnalyticsUserRiskScoresComponent = () => { return filter ? JSON.stringify(filter.query) : undefined; }, [selectedSeverity]); - const { severityCount, loading: isKpiLoading } = useUserRiskScoreKpi({ - filterQuery: severityFilter, - skip: !toggleStatus, - }); - const timerange = useMemo( () => ({ from, @@ -65,6 +62,17 @@ const EntityAnalyticsUserRiskScoresComponent = () => { [from, to] ); + const { + severityCount, + loading: isKpiLoading, + refetch: refetchKpi, + inspect: inspectKpi, + } = useUserRiskScoreKpi({ + filterQuery: severityFilter, + skip: !toggleStatus, + timerange, + }); + const [ isTableLoading, { data, inspect, refetch, isLicenseValid, isDeprecated, isModuleEnabled }, @@ -87,6 +95,15 @@ const EntityAnalyticsUserRiskScoresComponent = () => { inspect, }); + useQueryInspector({ + queryId: USER_RISK_KPI_QUERY_ID, + loading: isKpiLoading, + refetch: refetchKpi, + setQuery, + deleteQuery, + inspect: inspectKpi, + }); + useEffect(() => { setUpdatedAt(Date.now()); }, [isTableLoading, isKpiLoading]); // Update the time when data loads @@ -106,26 +123,28 @@ const EntityAnalyticsUserRiskScoresComponent = () => { return [onClick, href]; }, [dispatch, getSecuritySolutionLinkProps]); + const refreshPage = useRefetchQueries(); + if (!isLicenseValid) { return null; } - if (!isModuleEnabled) { - return ; + if (!isModuleEnabled && !isTableLoading) { + return ; } - if (isDeprecated) { + if (isDeprecated && !isTableLoading) { return ( ); } if (isModuleEnabled && selectedSeverity.length === 0 && data && data.length === 0) { - return ; + return ; } return ( @@ -155,7 +174,7 @@ const EntityAnalyticsUserRiskScoresComponent = () => { @@ -175,11 +194,7 @@ const EntityAnalyticsUserRiskScoresComponent = () => { {toggleStatus && ( - + ( ); const { from, to } = useGlobalTime(); + const timerange = useMemo( + () => ({ + from, + to, + }), + [from, to] + ); const [_, { data: hostRisk, isLicenseValid }] = useHostRiskScore({ filterQuery, skip: hostName == null, - timerange: { to, from }, + timerange, }); const getDefaultRenderer = useCallback( diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx index e635f94e51d51..68b40f581d384 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx @@ -85,10 +85,18 @@ export const UserOverview = React.memo( const { from, to } = useGlobalTime(); + const timerange = useMemo( + () => ({ + from, + to, + }), + [from, to] + ); + const [_, { data: userRisk, isLicenseValid }] = useUserRiskScore({ filterQuery, skip: userName == null, - timerange: { to, from }, + timerange, }); const getDefaultRenderer = useCallback( diff --git a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx index bc478eeecc4b1..3ef48810d6094 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx @@ -27,15 +27,15 @@ import { InputsModelId } from '../../common/store/inputs/constants'; const EntityAnalyticsComponent = () => { const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); + const { isPlatinumOrTrialLicense, capabilitiesFetched } = useMlCapabilities(); - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; return ( <> {indicesExist ? ( <> - {isPlatinumOrTrialLicense && ( + {isPlatinumOrTrialLicense && capabilitiesFetched && ( { /> )} - {isPlatinumOrTrialLicense ? ( - isSourcererLoading ? ( - - ) : ( - - - - + {!isPlatinumOrTrialLicense && capabilitiesFetched ? ( + + ) : isSourcererLoading ? ( + + ) : ( + + + + - - - + + + - - - + + + - - - - - ) - ) : ( - + + + + )} diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx index e1f1b7ecd13ba..9c5671640f87a 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx @@ -12,8 +12,8 @@ import { createFilter } from '../../../common/containers/helpers'; import type { RiskScoreSortField, StrategyResponseType } from '../../../../common/search_strategy'; import { getHostRiskIndex, - RiskQueries, getUserRiskIndex, + RiskQueries, } from '../../../../common/search_strategy'; import type { ESQuery } from '../../../../common/typed_json'; @@ -64,7 +64,14 @@ export const initialResult: Omit< }; export const useHostRiskScore = (params?: UseRiskScoreParams) => { - const { timerange, onlyLatest, filterQuery, sort, skip = false, pagination } = params ?? {}; + const { + timerange, + onlyLatest = true, + filterQuery, + sort, + skip = false, + pagination, + } = params ?? {}; const spaceId = useSpaceId(); const defaultIndex = spaceId ? getHostRiskIndex(spaceId, onlyLatest) : undefined; @@ -81,7 +88,14 @@ export const useHostRiskScore = (params?: UseRiskScoreParams) => { }; export const useUserRiskScore = (params?: UseRiskScoreParams) => { - const { timerange, onlyLatest, filterQuery, sort, skip = false, pagination } = params ?? {}; + const { + timerange, + onlyLatest = true, + filterQuery, + sort, + skip = false, + pagination, + } = params ?? {}; const spaceId = useSpaceId(); const defaultIndex = spaceId ? getUserRiskIndex(spaceId, onlyLatest) : undefined; @@ -99,6 +113,7 @@ export const useUserRiskScore = (params?: UseRiskScoreParams) => { const useRiskScore = ({ timerange, + onlyLatest, filterQuery, sort, skip = false, @@ -167,6 +182,11 @@ const useRiskScore = (timerange ? { to: timerange.to, from: timerange.from, interval: '' } : undefined), + [timerange] + ); + const riskScoreRequest = useMemo( () => defaultIndex @@ -182,9 +202,19 @@ const useRiskScore = { @@ -196,10 +226,25 @@ const useRiskScore = { - if (!skip && riskScoreRequest != null && isLicenseValid && isEnabled && !isDeprecated) { + if ( + !skip && + !isDeprecatedLoading && + riskScoreRequest != null && + isLicenseValid && + isEnabled && + !isDeprecated + ) { search(riskScoreRequest); } - }, [isEnabled, isDeprecated, isLicenseValid, riskScoreRequest, search, skip]); + }, [ + isEnabled, + isDeprecated, + isLicenseValid, + isDeprecatedLoading, + riskScoreRequest, + search, + skip, + ]); return [loading || isDeprecatedLoading, riskScoreResponse]; }; diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts index cb5b549894044..619de81b36b9b 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts +++ b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts @@ -37,7 +37,7 @@ describe(`risk score feature status`, () => { isDeprecated: true, isLicenseValid: true, isEnabled: true, - isLoading: false, + isLoading: true, }; test('does not search if license is not valid, and initial isDeprecated state is false', () => { diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts index cf5a8feb0ee0d..0099ed0df3f09 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts +++ b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts @@ -27,7 +27,7 @@ export const useRiskScoreFeatureStatus = ( factoryQueryType: RiskQueries.hostsRiskScore | RiskQueries.usersRiskScore, defaultIndex?: string ): RiskScoresFeatureStatus => { - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const { isPlatinumOrTrialLicense, capabilitiesFetched } = useMlCapabilities(); const entity = useMemo( () => factoryQueryType === RiskQueries.hostsRiskScore ? RiskScoreEntity.host : RiskScoreEntity.user, @@ -66,7 +66,7 @@ export const useRiskScoreFeatureStatus = ( return { error, - isLoading, + isLoading: isLoading || !capabilitiesFetched || defaultIndex == null, refetch: searchIndexStatus, isLicenseValid: isPlatinumOrTrialLicense, ...response, diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx index 2c137a8bf1e06..340ec3347a4f7 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx @@ -5,77 +5,35 @@ * 2.0. */ -import type { Observable } from 'rxjs'; -import { filter } from 'rxjs/operators'; import { useEffect, useMemo } from 'react'; -import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; -import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/common'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { createFilter } from '../../../common/containers/helpers'; - -import type { - KpiRiskScoreRequestOptions, - KpiRiskScoreStrategyResponse, -} from '../../../../common/search_strategy'; + import { getHostRiskIndex, getUserRiskIndex, RiskQueries, RiskSeverity, RiskScoreEntity, + EMPTY_SEVERITY_COUNT, } from '../../../../common/search_strategy'; - -import { useKibana } from '../../../common/lib/kibana'; +import * as i18n from './translations'; import { isIndexNotFoundError } from '../../../common/utils/exceptions'; import type { ESTermQuery } from '../../../../common/typed_json'; import type { SeverityCount } from '../../../common/components/severity/types'; import { useSpaceId } from '../../../common/hooks/use_space_id'; import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; - -type GetHostRiskScoreProps = KpiRiskScoreRequestOptions & { - data: DataPublicPluginStart; - signal: AbortSignal; -}; - -const getRiskScoreKpi = ({ - data, - defaultIndex, - signal, - filterQuery, - entity, -}: GetHostRiskScoreProps): Observable => - data.search.search( - { - defaultIndex, - factoryQueryType: RiskQueries.kpiRiskScore, - filterQuery: createFilter(filterQuery), - entity, - }, - { - strategy: 'securitySolutionSearchStrategy', - abortSignal: signal, - } - ); - -const getRiskScoreKpiComplete = ( - props: GetHostRiskScoreProps -): Observable => { - return getRiskScoreKpi(props).pipe( - filter((response) => { - return isErrorResponse(response) || isCompleteResponse(response); - }) - ); -}; - -const getRiskScoreKpiWithOptionalSignal = withOptionalSignal(getRiskScoreKpiComplete); - -const useRiskScoreKpiComplete = () => useObservable(getRiskScoreKpiWithOptionalSignal); +import { useSearchStrategy } from '../../../common/containers/use_search_strategy'; +import type { InspectResponse } from '../../../types'; +import type { inputsModel } from '../../../common/store'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; interface RiskScoreKpi { error: unknown; isModuleDisabled: boolean; - severityCount: SeverityCount; + severityCount?: SeverityCount; loading: boolean; + refetch: inputsModel.Refetch; + inspect: InspectResponse; + timerange?: { to: string; from: string }; } type UseHostRiskScoreKpiProps = Omit< @@ -90,6 +48,7 @@ type UseUserRiskScoreKpiProps = Omit< export const useUserRiskScoreKpi = ({ filterQuery, skip, + timerange, }: UseUserRiskScoreKpiProps): RiskScoreKpi => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getUserRiskIndex(spaceId) : undefined; @@ -101,12 +60,14 @@ export const useUserRiskScoreKpi = ({ defaultIndex, entity: RiskScoreEntity.user, featureEnabled: isPlatinumOrTrialLicense, + timerange, }); }; export const useHostRiskScoreKpi = ({ filterQuery, skip, + timerange, }: UseHostRiskScoreKpiProps): RiskScoreKpi => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getHostRiskIndex(spaceId) : undefined; @@ -118,6 +79,7 @@ export const useHostRiskScoreKpi = ({ defaultIndex, entity: RiskScoreEntity.host, featureEnabled: isPlatinumOrTrialLicense, + timerange, }); }; @@ -127,6 +89,7 @@ interface UseRiskScoreKpiProps { defaultIndex: string | undefined; entity: RiskScoreEntity; featureEnabled: boolean; + timerange?: { to: string; from: string }; } const useRiskScoreKpi = ({ @@ -135,33 +98,59 @@ const useRiskScoreKpi = ({ defaultIndex, entity, featureEnabled, + timerange, }: UseRiskScoreKpiProps): RiskScoreKpi => { - const { error, result, start, loading } = useRiskScoreKpiComplete(); - const { data } = useKibana().services; + const { addError } = useAppToasts(); + + const { loading, result, search, refetch, inspect, error } = + useSearchStrategy({ + factoryQueryType: RiskQueries.kpiRiskScore, + initialResult: { + kpiRiskScore: EMPTY_SEVERITY_COUNT, + }, + abort: skip, + showErrorToast: false, + }); + const isModuleDisabled = !!error && isIndexNotFoundError(error); useEffect(() => { if (!skip && defaultIndex && featureEnabled) { - start({ - data, + search({ filterQuery, defaultIndex: [defaultIndex], entity, }); } - }, [data, defaultIndex, start, filterQuery, skip, entity, featureEnabled]); - - const severityCount = useMemo( - () => ({ - [RiskSeverity.unknown]: 0, - [RiskSeverity.low]: 0, - [RiskSeverity.moderate]: 0, - [RiskSeverity.high]: 0, - [RiskSeverity.critical]: 0, - ...(result?.kpiRiskScore ?? {}), - }), - [result] - ); - - return { error, severityCount, loading, isModuleDisabled }; + }, [defaultIndex, search, filterQuery, skip, entity, featureEnabled]); + + // since query does not take timerange arg, we need to manually refetch when time range updates + useEffect(() => { + refetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timerange?.to, timerange?.from]); + + useEffect(() => { + if (error) { + if (!isIndexNotFoundError(error)) { + addError(error, { title: i18n.FAIL_RISK_SCORE }); + } + } + }, [addError, error]); + + const severityCount = useMemo(() => { + if (loading || error) { + return undefined; + } + + return { + [RiskSeverity.unknown]: result.kpiRiskScore[RiskSeverity.unknown] ?? 0, + [RiskSeverity.low]: result.kpiRiskScore[RiskSeverity.low] ?? 0, + [RiskSeverity.moderate]: result.kpiRiskScore[RiskSeverity.moderate] ?? 0, + [RiskSeverity.high]: result.kpiRiskScore[RiskSeverity.high] ?? 0, + [RiskSeverity.critical]: result.kpiRiskScore[RiskSeverity.critical] ?? 0, + }; + }, [result, loading, error]); + + return { error, severityCount, loading, isModuleDisabled, refetch, inspect }; }; diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/translations.ts b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/translations.ts new file mode 100644 index 0000000000000..83b402220f3d4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/translations.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. + */ + +import { i18n } from '@kbn/i18n'; + +export const FAIL_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.riskScore.kpi.failSearchDescription', + { + defaultMessage: `Failed to run search on risk score`, + } +); diff --git a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx index 43933340fac7f..4344c52e10d4b 100644 --- a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx @@ -12,19 +12,25 @@ import type { UsersKpiProps } from './types'; import { UsersKpiAuthentications } from './authentications'; import { TotalUsersKpi } from './total_users'; -import { useUserRiskScore } from '../../../risk_score/containers'; import { CallOutSwitcher } from '../../../common/components/callouts'; import * as i18n from './translations'; import { RiskScoreDocLink } from '../../../common/components/risk_score/risk_score_onboarding/risk_score_doc_link'; -import { RiskScoreEntity } from '../../../../common/search_strategy'; +import { getUserRiskIndex, RiskQueries, RiskScoreEntity } from '../../../../common/search_strategy'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { useRiskScoreFeatureStatus } from '../../../risk_score/containers/feature_status'; export const UsersKpiComponent = React.memo( ({ filterQuery, from, indexNames, to, setQuery, skip, updateDateRange }) => { - const [loading, { isLicenseValid, isModuleEnabled }] = useUserRiskScore(); + const spaceId = useSpaceId(); + const defaultIndex = spaceId ? getUserRiskIndex(spaceId) : undefined; + const { isEnabled, isLicenseValid, isLoading } = useRiskScoreFeatureStatus( + RiskQueries.usersRiskScore, + defaultIndex + ); return ( <> - {isLicenseValid && !isModuleEnabled && !loading && ( + {isLicenseValid && !isEnabled && !isLoading && ( <> ; } - if (isDeprecated) { + if (isDeprecated && !loading) { return ( ; + return ; } return ( @@ -110,7 +110,7 @@ export const UserRiskScoreQueryTabBody = ({ refetch={refetch} setQuery={setQuery} setQuerySkip={setQuerySkip} - severityCount={severityCount} + severityCount={severityCount ?? EMPTY_SEVERITY_COUNT} totalCount={totalCount} type={type} /> diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_tab_body.tsx b/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_tab_body.tsx index d8123148480c6..fed651759df7c 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_tab_body.tsx @@ -94,7 +94,7 @@ const UserRiskTabBodyComponent: React.FC< return ; } - if (isDeprecated) { + if (isDeprecated && !loading) { return ( ; + return ; } const lastUsertRiskItem: UserRiskScore | null = From 36ceb94d42110983125120f7e1f0b5d31ecdbdfc Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Mon, 26 Sep 2022 10:12:55 -0500 Subject: [PATCH 021/172] [TIP] Rename lib folders to utils (#141781) --- .../indicator_field_value/indicator_field_value.tsx | 2 +- .../indicator_value_actions/indicator_value_actions.tsx | 2 +- .../components/indicators_flyout/indicators_flyout.tsx | 2 +- .../highlighted_values_table/highlighted_values_table.tsx | 2 +- .../indicators_flyout_overview.tsx | 2 +- .../indicators_flyout_table.test.tsx | 2 +- .../components/indicators_table/cell_actions.tsx | 2 +- .../components/indicators_table/indicators_table.tsx | 2 +- .../modules/indicators/hooks/use_aggregated_indicators.ts | 4 ++-- .../public/modules/indicators/hooks/use_indicators.ts | 4 ++-- .../modules/indicators/{lib => utils}/display_name.test.ts | 0 .../modules/indicators/{lib => utils}/display_name.ts | 0 .../modules/indicators/{lib => utils}/field_value.test.ts | 0 .../modules/indicators/{lib => utils}/field_value.ts | 0 .../modules/indicators/{lib => utils}/get_field_schema.ts | 0 .../indicators/{lib => utils}/get_indicators_query.ts | 0 .../indicators/{lib => utils}/get_runtime_mappings.ts | 0 .../modules/indicators/{lib => utils}/unwrap_value.test.ts | 0 .../modules/indicators/{lib => utils}/unwrap_value.ts | 0 .../modules/query_bar/components/filter_in/filter_in.tsx | 7 +++++-- .../modules/query_bar/components/filter_out/filter_out.tsx | 7 +++++-- .../public/modules/query_bar/{lib => utils}/filter.test.ts | 0 .../public/modules/query_bar/{lib => utils}/filter.ts | 0 .../components/add_to_timeline/add_to_timeline.tsx | 7 +++++-- .../modules/timeline/hooks/use_investigate_in_timeline.ts | 6 +++--- .../modules/timeline/{lib => utils}/data_provider.test.ts | 0 .../modules/timeline/{lib => utils}/data_provider.ts | 0 27 files changed, 30 insertions(+), 21 deletions(-) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/display_name.test.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/display_name.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/field_value.test.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/field_value.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/get_field_schema.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/get_indicators_query.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/get_runtime_mappings.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/unwrap_value.test.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{lib => utils}/unwrap_value.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/query_bar/{lib => utils}/filter.test.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/query_bar/{lib => utils}/filter.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/timeline/{lib => utils}/data_provider.test.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/timeline/{lib => utils}/data_provider.ts (100%) diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx index c0b46cd1b44b0..55dfa883c30ad 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx @@ -10,7 +10,7 @@ import { useFieldTypes } from '../../../../hooks/use_field_types'; import { EMPTY_VALUE } from '../../../../../common/constants'; import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; import { DateFormatter } from '../../../../components/date_formatter'; -import { unwrapValue } from '../../lib/unwrap_value'; +import { unwrapValue } from '../../utils/unwrap_value'; export interface IndicatorFieldValueProps { /** diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx index f3c5fd7c2e7d8..42fd697ec5395 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx @@ -12,7 +12,7 @@ import { Indicator } from '../../../../../common/types/indicator'; import { FilterIn } from '../../../query_bar/components/filter_in'; import { FilterOut } from '../../../query_bar/components/filter_out'; import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../lib/field_value'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../utils/field_value'; export const TIMELINE_BUTTON_TEST_ID = 'TimelineButton'; export const FILTER_IN_BUTTON_TEST_ID = 'FilterInButton'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx index 303059c61fc69..86949da21a814 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx @@ -26,7 +26,7 @@ import { DateFormatter } from '../../../../components/date_formatter/date_format import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; import { IndicatorsFlyoutJson } from './tabs/indicators_flyout_json/indicators_flyout_json'; import { IndicatorsFlyoutTable } from './tabs/indicators_flyout_table/indicators_flyout_table'; -import { unwrapValue } from '../../lib/unwrap_value'; +import { unwrapValue } from '../../utils/unwrap_value'; import { IndicatorsFlyoutOverview } from './tabs/indicators_flyout_overview'; export const TITLE_TEST_ID = 'tiIndicatorFlyoutTitle'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx index d7cf25dca3239..d9af696065db1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx @@ -7,7 +7,7 @@ import React, { useMemo, VFC } from 'react'; import { Indicator, RawIndicatorFieldId } from '../../../../../../../../../common/types/indicator'; -import { unwrapValue } from '../../../../../../lib/unwrap_value'; +import { unwrapValue } from '../../../../../../utils/unwrap_value'; import { IndicatorFieldsTable } from '../../../../components/indicator_fields_table'; /** diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx index c1a0468822269..a7551398b62c4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx @@ -19,7 +19,7 @@ import React, { useMemo } from 'react'; import { VFC } from 'react'; import { EMPTY_VALUE } from '../../../../../../../common/constants'; import { Indicator, RawIndicatorFieldId } from '../../../../../../../common/types/indicator'; -import { unwrapValue } from '../../../../lib/unwrap_value'; +import { unwrapValue } from '../../../../utils/unwrap_value'; import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; import { IndicatorBlock } from './components/block'; import { HighlightedValuesTable } from './components/highlighted_values_table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx index c91ce0e6aa89c..5e1264b1b2e4a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx @@ -14,7 +14,7 @@ import { RawIndicatorFieldId, } from '../../../../../../../common/types/indicator'; import { IndicatorsFlyoutTable, TABLE_TEST_ID } from './indicators_flyout_table'; -import { unwrapValue } from '../../../../lib/unwrap_value'; +import { unwrapValue } from '../../../../utils/unwrap_value'; import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; const mockIndicator: Indicator = generateMockIndicator(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx index 0f111f96c4c25..38f22fe34556e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx @@ -11,7 +11,7 @@ import { ComponentType } from '../../../../../common/types/component_type'; import { Indicator } from '../../../../../common/types/indicator'; import { Pagination } from '../../hooks/use_indicators'; import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../lib/field_value'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../utils/field_value'; import { FilterIn } from '../../../query_bar/components/filter_in'; import { FilterOut } from '../../../query_bar/components/filter_out'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx index 072fd42db696e..3e3035c8c43e4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx @@ -28,7 +28,7 @@ import { Pagination } from '../../hooks/use_indicators'; import { useToolbarOptions } from './hooks/use_toolbar_options'; import { ColumnSettingsValue } from './hooks/use_column_settings'; import { useFieldTypes } from '../../../../hooks/use_field_types'; -import { getFieldSchema } from '../../lib/get_field_schema'; +import { getFieldSchema } from '../../utils/get_field_schema'; export interface IndicatorsTableProps { indicators: Indicator[]; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts index 02230defa9688..ab583fb0ed95a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts @@ -23,8 +23,8 @@ import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates import { useKibana } from '../../../hooks/use_kibana'; import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { getRuntimeMappings } from '../lib/get_runtime_mappings'; -import { getIndicatorsQuery } from '../lib/get_indicators_query'; +import { getRuntimeMappings } from '../utils/get_runtime_mappings'; +import { getIndicatorsQuery } from '../utils/get_indicators_query'; export interface UseAggregatedIndicatorsParam { /** diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts index e44e2e05ca230..f06fb03b27d5c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts @@ -18,8 +18,8 @@ import { useInspector } from '../../../hooks/use_inspector'; import { Indicator } from '../../../../common/types/indicator'; import { useKibana } from '../../../hooks/use_kibana'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { getRuntimeMappings } from '../lib/get_runtime_mappings'; -import { getIndicatorsQuery } from '../lib/get_indicators_query'; +import { getRuntimeMappings } from '../utils/get_runtime_mappings'; +import { getIndicatorsQuery } from '../utils/get_indicators_query'; const PAGE_SIZES = [10, 25, 50]; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/display_name.test.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/display_name.test.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/display_name.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/display_name.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/field_value.test.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/field_value.test.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/field_value.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/field_value.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_field_schema.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_field_schema.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_field_schema.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_field_schema.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_indicators_query.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_indicators_query.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_runtime_mappings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_runtime_mappings.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_runtime_mappings.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_runtime_mappings.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/unwrap_value.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/unwrap_value.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/unwrap_value.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/lib/unwrap_value.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx index d801f46915921..d6d62b3e35380 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx @@ -11,8 +11,11 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem, EuiToolTip } from '@ import { Filter } from '@kbn/es-query'; import { ComponentType } from '../../../../../common/types/component_type'; import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; -import { FilterIn as FilterInConst, updateFiltersArray } from '../../lib/filter'; +import { + fieldAndValueValid, + getIndicatorFieldAndValue, +} from '../../../indicators/utils/field_value'; +import { FilterIn as FilterInConst, updateFiltersArray } from '../../utils/filter'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx index 67f28a66b486e..afa1bc02a6ba9 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx @@ -11,8 +11,11 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem, EuiToolTip } from '@ import { Filter } from '@kbn/es-query'; import { ComponentType } from '../../../../../common/types/component_type'; import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; -import { FilterOut as FilterOutConst, updateFiltersArray } from '../../lib/filter'; +import { + fieldAndValueValid, + getIndicatorFieldAndValue, +} from '../../../indicators/utils/field_value'; +import { FilterOut as FilterOutConst, updateFiltersArray } from '../../utils/filter'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/lib/filter.test.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/query_bar/lib/filter.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/lib/filter.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/query_bar/lib/filter.ts rename to x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx index 2c0d2ec6c5f37..5b33a3bfeaa35 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx @@ -12,9 +12,12 @@ import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui/src/components/butto import { EuiContextMenuItem, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { generateDataProvider } from '../../lib/data_provider'; +import { generateDataProvider } from '../../utils/data_provider'; import { ComponentType } from '../../../../../common/types/component_type'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; +import { + fieldAndValueValid, + getIndicatorFieldAndValue, +} from '../../../indicators/utils/field_value'; import { useKibana } from '../../../../hooks/use_kibana'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts index 904a0afc4fd93..4f79990d00c34 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts @@ -8,10 +8,10 @@ import { useContext } from 'react'; import moment from 'moment'; import { DataProvider } from '@kbn/timelines-plugin/common'; -import { generateDataProvider } from '../lib/data_provider'; +import { generateDataProvider } from '../utils/data_provider'; import { SecuritySolutionContext } from '../../../containers/security_solution_context'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/lib/field_value'; -import { unwrapValue } from '../../indicators/lib/unwrap_value'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/utils/field_value'; +import { unwrapValue } from '../../indicators/utils/unwrap_value'; import { Indicator, IndicatorFieldEventEnrichmentMap, diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.test.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/utils/data_provider.test.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/timeline/utils/data_provider.test.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/utils/data_provider.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.ts rename to x-pack/plugins/threat_intelligence/public/modules/timeline/utils/data_provider.ts From 636f9239fd435830cf028882f16495c84c52d29b Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Mon, 26 Sep 2022 10:45:35 -0500 Subject: [PATCH 022/172] [Security Solution] add endpoint rbac (#140865) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/authz.test.ts | 85 +++++ x-pack/plugins/fleet/common/authz.ts | 82 +++++ .../plugins/fleet/common/constants/authz.ts | 26 ++ .../plugins/fleet/common/constants/index.ts | 1 + x-pack/plugins/fleet/common/index.ts | 2 + x-pack/plugins/fleet/public/plugin.ts | 27 +- .../plugins/fleet/server/constants/index.ts | 2 + .../plugins/fleet/server/routes/security.ts | 28 +- .../common/endpoint/service/authz/authz.ts | 6 +- .../common/experimental_features.ts | 5 + .../endpoint/use_endpoint_privileges.test.ts | 3 + .../endpoint/use_endpoint_privileges.ts | 14 +- .../public/management/links.test.ts | 16 + .../public/management/links.ts | 23 +- .../endpoint/endpoint_app_context_services.ts | 20 +- .../server/endpoint/mocks.ts | 1 + .../security_solution/server/features.ts | 340 +++++++++++++++++- .../security_solution/server/plugin.ts | 5 +- .../server/request_context_factory.ts | 10 +- 19 files changed, 658 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/fleet/common/authz.test.ts create mode 100644 x-pack/plugins/fleet/common/constants/authz.ts diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts new file mode 100644 index 0000000000000..ced783c6d4adb --- /dev/null +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -0,0 +1,85 @@ +/* + * 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 { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; + +import { + calculatePackagePrivilegesFromCapabilities, + calculatePackagePrivilegesFromKibanaPrivileges, +} from './authz'; +import { ENDPOINT_PRIVILEGES } from './constants'; + +const SECURITY_SOLUTION_ID = DEFAULT_APP_CATEGORIES.security.id; + +function generateActions(privileges: string[] = [], overrides: Record = {}) { + return privileges.reduce((acc, privilege) => { + const executePackageAction = overrides[privilege] || false; + + return { + ...acc, + [privilege]: { + executePackageAction, + }, + }; + }, {}); +} + +describe('fleet authz', () => { + describe('calculatePackagePrivilegesFromCapabilities', () => { + it('calculates privileges correctly', () => { + const endpointCapabilities = { + writeEndpointList: true, + writeTrustedApplications: true, + writePolicyManagement: false, + readPolicyManagement: true, + writeHostIsolationExceptions: true, + writeHostIsolation: false, + }; + const expected = { + endpoint: { + actions: generateActions(ENDPOINT_PRIVILEGES, endpointCapabilities), + }, + }; + const actual = calculatePackagePrivilegesFromCapabilities({ + navLinks: {}, + management: {}, + catalogue: {}, + siem: endpointCapabilities, + }); + + expect(actual).toEqual(expected); + }); + }); + + describe('calculatePackagePrivilegesFromKibanaPrivileges', () => { + it('calculates privileges correctly', () => { + const endpointPrivileges = [ + { privilege: `${SECURITY_SOLUTION_ID}-writeEndpointList`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-writeTrustedApplications`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-writePolicyManagement`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-readPolicyManagement`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolationExceptions`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolation`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: true }, + ]; + const expected = { + endpoint: { + actions: generateActions(ENDPOINT_PRIVILEGES, { + writeEndpointList: true, + writeTrustedApplications: true, + writePolicyManagement: false, + readPolicyManagement: true, + writeHostIsolationExceptions: true, + writeHostIsolation: false, + }), + }, + }; + const actual = calculatePackagePrivilegesFromKibanaPrivileges(endpointPrivileges); + expect(actual).toEqual(expected); + }); + }); +}); diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index c8ae7b76d0403..72812576e13b3 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -5,6 +5,11 @@ * 2.0. */ +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import type { Capabilities } from '@kbn/core-capabilities-common'; + +import { ENDPOINT_PRIVILEGES } from './constants'; + export interface FleetAuthz { fleet: { all: boolean; @@ -27,6 +32,16 @@ export interface FleetAuthz { readIntegrationPolicies: boolean; writeIntegrationPolicies: boolean; }; + + packagePrivileges?: { + [packageName: string]: { + actions: { + [key: string]: { + executePackageAction: boolean; + }; + }; + }; + }; } interface CalculateParams { @@ -72,3 +87,70 @@ export const calculateAuthz = ({ writeIntegrationPolicies: fleet.all && integrations.all, }, }); + +export function calculatePackagePrivilegesFromCapabilities( + capabilities: Capabilities | undefined +): FleetAuthz['packagePrivileges'] { + if (!capabilities) { + return {}; + } + + const endpointActions = ENDPOINT_PRIVILEGES.reduce((acc, privilege) => { + return { + ...acc, + [privilege]: { + executePackageAction: capabilities.siem[privilege] || false, + }, + }; + }, {}); + + return { + endpoint: { + actions: endpointActions, + }, + }; +} + +function getAuthorizationFromPrivileges( + kibanaPrivileges: Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }>, + searchPrivilege: string +): boolean { + const privilege = kibanaPrivileges.find((p) => + p.privilege.endsWith(`${DEFAULT_APP_CATEGORIES.security.id}-${searchPrivilege}`) + ); + return privilege?.authorized || false; +} + +export function calculatePackagePrivilegesFromKibanaPrivileges( + kibanaPrivileges: + | Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }> + | undefined +): FleetAuthz['packagePrivileges'] { + if (!kibanaPrivileges || !kibanaPrivileges.length) { + return {}; + } + + const endpointActions = ENDPOINT_PRIVILEGES.reduce((acc, privilege: string) => { + const kibanaPrivilege = getAuthorizationFromPrivileges(kibanaPrivileges, privilege); + return { + ...acc, + [privilege]: { + executePackageAction: kibanaPrivilege, + }, + }; + }, {}); + + return { + endpoint: { + actions: endpointActions, + }, + }; +} diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts new file mode 100644 index 0000000000000..3bf1aeb46adc8 --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ENDPOINT_PRIVILEGES = [ + 'writeEndpointList', + 'readEndpointList', + 'writeTrustedApplications', + 'readTrustedApplications', + 'writeHostIsolationExceptions', + 'readHostIsolationExceptions', + 'writeBlocklist', + 'readBlocklist', + 'writeEventFilters', + 'readEventFilters', + 'writePolicyManagement', + 'readPolicyManagement', + 'writeActionsLogManagement', + 'readActionsLogManagement', + 'writeHostIsolation', + 'writeProcessOperations', + 'writeFileOperations', +]; diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index 615a48f7493cf..955abb6b7456c 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -16,6 +16,7 @@ export * from './enrollment_api_key'; export * from './settings'; export * from './preconfiguration'; export * from './download_source'; +export * from './authz'; // TODO: This is the default `index.max_result_window` ES setting, which dictates // the maximum amount of results allowed to be returned from a search. It's possible diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index d9e3fd8b9ceb6..e8995b4bf6b74 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -48,6 +48,8 @@ export { // Should probably be removed SO_SEARCH_LIMIT, // Statuses + // Authz + ENDPOINT_PRIVILEGES, } from './constants'; export { // Route services diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 5b5a31461c911..b1d845aa9e52f 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -46,7 +46,7 @@ import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, setupRouteService, appRoutesService } from '../common'; -import { calculateAuthz } from '../common/authz'; +import { calculateAuthz, calculatePackagePrivilegesFromCapabilities } from '../common/authz'; import { parseExperimentalConfigValue } from '../common/experimental_features'; import type { CheckPermissionsResponse, PostFleetSetupResponse } from '../common/types'; import type { FleetAuthz } from '../common'; @@ -277,17 +277,20 @@ export class FleetPlugin implements Plugin { const permissionsResponse = await getPermissions(); diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 87f0b5eeedfcf..cd0c0262a77d2 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -64,6 +64,8 @@ export { DEFAULT_DOWNLOAD_SOURCE_URI, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, DEFAULT_DOWNLOAD_SOURCE_ID, + // Authz + ENDPOINT_PRIVILEGES, } from '../../common/constants'; export { diff --git a/x-pack/plugins/fleet/server/routes/security.ts b/x-pack/plugins/fleet/server/routes/security.ts index 410bd9a5b9224..da90480944a46 100644 --- a/x-pack/plugins/fleet/server/routes/security.ts +++ b/x-pack/plugins/fleet/server/routes/security.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import type { IRouter, RouteConfig, @@ -17,11 +18,11 @@ import type { import type { FleetAuthz } from '../../common'; import { INTEGRATIONS_PLUGIN_ID } from '../../common'; -import { calculateAuthz } from '../../common/authz'; +import { calculateAuthz, calculatePackagePrivilegesFromKibanaPrivileges } from '../../common/authz'; import { appContextService } from '../services'; import type { FleetRequestHandlerContext } from '../types'; -import { PLUGIN_ID } from '../constants'; +import { PLUGIN_ID, ENDPOINT_PRIVILEGES } from '../constants'; function checkSecurityEnabled() { return appContextService.getSecurityLicense().isEnabled(); @@ -63,12 +64,16 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise + security.authz.actions.api.get(`${DEFAULT_APP_CATEGORIES.security.id}-${privilege}`) + ); const { privileges } = await checkPrivileges({ kibana: [ security.authz.actions.api.get(`${PLUGIN_ID}-all`), security.authz.actions.api.get(`${PLUGIN_ID}-setup`), security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-all`), security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-read`), + ...endpointPrivileges, ], }); const fleetAllAuth = getAuthorizationFromPrivileges(privileges.kibana, `${PLUGIN_ID}-all`); @@ -82,14 +87,17 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise + fleetAuthz: FleetAuthz, + userRoles: MaybeImmutable, + // to be used in follow-up PRs + isEndpointRbacEnabled: boolean = false ): EndpointAuthz => { const isPlatinumPlusLicense = licenseService.isPlatinumPlus(); const isEnterpriseLicense = licenseService.isEnterprise(); diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index fe65aab259395..aa581971e5f09 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -59,6 +59,11 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the detection response actions in rule + alerts */ responseActionsEnabled: true, + + /** + * Enables endpoint package level rbac + */ + endpointRbacEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts index c499861e6abfb..29da5688357b0 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts @@ -29,6 +29,9 @@ jest.mock('../../../hooks/use_license', () => { }, }; }); +jest.mock('../../../hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn((feature: string) => feature === 'endpointRbacEnabled'), +})); const licenseServiceMock = licenseService as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts index d0a1057d9f00e..7d7233312fecc 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts @@ -19,6 +19,7 @@ import { getEndpointAuthzInitialState, } from '../../../../../common/endpoint/service/authz'; import { useSecuritySolutionStartDependencies } from './security_solution_start_dependencies'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; /** * Retrieve the endpoint privileges for the current user. @@ -41,17 +42,26 @@ export const useEndpointPrivileges = (): Immutable => { const [userRoles, setUserRoles] = useState>([]); const fleetServices = fleetServicesFromUseKibana ?? fleetServicesFromPluginStart; + const isEndpointRbacEnabled = useIsExperimentalFeatureEnabled('endpointRbacEnabled'); const privileges = useMemo(() => { const privilegeList: EndpointPrivileges = Object.freeze({ loading: !fleetCheckDone || !userRolesCheckDone || !user, ...(fleetAuthz - ? calculateEndpointAuthz(licenseService, fleetAuthz, userRoles) + ? calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, isEndpointRbacEnabled) : getEndpointAuthzInitialState()), }); return privilegeList; - }, [fleetCheckDone, userRolesCheckDone, user, fleetAuthz, licenseService, userRoles]); + }, [ + fleetCheckDone, + userRolesCheckDone, + user, + fleetAuthz, + licenseService, + userRoles, + isEndpointRbacEnabled, + ]); // Check if user can access fleet useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts index 7d386b6d6c5e0..09c47bc70095c 100644 --- a/x-pack/plugins/security_solution/public/management/links.test.ts +++ b/x-pack/plugins/security_solution/public/management/links.test.ts @@ -7,10 +7,13 @@ import type { HttpSetup } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; + import { SecurityPageName } from '../app/types'; import { licenseService } from '../common/hooks/use_license'; import type { StartPlugins } from '../types'; import { links, getManagementFilteredLinks } from './links'; +import { allowedExperimentalValues } from '../../common/experimental_features'; +import { ExperimentalFeaturesService } from '../common/experimental_features_service'; jest.mock('../common/hooks/use_license', () => { const licenseServiceInstance = { @@ -30,6 +33,12 @@ describe('links', () => { let getPlugins: (roles: string[]) => StartPlugins; let fakeHttpServices: jest.Mocked; + beforeAll(() => { + ExperimentalFeaturesService.init({ + experimentalFeatures: { ...allowedExperimentalValues }, + }); + }); + beforeEach(() => { coreMockStarted = coreMock.createStart(); fakeHttpServices = coreMockStarted.http as jest.Mocked; @@ -41,6 +50,13 @@ describe('links', () => { getCurrentUser: jest.fn().mockReturnValue({ roles }), }, }, + fleet: { + authz: { + fleet: { + all: true, + }, + }, + }, } as unknown as StartPlugins); }); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 659fb7a8216a5..54c729e41911d 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -7,7 +7,11 @@ import type { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; + +import { + calculateEndpointAuthz, + getEndpointAuthzInitialState, +} from '../../common/endpoint/service/authz'; import { BLOCKLIST_PATH, ENDPOINTS_PATH, @@ -53,6 +57,7 @@ import { IconHostIsolation } from './icons/host_isolation'; import { IconSiemRules } from './icons/siem_rules'; import { IconTrustedApplications } from './icons/trusted_applications'; import { HostIsolationExceptionsApiClient } from './pages/host_isolation_exceptions/host_isolation_exceptions_api_client'; +import { ExperimentalFeaturesService } from '../common/experimental_features_service'; const categories = [ { @@ -230,13 +235,19 @@ export const getManagementFilteredLinks = async ( core: CoreStart, plugins: StartPlugins ): Promise => { + const fleetAuthz = plugins.fleet?.authz; + const isEndpointRbacEnabled = ExperimentalFeaturesService.get().endpointRbacEnabled; + try { const currentUserResponse = await plugins.security.authc.getCurrentUser(); - const privileges = calculateEndpointAuthz( - licenseService, - plugins.fleet?.authz, - currentUserResponse.roles - ); + const privileges = fleetAuthz + ? calculateEndpointAuthz( + licenseService, + fleetAuthz, + currentUserResponse.roles, + isEndpointRbacEnabled + ) + : getEndpointAuthzInitialState(); if (!privileges.canAccessEndpointManagement) { return getFilteredLinks([SecurityPageName.hostIsolationExceptions]); } diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 59deaae59f450..06d54a4353958 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -42,6 +42,7 @@ import { registerListsPluginEndpointExtensionPoints } from '../lists_integration import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; import type { FeatureUsageService } from './services/feature_usage/service'; +import type { ExperimentalFeatures } from '../../common/experimental_features'; export interface EndpointAppContextServiceSetupContract { securitySolutionRequestContextFactory: IRequestContextFactory; @@ -67,6 +68,7 @@ export type EndpointAppContextServiceStartContract = Partial< exceptionListsClient: ExceptionListClient | undefined; cases: CasesPluginStartContract | undefined; featureUsageService: FeatureUsageService; + experimentalFeatures: ExperimentalFeatures; }; /** @@ -161,8 +163,14 @@ export class EndpointAppContextService { public async getEndpointAuthz(request: KibanaRequest): Promise { const fleetAuthz = await this.getFleetAuthzService().fromRequest(request); const userRoles = this.startDependencies?.security.authc.getCurrentUser(request)?.roles ?? []; - - return calculateEndpointAuthz(this.getLicenseService(), fleetAuthz, userRoles); + const isEndpointRbacEnabled = this.experimentalFeatures.endpointRbacEnabled; + + return calculateEndpointAuthz( + this.getLicenseService(), + fleetAuthz, + userRoles, + isEndpointRbacEnabled + ); } public getEndpointMetadataService(): EndpointMetadataService { @@ -222,4 +230,12 @@ export class EndpointAppContextService { } return this.startDependencies.featureUsageService; } + + public get experimentalFeatures(): ExperimentalFeatures { + if (this.startDependencies == null) { + throw new EndpointAppContentServicesNotStartedError(); + } + + return this.startDependencies.experimentalFeatures; + } } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 8b9623b51b24c..50785f9c29bf9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -158,6 +158,7 @@ export const createMockEndpointAppContextServiceStartContract = getCasesClientWithRequest: jest.fn(async () => casesClientMock), }, featureUsageService: createFeatureUsageServiceMock(), + experimentalFeatures: createMockConfig().experimentalFeatures, }; }; diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index 2a794285b52b7..fc3307a650097 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -11,8 +11,10 @@ import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import { createUICapabilities } from '@kbn/cases-plugin/common'; + import { APP_ID, CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants'; import { savedObjectTypes } from './saved_objects'; +import type { ConfigType } from './config'; export const getCasesKibanaFeature = (): KibanaFeatureConfig => { const casesCapabilities = createUICapabilities(); @@ -99,7 +101,10 @@ const CLOUD_POSTURE_APP_ID = 'csp'; // Same as the saved-object type for rules defined by Cloud Security Posture const CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE = 'csp_rule'; -export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): KibanaFeatureConfig => ({ +export const getKibanaPrivilegesFeaturePrivileges = ( + ruleTypes: string[], + experimentalFeatures: ConfigType['experimentalFeatures'] +): KibanaFeatureConfig => ({ id: SERVER_APP_ID, name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle', { defaultMessage: 'Security', @@ -112,7 +117,6 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban insightsAndAlerting: ['triggersActions'], }, alerting: ruleTypes, - subFeatures: [], privileges: { all: { app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], @@ -178,4 +182,336 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban ui: ['show'], }, }, + subFeatures: experimentalFeatures.endpointRbacEnabled + ? [ + { + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.endpointList', { + defaultMessage: 'Endpoint List', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeEndpointList`, `${APP_ID}-readEndpointList`], + id: 'endpoint_list_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeEndpointList', 'readEndpointList'], + }, + { + api: [`${APP_ID}-readEndpointList`], + id: 'endpoint_list_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readEndpointList'], + }, + ], + }, + ], + }, + { + name: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications', + { + defaultMessage: 'Trusted Applications', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeTrustedApplications`, `${APP_ID}-readTrustedApplications`], + id: 'trusted_applications_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeTrustedApplications', 'readTrustedApplications'], + }, + { + api: [`${APP_ID}-readTrustedApplications`], + id: 'trusted_applications_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readTrustedApplications'], + }, + ], + }, + ], + }, + { + name: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions', + { + defaultMessage: 'Host Isolation Exceptions', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [ + `${APP_ID}-writeHostIsolationExceptions`, + `${APP_ID}-readHostIsolationExceptions`, + ], + id: 'host_isolation_exceptions_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeHostIsolationExceptions', 'readHostIsolationExceptions'], + }, + { + api: [`${APP_ID}-readHostIsolationExceptions`], + id: 'host_isolation_exceptions_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readHostIsolationExceptions'], + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.blockList', { + defaultMessage: 'Blocklist', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeBlocklist`, `${APP_ID}-readBlocklist`], + id: 'blocklist_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeBlocklist', 'readBlocklist'], + }, + { + api: [`${APP_ID}-readBlocklist`], + id: 'blocklist_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readBlocklist'], + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.eventFilters', { + defaultMessage: 'Event Filters', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeEventFilters`, `${APP_ID}-readEventFilters`], + id: 'event_filters_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeEventFilters', 'readEventFilters'], + }, + { + api: [`${APP_ID}-readEventFilters`], + id: 'event_filters_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readEventFilters'], + }, + ], + }, + ], + }, + { + name: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement', + { + defaultMessage: 'Policy Management', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writePolicyManagement`, `${APP_ID}-readPolicyManagement`], + id: 'policy_management_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writePolicyManagement', 'readPolicyManagement'], + }, + { + api: [`${APP_ID}-readPolicyManagement`], + id: 'policy_management_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readPolicyManagement'], + }, + ], + }, + ], + }, + { + name: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.actionsLogManagement', + { + defaultMessage: 'Actions Log Management', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [ + `${APP_ID}-writeActionsLogManagement`, + `${APP_ID}-readActionsLogManagement`, + ], + id: 'actions_log_management_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeActionsLogManagement', 'readActionsLogManagement'], + }, + { + api: [`${APP_ID}-readActionsLogManagement`], + id: 'actions_log_management_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ui: ['readActionsLogManagement'], + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.hostIsolation', { + defaultMessage: 'Host Isolation', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeHostIsolation`], + id: 'host_isolation_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeHostIsolation'], + }, + ], + }, + ], + }, + { + name: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.processOperations', + { + defaultMessage: 'Process Operations', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeProcessOperations`], + id: 'process_operations_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeProcessOperations'], + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.securitySolution.featureRegistr.subFeatures.fileOperations', { + defaultMessage: 'File Operations', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeFileOperations`], + id: 'file_operations_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeFileOperations'], + }, + ], + }, + ], + }, + ] + : [], }); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0552f9bb2bd00..a71048462b5e6 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -307,7 +307,9 @@ export class Plugin implements ISecuritySolutionPlugin { NEW_TERMS_RULE_TYPE_ID, ]; - plugins.features.registerKibanaFeature(getKibanaPrivilegesFeaturePrivileges(ruleTypes)); + plugins.features.registerKibanaFeature( + getKibanaPrivilegesFeaturePrivileges(ruleTypes, experimentalFeatures) + ); plugins.features.registerKibanaFeature(getCasesKibanaFeature()); if (plugins.alerting != null) { @@ -471,6 +473,7 @@ export class Plugin implements ISecuritySolutionPlugin { exceptionListsClient: exceptionListClient, registerListsServerExtension: this.lists?.registerExtension, featureUsageService, + experimentalFeatures: config.experimentalFeatures, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index b0eb6b1d7dbd9..730cbd259f50e 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -79,6 +79,9 @@ export class RequestContextFactory implements IRequestContextFactory { (await context.fleet)?.authz ?? (await startPlugins.fleet?.authz.fromRequest(request)); } + const isEndpointRbacEnabled = + endpointAppContextService.experimentalFeatures.endpointRbacEnabled; + const coreContext = await context.core; return { @@ -92,7 +95,12 @@ export class RequestContextFactory implements IRequestContextFactory { endpointAuthz = getEndpointAuthzInitialState(); } else { const userRoles = security?.authc.getCurrentUser(request)?.roles ?? []; - endpointAuthz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + endpointAuthz = calculateEndpointAuthz( + licenseService, + fleetAuthz, + userRoles, + isEndpointRbacEnabled + ); } } From a864509f2d98d34b2ba0890c49f62e3afbc47bac Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Mon, 26 Sep 2022 11:50:49 -0400 Subject: [PATCH 023/172] Check for bundled package dir once during setup (#141660) --- .../services/epm/packages/bundled_packages.ts | 8 +++++ x-pack/plugins/fleet/server/services/setup.ts | 30 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts index e18b64d8daae8..ede8a25ca254f 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts @@ -22,6 +22,14 @@ export async function getBundledPackages(): Promise { throw new FleetError('xpack.fleet.developer.bundledPackageLocation is not configured'); } + // If the bundled package directory is missing, we log a warning during setup, + // so we can safely ignore this case here and just retun and empty array + try { + await fs.stat(bundledPackageLocation); + } catch (error) { + return []; + } + try { const dirContents = await fs.readdir(bundledPackageLocation); const zipFiles = dirContents.filter((file) => file.endsWith('.zip')); diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index e9b26e50735b9..f536125e054a2 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -5,6 +5,8 @@ * 2.0. */ +import fs from 'fs/promises'; + import { compact } from 'lodash'; import pMap from 'p-map'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; @@ -31,7 +33,7 @@ import { outputService } from './output'; import { downloadSourceService } from './download_source'; import { generateEnrollmentAPIKey, hasEnrollementAPIKeysForPolicy } from './api_keys'; -import { settingsService } from '.'; +import { getRegistryUrl, settingsService } from '.'; import { awaitIfPending } from './setup_utils'; import { ensureFleetFinalPipelineIsInstalled } from './epm/elasticsearch/ingest_pipeline/install'; import { ensureDefaultComponentTemplates } from './epm/elasticsearch/template/install'; @@ -64,6 +66,8 @@ async function createSetupSideEffects( const logger = appContextService.getLogger(); logger.info('Beginning fleet setup'); + await ensureFleetDirectories(); + const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } = appContextService.getConfig() ?? {}; @@ -266,3 +270,27 @@ export function formatNonFatalErrors( } }); } + +/** + * Confirm existence of various directories used by Fleet and warn if they don't exist + */ +export async function ensureFleetDirectories() { + const logger = appContextService.getLogger(); + const config = appContextService.getConfig(); + + const bundledPackageLocation = config?.developer?.bundledPackageLocation; + const registryUrl = getRegistryUrl(); + + if (!bundledPackageLocation) { + logger.warn('xpack.fleet.developer.bundledPackageLocation is not configured'); + return; + } + + try { + await fs.stat(bundledPackageLocation); + } catch (error) { + logger.warn( + `Bundled package directory ${bundledPackageLocation} does not exist. All packages will be sourced from ${registryUrl}.` + ); + } +} From 249b59646500b343f979e483d13fca67e61ecb2f Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 26 Sep 2022 10:56:31 -0500 Subject: [PATCH 024/172] [journeys] restart ES for each journey, fix flakiness (#141530) --- .buildkite/pipelines/performance/daily.yml | 6 + .../functional/performance_playwright.sh | 110 +++++++++++++----- .../scalability_dataset_extraction.sh | 15 ++- .../report_failures_to_file.ts | 24 +++- ...report_failures_to_file_html_template.html | 61 ++++++++-- .../journey/journey_ftr_config.ts | 2 +- .../journey/journey_ftr_harness.ts | 14 ++- .../journey/journey_screenshots.ts | 16 ++- x-pack/performance/jest.config.js | 12 ++ x-pack/performance/services/lib/time.test.ts | 26 +++++ x-pack/performance/services/lib/time.ts | 41 +++++++ x-pack/performance/services/toasts.ts | 15 ++- x-pack/performance/tsconfig.json | 2 +- 13 files changed, 291 insertions(+), 53 deletions(-) create mode 100644 x-pack/performance/jest.config.js create mode 100644 x-pack/performance/services/lib/time.test.ts create mode 100644 x-pack/performance/services/lib/time.ts diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index 10f137a5c5088..ea7e406ba63d8 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -20,6 +20,12 @@ steps: depends_on: build key: tests timeout_in_minutes: 60 + retry: + automatic: + - exit_status: '-1' + limit: 3 + - exit_status: '*' + limit: 1 - label: '🚢 Performance Tests dataset extraction for scalability benchmarking' command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index 111825b3b03a0..6f0399b2de90d 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -11,26 +11,9 @@ is_test_execution_step rm -rf "$KIBANA_BUILD_LOCATION" .buildkite/scripts/download_build_artifacts.sh -echo "--- 🦺 Starting Elasticsearch" - -node scripts/es snapshot& -export esPid=$! -trap 'kill ${esPid}' EXIT - -export TEST_ES_URL=http://elastic:changeme@localhost:9200 -export TEST_ES_DISABLE_STARTUP=true - -# Pings the es server every second for up to 2 minutes until it is green -curl \ - --fail \ - --silent \ - --retry 120 \ - --retry-delay 1 \ - --retry-connrefused \ - -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" \ - > /dev/null - -echo "✅ ES is ready and will continue to run in the background" +function is_running { + kill -0 "$1" &>/dev/null +} # unset env vars defined in other parts of CI for automatic APM collection of # Kibana. We manage APM config in our FTR config and performance service, and @@ -46,29 +29,100 @@ unset ELASTIC_APM_SERVER_URL unset ELASTIC_APM_SECRET_TOKEN unset ELASTIC_APM_GLOBAL_LABELS -journeys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard" "many_fields_discover" "data_stress_test_lens") +# `kill $esPid` doesn't work, seems that kbn-es doesn't listen to signals correctly, this does work +trap 'killall node -q' EXIT + +export TEST_ES_URL=http://elastic:changeme@localhost:9200 +export TEST_ES_DISABLE_STARTUP=true + +echo "--- determining which journeys to run" + +journeys=$(buildkite-agent meta-data get "failed-journeys" --default '') +if [ "$journeys" != "" ]; then + echo "re-running failed journeys:${journeys}" +else + paths=() + for path in x-pack/performance/journeys/*; do + paths+=("$path") + done + journeys=$(printf "%s\n" "${paths[@]}") + echo "running discovered journeys:${journeys}" +fi + +# track failed journeys here which might get written to metadata +failedJourneys=() + +while read -r journey; do + if [ "$journey" == "" ]; then + continue; + fi + + echo "--- $journey - 🔎 Start es" -for journey in "${journeys[@]}"; do - set +e + node scripts/es snapshot& + export esPid=$! + + # Pings the es server every second for up to 2 minutes until it is green + curl \ + --fail \ + --silent \ + --retry 120 \ + --retry-delay 1 \ + --retry-connrefused \ + -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" \ + > /dev/null + + echo "✅ ES is ready and will run in the background" phases=("WARMUP" "TEST") + status=0 for phase in "${phases[@]}"; do echo "--- $journey - $phase" export TEST_PERFORMANCE_PHASE="$phase" + + set +e node scripts/functional_tests \ - --config "x-pack/performance/journeys/$journey.ts" \ + --config "$journey" \ --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ --debug \ --bail - status=$? + set -e + if [ $status -ne 0 ]; then + failedJourneys+=("$journey") echo "^^^ +++" echo "❌ FTR failed with status code: $status" - exit 1 + break + fi + done + + # remove trap, we're manually shutting down + trap - EXIT; + + echo "--- $journey - 🔎 Shutdown ES" + killall node + echo "waiting for $esPid to exit gracefully"; + + timeout=30 #seconds + dur=0 + while is_running $esPid; do + sleep 1; + ((dur=dur+1)) + if [ $dur -ge $timeout ]; then + echo "es still running after $dur seconds, killing ES and node forcefully"; + killall -SIGKILL java + killall -SIGKILL node + sleep 5; fi done +done <<< "$journeys" + +echo "--- report/record failed journeys" +if [ "${failedJourneys[*]}" != "" ]; then + buildkite-agent meta-data set "failed-journeys" "$(printf "%s\n" "${failedJourneys[@]}")" - set -e -done + echo "failed journeys: ${failedJourneys[*]}" + exit 1 +fi diff --git a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh index a2b81f538b92b..aff087005ac5c 100755 --- a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh +++ b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh @@ -46,8 +46,13 @@ cd "${OUTPUT_DIR}/.." gsutil -m cp -r "${BUILD_ID}" "${GCS_BUCKET}" cd - -echo "--- Promoting '${BUILD_ID}' dataset to LATEST" -cd "${OUTPUT_DIR}/.." -echo "${BUILD_ID}" > latest -gsutil cp latest "${GCS_BUCKET}" -cd - +if [ "$BUILDKITE_PIPELINE_SLUG" == "kibana-single-user-performance" ]; then + echo "--- Promoting '${BUILD_ID}' dataset to LATEST" + cd "${OUTPUT_DIR}/.." + echo "${BUILD_ID}" > latest + gsutil cp latest "${GCS_BUCKET}" + cd - +else + echo "--- Skipping promotion of dataset to LATEST" + echo "$BUILDKITE_PIPELINE_SLUG is not 'kibana-single-user-performance', so skipping" +fi diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts index d34df80f3d0a8..da643164a14aa 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts @@ -44,12 +44,26 @@ async function getJourneySnapshotHtml(log: ToolingLog, journeyMeta: JourneyMeta) return [ '

    ', '
    Steps
    ', - ...screenshots.get().flatMap(({ title, path }) => { + ...screenshots.get().flatMap(({ title, path, fullscreenPath }) => { const base64 = Fs.readFileSync(path, 'base64'); + const fullscreenBase64 = Fs.readFileSync(fullscreenPath, 'base64'); return [ `

    ${escape(title)}

    `, - ``, + `
    + + + + +
    `, ]; }), '
    ', @@ -88,7 +102,11 @@ function getFtrScreenshotHtml(log: ToolingLog, failureName: string) { .filter((s) => s.name.startsWith(FtrScreenshotFilename.create(failureName, { ext: false }))) .map((s) => { const base64 = Fs.readFileSync(s.path).toString('base64'); - return ``; + return ` +
    + +
    + `; }) .join('\n'); } diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html index 485a2c2d4eb3f..01ebc4ba22210 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html @@ -16,12 +16,24 @@ img.screenshot { cursor: pointer; - height: 200px; margin: 5px 0; } + .screenshotContainer:not(.expanded) img.screenshot { + height: 200px; + } + + .screenshotContainer:not(.fs) img.screenshot.fs, + .screenshotContainer:not(.fs) button.toggleFs.off, + .screenshotContainer.fs img.screenshot:not(.fs), + .screenshotContainer.fs button.toggleFs.on { + display: none; + } - img.screenshot.expanded { - height: auto; + .screenshotContainer .toggleFs { + background: none; + border: none; + margin: 0 0 0 5px; + vertical-align: top; } $TITLE @@ -31,11 +43,46 @@
    $MAIN
diff --git a/packages/kbn-journeys/journey/journey_ftr_config.ts b/packages/kbn-journeys/journey/journey_ftr_config.ts index 392ad69b63ba1..b0d8e33ad01c0 100644 --- a/packages/kbn-journeys/journey/journey_ftr_config.ts +++ b/packages/kbn-journeys/journey/journey_ftr_config.ts @@ -82,7 +82,7 @@ export function makeFtrConfigProvider( kbnTestServer: { ...baseConfig.kbnTestServer, // delay shutdown by 15 seconds to ensure that APM can report the data it collects during test execution - delayShutdown: 15_000, + delayShutdown: process.env.TEST_PERFORMANCE_PHASE === 'TEST' ? 15_000 : 0, serverArgs: [ ...baseConfig.kbnTestServer.serverArgs, diff --git a/packages/kbn-journeys/journey/journey_ftr_harness.ts b/packages/kbn-journeys/journey/journey_ftr_harness.ts index 672b14f0e1a85..6154581738ac7 100644 --- a/packages/kbn-journeys/journey/journey_ftr_harness.ts +++ b/packages/kbn-journeys/journey/journey_ftr_harness.ts @@ -198,7 +198,12 @@ export class JourneyFtrHarness { return; } - await this.screenshots.addSuccess(step, await this.page.screenshot()); + const [screenshot, fs] = await Promise.all([ + this.page.screenshot(), + this.page.screenshot({ fullPage: true }), + ]); + + await this.screenshots.addSuccess(step, screenshot, fs); } private async onStepError(step: AnyStep, err: Error) { @@ -208,7 +213,12 @@ export class JourneyFtrHarness { } if (this.page) { - await this.screenshots.addError(step, await this.page.screenshot()); + const [screenshot, fs] = await Promise.all([ + this.page.screenshot(), + this.page.screenshot({ fullPage: true }), + ]); + + await this.screenshots.addError(step, screenshot, fs); } } diff --git a/packages/kbn-journeys/journey/journey_screenshots.ts b/packages/kbn-journeys/journey/journey_screenshots.ts index 8cd36444ef7ee..adf1021fa163d 100644 --- a/packages/kbn-journeys/journey/journey_screenshots.ts +++ b/packages/kbn-journeys/journey/journey_screenshots.ts @@ -19,6 +19,7 @@ interface StepShot { type: 'success' | 'failure'; title: string; filename: string; + fullscreenFilename: string; } interface Manifest { @@ -87,34 +88,44 @@ export class JourneyScreenshots { } } - async addError(step: AnyStep, screenshot: Buffer) { + async addError(step: AnyStep, screenshot: Buffer, fullscreenScreenshot: Buffer) { await this.lock(async () => { const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}-failure`); + const fullscreenFilename = FtrScreenshotFilename.create( + `${step.index}-${step.name}-failure-fullscreen` + ); this.#manifest.steps.push({ type: 'failure', title: `Step #${step.index + 1}: ${step.name} - FAILED`, filename, + fullscreenFilename, }); await Promise.all([ write(Path.resolve(this.#dir, 'manifest.json'), JSON.stringify(this.#manifest)), write(Path.resolve(this.#dir, filename), screenshot), + write(Path.resolve(this.#dir, fullscreenFilename), fullscreenScreenshot), ]); }); } - async addSuccess(step: AnyStep, screenshot: Buffer) { + async addSuccess(step: AnyStep, screenshot: Buffer, fullscreenScreenshot: Buffer) { await this.lock(async () => { const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}`); + const fullscreenFilename = FtrScreenshotFilename.create( + `${step.index}-${step.name}-fullscreen` + ); this.#manifest.steps.push({ type: 'success', title: `Step #${step.index + 1}: ${step.name} - DONE`, filename, + fullscreenFilename, }); await Promise.all([ write(Path.resolve(this.#dir, 'manifest.json'), JSON.stringify(this.#manifest)), write(Path.resolve(this.#dir, filename), screenshot), + write(Path.resolve(this.#dir, fullscreenFilename), fullscreenScreenshot), ]); }); } @@ -123,6 +134,7 @@ export class JourneyScreenshots { return this.#manifest.steps.map((stepShot) => ({ ...stepShot, path: Path.resolve(this.#dir, stepShot.filename), + fullscreenPath: Path.resolve(this.#dir, stepShot.fullscreenFilename), })); } } diff --git a/x-pack/performance/jest.config.js b/x-pack/performance/jest.config.js new file mode 100644 index 0000000000000..b8afeee25a9ed --- /dev/null +++ b/x-pack/performance/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/jest_node', + rootDir: '../..', + roots: ['/x-pack/performance'], +}; diff --git a/x-pack/performance/services/lib/time.test.ts b/x-pack/performance/services/lib/time.test.ts new file mode 100644 index 0000000000000..a1dab5878fcc2 --- /dev/null +++ b/x-pack/performance/services/lib/time.test.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ms, toMs } from './time'; + +describe('ms()', () => { + it('converts simple timestrings to milliseconds', () => { + expect(ms('1s')).toMatchInlineSnapshot(`1000`); + expect(ms('10s')).toMatchInlineSnapshot(`10000`); + expect(ms('1m')).toMatchInlineSnapshot(`60000`); + expect(ms('10m')).toMatchInlineSnapshot(`600000`); + expect(ms('0.5s')).toMatchInlineSnapshot(`500`); + expect(ms('0.5m')).toMatchInlineSnapshot(`30000`); + }); +}); + +describe('toMs()', () => { + it('converts strings to ms, returns number directly', () => { + expect(toMs(1000)).toBe(1000); + expect(toMs('1s')).toBe(1000); + }); +}); diff --git a/x-pack/performance/services/lib/time.ts b/x-pack/performance/services/lib/time.ts new file mode 100644 index 0000000000000..4557ca71d2d36 --- /dev/null +++ b/x-pack/performance/services/lib/time.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SECOND = 1000; +export const MINUTE = 60 * SECOND; + +const TIME_STR_RE = /^((?:\d+)(?:\.\d+)?)(m|s)$/i; + +/** + * Either a number of milliseconds or a simple time string (eg. 2m or 30s) + */ +export type TimeOrMilliseconds = number | string; + +export function toMs(timeOrMs: TimeOrMilliseconds) { + return typeof timeOrMs === 'number' ? timeOrMs : ms(timeOrMs); +} + +/** + * Convert a basic time string into milliseconds. The string can end with + * `m` (for minutes) or `s` (for seconds) and have any number before it. + */ +export function ms(time: string) { + const match = time.match(TIME_STR_RE); + if (!match) { + throw new Error('invalid time string, expected a number followed by "m" or "s"'); + } + + const [, num, unit] = match; + switch (unit.toLowerCase()) { + case 's': + return Number.parseFloat(num) * SECOND; + case 'm': + return Number.parseFloat(num) * MINUTE; + default: + throw new Error(`unexpected timestring unit [time=${time}] [unit=${unit}]`); + } +} diff --git a/x-pack/performance/services/toasts.ts b/x-pack/performance/services/toasts.ts index b3859ddb92ec4..4a556e3780105 100644 --- a/x-pack/performance/services/toasts.ts +++ b/x-pack/performance/services/toasts.ts @@ -9,6 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log'; import { subj } from '@kbn/test-subj-selector'; import { Page } from 'playwright'; +import { toMs, type TimeOrMilliseconds } from './lib/time'; + export class ToastsService { constructor(private readonly log: ToolingLog, private readonly page: Page) {} @@ -16,13 +18,18 @@ export class ToastsService { * Wait for a toast with some bit of text matching the provided `textSnipped`, then clear * it and resolve the promise. */ - async waitForAndClear(textSnippet: string) { + async waitForAndClear( + textSnippet: string, + options?: { + /** How long should we wait for the toast to show up? */ + timeout?: TimeOrMilliseconds; + } + ) { const txt = JSON.stringify(textSnippet); this.log.info(`waiting for toast that has the text ${txt}`); - const toastSel = `.euiToast:has-text(${txt})`; - const toast = this.page.locator(toastSel); - await toast.waitFor(); + const toast = this.page.locator(`.euiToast:has-text(${txt})`); + await toast.waitFor({ timeout: toMs(options?.timeout ?? '2m') }); this.log.info('toast found, closing'); diff --git a/x-pack/performance/tsconfig.json b/x-pack/performance/tsconfig.json index caff6d53f4476..923a42ffe52f3 100644 --- a/x-pack/performance/tsconfig.json +++ b/x-pack/performance/tsconfig.json @@ -5,7 +5,7 @@ "emitDeclarationOnly": true, "declaration": true, "declarationMap": true, - "types": ["node", "mocha"] + "types": ["node", "jest"] }, "include": ["**/*.ts"], } From c9d9d63ee7e95e58eea9c0058a1c76369a81b25c Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 26 Sep 2022 12:46:38 -0400 Subject: [PATCH 025/172] [Fleet] Add unit test for mapping generation (#141795) --- .../elasticsearch/template/mappings.test.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.test.ts diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.test.ts new file mode 100644 index 0000000000000..6925da569bae4 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { keyword } from './mappings'; + +describe('mappings', () => { + describe('keyword', () => { + it('should set ignore_above:1024 by default for indexed field', () => { + const mappings = keyword({ + name: 'test', + type: 'keyword', + }); + + expect(mappings).toEqual({ + ignore_above: 1024, + type: 'keyword', + }); + }); + + it('should not set ignore_above for non indexed field', () => { + const mappings = keyword({ + name: 'test', + type: 'keyword', + index: false, + }); + + expect(mappings).toEqual({ + type: 'keyword', + index: false, + }); + }); + + it('should not set ignore_above field with doc_values:false', () => { + const mappings = keyword({ + name: 'test', + type: 'keyword', + doc_values: false, + }); + + expect(mappings).toEqual({ + type: 'keyword', + doc_values: false, + }); + }); + }); +}); From fd16e6b224ddff8a3bbf90d4f17ad59c180d3d36 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 26 Sep 2022 12:46:55 -0400 Subject: [PATCH 026/172] [Fleet] Fix upgrade one agent should check fleet server version too (#141788) --- .../server/routes/agent/upgrade_handler.ts | 53 +++++++---- .../apis/agents/upgrade.ts | 92 +++++++++++++------ x-pack/test/fleet_api_integration/helpers.ts | 2 + 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index 4af0c1ff653b2..43a3987f29f60 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -47,26 +47,43 @@ export const postAgentUpgradeHandler: RequestHandler< }, }); } - const agent = await getAgentById(esClient, request.params.agentId); + try { + const agent = await getAgentById(esClient, request.params.agentId); - if (agent.unenrollment_started_at || agent.unenrolled_at) { - return response.customError({ - statusCode: 400, - body: { - message: 'cannot upgrade an unenrolling or unenrolled agent', - }, - }); - } - if (!force && !isAgentUpgradeable(agent, kibanaVersion, version)) { - return response.customError({ - statusCode: 400, - body: { - message: `agent ${request.params.agentId} is not upgradeable`, - }, - }); - } + const fleetServerAgents = await getAllFleetServerAgents(soClient, esClient); + const agentIsFleetServer = fleetServerAgents.some( + (fleetServerAgent) => fleetServerAgent.id === agent.id + ); + if (!agentIsFleetServer) { + try { + checkFleetServerVersion(version, fleetServerAgents); + } catch (err) { + return response.customError({ + statusCode: 400, + body: { + message: err.message, + }, + }); + } + } + + if (agent.unenrollment_started_at || agent.unenrolled_at) { + return response.customError({ + statusCode: 400, + body: { + message: 'cannot upgrade an unenrolling or unenrolled agent', + }, + }); + } + if (!force && !isAgentUpgradeable(agent, kibanaVersion, version)) { + return response.customError({ + statusCode: 400, + body: { + message: `agent ${request.params.agentId} is not upgradeable`, + }, + }); + } - try { await AgentService.sendUpgradeAgentAction({ soClient, esClient, diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index b842f89c8ac64..e941752f5f1b6 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -57,8 +57,38 @@ export default function (providerContext: FtrProviderContext) { }); describe('one agent', () => { + const fleetServerVersion = '7.16.0'; + + beforeEach(async () => { + await supertest.post(`/api/fleet/agent_policies`).set('kbn-xsrf', 'kibana').send({ + name: 'Fleet Server policy 1', + id: 'fleet-server-policy', + namespace: 'default', + has_fleet_server: true, + }); + + await kibanaServer.savedObjects.create({ + id: `package-policy-test`, + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + overwrite: true, + attributes: { + policy_id: 'fleet-server-policy', + name: 'Fleet Server', + package: { + name: 'fleet_server', + }, + }, + }); + await generateAgent( + providerContext, + 'healthy', + 'agentWithFS', + 'fleet-server-policy', + fleetServerVersion + ); + }); + it('should respond 200 to upgrade agent and update the agent SO', async () => { - const kibanaVersion = await kibanaServer.version.get(); await es.update({ id: 'agent1', refresh: 'wait_for', @@ -73,23 +103,39 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersion, + version: fleetServerVersion, }) .expect(200); const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); expect(typeof res.body.item.upgrade_started_at).to.be('string'); }); - it('should respond 400 if upgrading agent with version the same as snapshot version', async () => { + + it('should allow to upgrade a Fleet server agent to a version > fleet server version', async () => { const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + await supertest + .post(`/api/fleet/agents/agentWithFS/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + }) + .expect(200); + + const res = await supertest.get(`/api/fleet/agents/agentWithFS`).set('kbn-xsrf', 'xxx'); + expect(typeof res.body.item.upgrade_started_at).to.be('string'); + }); + + it('should respond 400 if upgrading agent with version the same as snapshot version', async () => { + const fleetServerVersionSnapshot = makeSnapshotVersion(fleetServerVersion); await es.update({ id: 'agent1', refresh: 'wait_for', index: AGENTS_INDEX, body: { doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + local_metadata: { + elastic: { agent: { upgradeable: true, version: fleetServerVersion } }, + }, }, }, }); @@ -97,20 +143,21 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersionSnapshot, + version: fleetServerVersionSnapshot, }) .expect(400); }); it('should respond 200 if upgrading agent with version the same as snapshot version and force flag is passed', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + const fleetServerVersionSnapshot = makeSnapshotVersion(fleetServerVersion); await es.update({ id: 'agent1', refresh: 'wait_for', index: AGENTS_INDEX, body: { doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + local_metadata: { + elastic: { agent: { upgradeable: true, version: fleetServerVersion } }, + }, }, }, }); @@ -118,14 +165,13 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersionSnapshot, + version: fleetServerVersionSnapshot, force: true, }) .expect(200); }); it('should respond 200 if upgrading agent with version less than kibana snapshot version', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + const fleetServerVersionSnapshot = makeSnapshotVersion(fleetServerVersion); await es.update({ id: 'agent1', @@ -141,12 +187,11 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersionSnapshot, + version: fleetServerVersionSnapshot, }) .expect(200); }); it('should respond 200 if trying to upgrade with source_uri set', async () => { - const kibanaVersion = await kibanaServer.version.get(); await es.update({ id: 'agent1', refresh: 'wait_for', @@ -161,7 +206,7 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersion, + version: fleetServerVersion, source_uri: 'http://path/to/download', }) .expect(200); @@ -205,7 +250,6 @@ export default function (providerContext: FtrProviderContext) { .expect(400); }); it('should respond 400 if trying to upgrade an agent that is unenrolling', async () => { - const kibanaVersion = await kibanaServer.version.get(); await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({ revoke: true, }); @@ -213,12 +257,11 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersion, + version: fleetServerVersion, }) .expect(400); }); it('should respond 400 if trying to upgrade an agent that is unenrolled', async () => { - const kibanaVersion = await kibanaServer.version.get(); await es.update({ id: 'agent1', refresh: 'wait_for', @@ -233,18 +276,17 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersion, + version: fleetServerVersion, }) .expect(400); }); it('should respond 400 if trying to upgrade an agent that is not upgradeable', async () => { - const kibanaVersion = await kibanaServer.version.get(); const res = await supertest .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') .send({ - version: kibanaVersion, + version: fleetServerVersion, }) .expect(400); expect(res.body.message).to.equal('agent agent1 is not upgradeable'); @@ -258,7 +300,6 @@ export default function (providerContext: FtrProviderContext) { is_managed: true, }); - const kibanaVersion = await kibanaServer.version.get(); await es.update({ id: 'agent1', refresh: 'wait_for', @@ -273,7 +314,7 @@ export default function (providerContext: FtrProviderContext) { const { body } = await supertest .post(`/api/fleet/agents/agent1/upgrade`) .set('kbn-xsrf', 'xxx') - .send({ version: kibanaVersion }) + .send({ version: fleetServerVersion }) .expect(400); expect(body.message).to.contain( 'Cannot upgrade agent agent1 in hosted agent policy policy1' @@ -284,7 +325,6 @@ export default function (providerContext: FtrProviderContext) { }); it('should respond 403 if user lacks fleet all permissions', async () => { - const kibanaVersion = await kibanaServer.version.get(); await es.update({ id: 'agent1', refresh: 'wait_for', @@ -300,7 +340,7 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .auth(testUsers.fleet_no_access.username, testUsers.fleet_no_access.password) .send({ - version: kibanaVersion, + version: fleetServerVersion, }) .expect(403); }); @@ -312,7 +352,7 @@ export default function (providerContext: FtrProviderContext) { beforeEach(async () => { await supertest.post(`/api/fleet/agent_policies`).set('kbn-xsrf', 'kibana').send({ name: 'Fleet Server policy 1', - policy_id: 'fleet-server-policy', + id: 'fleet-server-policy', namespace: 'default', has_fleet_server: true, }); diff --git a/x-pack/test/fleet_api_integration/helpers.ts b/x-pack/test/fleet_api_integration/helpers.ts index 552c83d3ca890..d3b623d0426f3 100644 --- a/x-pack/test/fleet_api_integration/helpers.ts +++ b/x-pack/test/fleet_api_integration/helpers.ts @@ -69,6 +69,7 @@ export async function generateAgent( await es.index({ index: '.fleet-agents', + id, body: { id, active: true, @@ -79,6 +80,7 @@ export async function generateAgent( elastic: { agent: { version, + upgradeable: true, }, }, }, From e35a7310e5e146886ad2912bfa04611229f1a2ea Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 26 Sep 2022 18:49:21 +0200 Subject: [PATCH 027/172] [Discover] Fix management flaky test (#141650) * [Discover] Tmp commit * [Discover] Update deprecated class * [Discover] Wait for lens page to load --- .../apps/management/_scripted_fields.ts | 27 +++++++++++-------- test/functional/page_objects/discover_page.ts | 3 ++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/test/functional/apps/management/_scripted_fields.ts b/test/functional/apps/management/_scripted_fields.ts index 03a298182eec0..d12585088893d 100644 --- a/test/functional/apps/management/_scripted_fields.ts +++ b/test/functional/apps/management/_scripted_fields.ts @@ -211,10 +211,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization - expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( - '@timestamp', - 'Median of ram_Pain1' - ); + await retry.waitFor('lens visualization', async () => { + const elements = await testSubjects.getVisibleTextAll('lns-dimensionTrigger'); + return elements[0] === '@timestamp' && elements[1] === 'Median of ram_Pain1'; + }); }); }); @@ -314,9 +314,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization - expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( - 'Top 5 values of painString' - ); + await retry.waitFor('lens visualization', async () => { + const elements = await testSubjects.getVisibleTextAll('lns-dimensionTrigger'); + return elements[0] === 'Top 5 values of painString'; + }); }); after(async () => { @@ -408,9 +409,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization - expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( - 'Top 5 values of painBool' - ); + await retry.waitFor('lens visualization', async () => { + const elements = await testSubjects.getVisibleTextAll('lns-dimensionTrigger'); + return elements[0] === 'Top 5 values of painBool'; + }); }); after(async () => { @@ -503,7 +505,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization - expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain('painDate'); + await retry.waitFor('lens visualization', async () => { + const elements = await testSubjects.getVisibleTextAll('lns-dimensionTrigger'); + return elements[0] === 'painDate'; + }); }); }); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 5a6f842086498..cc398f8d67be6 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -486,7 +486,7 @@ export class DiscoverPageObject extends FtrService { public async clickFieldListItemVisualize(fieldName: string) { const field = await this.testSubjects.find(`field-${fieldName}-showDetails`); - const isActive = await field.elementHasClass('dscSidebarItem--active'); + const isActive = await field.elementHasClass('kbnFieldButton-isActive'); if (!isActive) { // expand the field to show the "Visualize" button @@ -494,6 +494,7 @@ export class DiscoverPageObject extends FtrService { } await this.testSubjects.click(`fieldVisualize-${fieldName}`); + await this.header.waitUntilLoadingHasFinished(); } public async expectFieldListItemVisualize(field: string) { From 74bf60f14a4a59a97f376ce2e236f2c075122ac0 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 26 Sep 2022 13:52:23 -0300 Subject: [PATCH 028/172] [Discover] Show error and fix app state when updating data view ID in the URL to an invalid ID (#141540) * [Discover] Fix issue where updating the data view ID in the URL to an invalid ID does not trigger an error, and sets the app state to an invalid state * [Discover] Clean up work for invalid data view ID Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../main/hooks/use_discover_state.ts | 31 ++++++++++++++--- .../main/utils/resolve_data_view.ts | 2 ++ .../apps/discover/group1/_discover.ts | 34 +++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts index 8ad0d7ff7b590..1c530f475f0b1 100644 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts +++ b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts @@ -16,7 +16,7 @@ import { useUrlTracking } from './use_url_tracking'; import { getState } from '../services/discover_state'; import { getStateDefaults } from '../utils/get_state_defaults'; import { DiscoverServices } from '../../../build_services'; -import { loadDataView } from '../utils/resolve_data_view'; +import { loadDataView, resolveDataView } from '../utils/resolve_data_view'; import { useSavedSearch as useSavedSearchData } from './use_saved_search'; import { MODIFY_COLUMNS_ON_SWITCH, @@ -67,7 +67,7 @@ export function useDiscoverState({ [history, savedSearch, services] ); - const { appStateContainer } = stateContainer; + const { appStateContainer, replaceUrlAppState } = stateContainer; const [state, setState] = useState(appStateContainer.getState()); @@ -190,13 +190,25 @@ export function useDiscoverState({ * That's because appState is updated before savedSearchData$ * The following line of code catches this, but should be improved */ - const nextDataView = await loadDataView( + const nextDataViewData = await loadDataView( services.dataViews, services.uiSettings, nextState.index ); - savedSearch.searchSource.setField('index', nextDataView.loaded); + const nextDataView = resolveDataView( + nextDataViewData, + savedSearch.searchSource, + services.toastNotifications + ); + + // If the requested data view is not found, don't try to load it, + // and instead reset the app state to the fallback data view + if (!nextDataViewData.stateValFound) { + replaceUrlAppState({ index: nextDataView.id }); + return; + } + savedSearch.searchSource.setField('index', nextDataView); reset(); } @@ -206,7 +218,16 @@ export function useDiscoverState({ setState(nextState); }); return () => unsubscribe(); - }, [services, appStateContainer, state, refetch$, data$, reset, savedSearch.searchSource]); + }, [ + services, + appStateContainer, + state, + refetch$, + data$, + reset, + savedSearch.searchSource, + replaceUrlAppState, + ]); /** * function to revert any changes to a given saved search diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts index 7baede8101851..094e1ec837e3c 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts @@ -166,6 +166,7 @@ export function resolveDataView( ownDataViewId: ownDataView.id, }, }), + 'data-test-subj': 'dscDataViewNotFoundShowSavedWarning', }); return ownDataView; } @@ -180,6 +181,7 @@ export function resolveDataView( loadedDataViewId: loadedDataView.id, }, }), + 'data-test-subj': 'dscDataViewNotFoundShowDefaultWarning', }); } diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index 39793c9c047f0..9ac159fa39168 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -362,5 +362,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(newMainPanelSize).to.be(mainPanelSize - resizeDistance); }); }); + + describe('URL state', () => { + const getCurrentDataViewId = (currentUrl: string) => { + const [indexSubstring] = currentUrl.match(/index:[^,]*/)!; + const dataViewId = indexSubstring.replace('index:', ''); + return dataViewId; + }; + + it('should show a warning and fall back to the default data view when navigating to a URL with an invalid data view ID', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const originalUrl = await browser.getCurrentUrl(); + const dataViewId = getCurrentDataViewId(originalUrl); + const newUrl = originalUrl.replace(dataViewId, 'invalid-data-view-id'); + await browser.get(newUrl); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await browser.getCurrentUrl()).to.be(originalUrl); + expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); + }); + + it('should show a warning and fall back to the current data view if the URL is updated to an invalid data view ID', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + const originalHash = await browser.execute<[], string>('return window.location.hash'); + const dataViewId = getCurrentDataViewId(originalHash); + const newHash = originalHash.replace(dataViewId, 'invalid-data-view-id'); + await browser.execute(`window.location.hash = "${newHash}"`); + await PageObjects.header.waitUntilLoadingHasFinished(); + const currentHash = await browser.execute<[], string>('return window.location.hash'); + expect(currentHash).to.be(originalHash); + expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + }); + }); }); } From 90b9afdceea2439f7d7493000b22e8e6ff0f7185 Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Mon, 26 Sep 2022 11:12:57 -0600 Subject: [PATCH 029/172] [APM] Hash service name/environment for APM per_service telemetry (#141685) * Hash service name/environment for per_service telemetry * Fix typing and reorder service name/env Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/apm_telemetry/collect_data_telemetry/tasks.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index 167fb0d57208f..430930fb3f916 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -6,6 +6,7 @@ */ import { fromKueryExpression } from '@kbn/es-query'; import { flatten, merge, sortBy, sum, pickBy } from 'lodash'; +import { createHash } from 'crypto'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; @@ -1247,11 +1248,15 @@ export const tasks: TelemetryTask[] = [ }); const envBuckets = response.aggregations?.environments.buckets ?? []; const data: APMPerService[] = envBuckets.flatMap((envBucket) => { - const env = envBucket.key; + const envHash = createHash('sha256') + .update(envBucket.key as string) + .digest('hex'); const serviceBuckets = envBucket.service_names?.buckets ?? []; return serviceBuckets.map((serviceBucket) => { - const name = serviceBucket.key; - const fullServiceName = `${env}~${name}`; + const nameHash = createHash('sha256') + .update(serviceBucket.key as string) + .digest('hex'); + const fullServiceName = `${nameHash}~${envHash}`; return { service_id: fullServiceName, timed_out: response.timed_out, From 924c7f912ab9203b8c0e320ce97ed8e3f1b5a817 Mon Sep 17 00:00:00 2001 From: Joseph Crail Date: Mon, 26 Sep 2022 10:27:06 -0700 Subject: [PATCH 030/172] [Profiling] Simplify query post-processing and flamegraph response (#141729) * Replace non-null assertion with nullish coalescing * Remove createFrameGroup * Remove callers * Use adjacency list representation for tree * Move frame type map outside function * Inline frame group name * Replace FrameGroupID with ID * Create columnar view model in client --- .../{callercallee.test.ts => callee.test.ts} | 13 +- x-pack/plugins/profiling/common/callee.ts | 193 ++++++++++++++++ .../plugins/profiling/common/callercallee.ts | 177 -------------- .../profiling/common/flamegraph.test.ts | 32 +++ x-pack/plugins/profiling/common/flamegraph.ts | 216 +++++++++--------- .../profiling/common/frame_group.test.ts | 112 +++++++-- .../plugins/profiling/common/frame_group.ts | 72 +----- x-pack/plugins/profiling/common/functions.ts | 11 +- x-pack/plugins/profiling/common/profiling.ts | 40 ++-- .../public/components/flamegraph.tsx | 10 +- .../utils/get_flamegraph_model/index.ts | 38 ++- .../profiling/server/routes/flamechart.ts | 78 +++---- .../routes/get_executables_and_stacktraces.ts | 3 +- .../profiling/server/routes/stacktrace.ts | 16 +- 14 files changed, 535 insertions(+), 476 deletions(-) rename x-pack/plugins/profiling/common/{callercallee.test.ts => callee.test.ts} (51%) create mode 100644 x-pack/plugins/profiling/common/callee.ts delete mode 100644 x-pack/plugins/profiling/common/callercallee.ts create mode 100644 x-pack/plugins/profiling/common/flamegraph.test.ts diff --git a/x-pack/plugins/profiling/common/callercallee.test.ts b/x-pack/plugins/profiling/common/callee.test.ts similarity index 51% rename from x-pack/plugins/profiling/common/callercallee.test.ts rename to x-pack/plugins/profiling/common/callee.test.ts index a249d21623c44..0ae26e6d848e7 100644 --- a/x-pack/plugins/profiling/common/callercallee.test.ts +++ b/x-pack/plugins/profiling/common/callee.test.ts @@ -6,18 +6,19 @@ */ import { sum } from 'lodash'; -import { createCallerCalleeGraph } from './callercallee'; +import { createCalleeTree } from './callee'; import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; -describe('Caller-callee operations', () => { +describe('Callee operations', () => { test('1', () => { const totalSamples = sum([...events.values()]); + const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); - const graph = createCallerCalleeGraph(events, stackTraces, stackFrames, executables); + const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); - expect(graph.root.Samples).toEqual(totalSamples); - expect(graph.root.CountInclusive).toEqual(totalSamples); - expect(graph.root.CountExclusive).toEqual(0); + expect(tree.Samples[0]).toEqual(totalSamples); + expect(tree.CountInclusive[0]).toEqual(totalSamples); + expect(tree.CountExclusive[0]).toEqual(0); }); }); diff --git a/x-pack/plugins/profiling/common/callee.ts b/x-pack/plugins/profiling/common/callee.ts new file mode 100644 index 0000000000000..63db0640513c3 --- /dev/null +++ b/x-pack/plugins/profiling/common/callee.ts @@ -0,0 +1,193 @@ +/* + * 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 fnv from 'fnv-plus'; + +import { createFrameGroupID, FrameGroupID } from './frame_group'; +import { + createStackFrameMetadata, + emptyExecutable, + emptyStackFrame, + emptyStackTrace, + Executable, + FileID, + getCalleeLabel, + StackFrame, + StackFrameID, + StackFrameMetadata, + StackTrace, + StackTraceID, +} from './profiling'; + +type NodeID = number; + +export interface CalleeTree { + Size: number; + Edges: Array>; + + ID: string[]; + FrameType: number[]; + FrameID: StackFrameID[]; + FileID: FileID[]; + Label: string[]; + + Samples: number[]; + CountInclusive: number[]; + CountExclusive: number[]; +} + +function initCalleeTree(capacity: number): CalleeTree { + const metadata = createStackFrameMetadata(); + const frameGroupID = createFrameGroupID( + metadata.FileID, + metadata.AddressOrLine, + metadata.ExeFileName, + metadata.SourceFilename, + metadata.FunctionName + ); + const tree: CalleeTree = { + Size: 1, + Edges: new Array(capacity), + ID: new Array(capacity), + FrameType: new Array(capacity), + FrameID: new Array(capacity), + FileID: new Array(capacity), + Label: new Array(capacity), + Samples: new Array(capacity), + CountInclusive: new Array(capacity), + CountExclusive: new Array(capacity), + }; + + tree.Edges[0] = new Map(); + + tree.ID[0] = fnv.fast1a64utf(frameGroupID).toString(); + tree.FrameType[0] = metadata.FrameType; + tree.FrameID[0] = metadata.FrameID; + tree.FileID[0] = metadata.FileID; + tree.Label[0] = 'root: Represents 100% of CPU time.'; + tree.Samples[0] = 0; + tree.CountInclusive[0] = 0; + tree.CountExclusive[0] = 0; + + return tree; +} + +function insertNode( + tree: CalleeTree, + parent: NodeID, + metadata: StackFrameMetadata, + frameGroupID: FrameGroupID, + samples: number +) { + const node = tree.Size; + + tree.Edges[parent].set(frameGroupID, node); + tree.Edges[node] = new Map(); + + tree.ID[node] = fnv.fast1a64utf(`${tree.ID[parent]}${frameGroupID}`).toString(); + tree.FrameType[node] = metadata.FrameType; + tree.FrameID[node] = metadata.FrameID; + tree.FileID[node] = metadata.FileID; + tree.Label[node] = getCalleeLabel(metadata); + tree.Samples[node] = samples; + tree.CountInclusive[node] = 0; + tree.CountExclusive[node] = 0; + + tree.Size++; + + return node; +} + +// createCalleeTree creates a tree from the trace results, the number of +// times that the trace has been seen, and the respective metadata. +// +// The resulting data structure contains all of the data, but is not yet in the +// form most easily digestible by others. +export function createCalleeTree( + events: Map, + stackTraces: Map, + stackFrames: Map, + executables: Map, + totalFrames: number +): CalleeTree { + const tree = initCalleeTree(totalFrames); + + const sortedStackTraceIDs = new Array(); + for (const trace of stackTraces.keys()) { + sortedStackTraceIDs.push(trace); + } + sortedStackTraceIDs.sort((t1, t2) => { + return t1.localeCompare(t2); + }); + + // Walk through all traces. Increment the count of the root by the count of + // that trace. Walk "down" the trace (through the callees) and add the count + // of the trace to each callee. + + for (const stackTraceID of sortedStackTraceIDs) { + // The slice of frames is ordered so that the leaf function is at the + // highest index. + + // It is possible that we do not have a stacktrace for an event, + // e.g. when stopping the host agent or on network errors. + const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace; + const lenStackTrace = stackTrace.FrameIDs.length; + const samples = events.get(stackTraceID) ?? 0; + + let currentNode = 0; + tree.Samples[currentNode] += samples; + + for (let i = 0; i < lenStackTrace; i++) { + const frameID = stackTrace.FrameIDs[i]; + const fileID = stackTrace.FileIDs[i]; + const addressOrLine = stackTrace.AddressOrLines[i]; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; + + const frameGroupID = createFrameGroupID( + fileID, + addressOrLine, + executable.FileName, + frame.FileName, + frame.FunctionName + ); + + let node = tree.Edges[currentNode].get(frameGroupID); + + if (node === undefined) { + const metadata = createStackFrameMetadata({ + FrameID: frameID, + FileID: fileID, + AddressOrLine: addressOrLine, + FrameType: stackTrace.Types[i], + FunctionName: frame.FunctionName, + FunctionOffset: frame.FunctionOffset, + SourceLine: frame.LineNumber, + SourceFilename: frame.FileName, + ExeFileName: executable.FileName, + }); + + node = insertNode(tree, currentNode, metadata, frameGroupID, samples); + } else { + tree.Samples[node] += samples; + } + + tree.CountInclusive[node] += samples; + + if (i === lenStackTrace - 1) { + // Leaf frame: sum up counts for exclusive CPU. + tree.CountExclusive[node] += samples; + } + currentNode = node; + } + } + + tree.CountExclusive[0] = 0; + tree.CountInclusive[0] = tree.Samples[0]; + + return tree; +} diff --git a/x-pack/plugins/profiling/common/callercallee.ts b/x-pack/plugins/profiling/common/callercallee.ts deleted file mode 100644 index 60573306d7f29..0000000000000 --- a/x-pack/plugins/profiling/common/callercallee.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createFrameGroup, createFrameGroupID, FrameGroupID } from './frame_group'; -import { - createStackFrameMetadata, - emptyStackTrace, - Executable, - FileID, - StackFrame, - StackFrameID, - StackFrameMetadata, - StackTrace, - StackTraceID, -} from './profiling'; - -export interface CallerCalleeNode { - Callers: Map; - Callees: Map; - FrameMetadata: StackFrameMetadata; - FrameGroupID: FrameGroupID; - Samples: number; - CountInclusive: number; - CountExclusive: number; -} - -export function createCallerCalleeNode( - frameMetadata: StackFrameMetadata, - frameGroupID: FrameGroupID, - samples: number -): CallerCalleeNode { - return { - Callers: new Map(), - Callees: new Map(), - FrameMetadata: frameMetadata, - FrameGroupID: frameGroupID, - Samples: samples, - CountInclusive: 0, - CountExclusive: 0, - }; -} - -export interface CallerCalleeGraph { - root: CallerCalleeNode; - size: number; -} - -// createCallerCalleeGraph creates a graph in the internal representation -// from a StackFrameMetadata that identifies the "centered" function and -// the trace results that provide traces and the number of times that the -// trace has been seen. -// -// The resulting data structure contains all of the data, but is not yet in the -// form most easily digestible by others. -export function createCallerCalleeGraph( - events: Map, - stackTraces: Map, - stackFrames: Map, - executables: Map -): CallerCalleeGraph { - // Create a root node for the graph - const rootFrame = createStackFrameMetadata(); - const rootFrameGroup = createFrameGroup( - rootFrame.FileID, - rootFrame.AddressOrLine, - rootFrame.ExeFileName, - rootFrame.SourceFilename, - rootFrame.FunctionName - ); - const rootFrameGroupID = createFrameGroupID(rootFrameGroup); - const root = createCallerCalleeNode(rootFrame, rootFrameGroupID, 0); - const graph: CallerCalleeGraph = { root, size: 1 }; - - const sortedStackTraceIDs = new Array(); - for (const trace of stackTraces.keys()) { - sortedStackTraceIDs.push(trace); - } - sortedStackTraceIDs.sort((t1, t2) => { - return t1.localeCompare(t2); - }); - - // Walk through all traces that contain the root. Increment the count of the - // root by the count of that trace. Walk "up" the trace (through the callers) - // and add the count of the trace to each caller. Then walk "down" the trace - // (through the callees) and add the count of the trace to each callee. - - for (const stackTraceID of sortedStackTraceIDs) { - // The slice of frames is ordered so that the leaf function is at the - // highest index. This means that the "first part" of the slice are the - // callers, and the "second part" are the callees. - // - // We currently assume there are no callers. - - // It is possible that we do not have a stacktrace for an event, - // e.g. when stopping the host agent or on network errors. - const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace; - const lenStackTrace = stackTrace.FrameIDs.length; - const samples = events.get(stackTraceID)!; - - let currentNode = root; - root.Samples += samples; - - for (let i = 0; i < lenStackTrace; i++) { - const frameID = stackTrace.FrameIDs[i]; - const fileID = stackTrace.FileIDs[i]; - const addressOrLine = stackTrace.AddressOrLines[i]; - const frame = stackFrames.get(frameID)!; - const executable = executables.get(fileID)!; - - const frameGroup = createFrameGroup( - fileID, - addressOrLine, - executable.FileName, - frame.FileName, - frame.FunctionName - ); - const frameGroupID = createFrameGroupID(frameGroup); - - let node = currentNode.Callees.get(frameGroupID); - - if (node === undefined) { - const callee = createStackFrameMetadata({ - FrameID: frameID, - FileID: fileID, - AddressOrLine: addressOrLine, - FrameType: stackTrace.Types[i], - FunctionName: frame.FunctionName, - FunctionOffset: frame.FunctionOffset, - SourceLine: frame.LineNumber, - SourceFilename: frame.FileName, - ExeFileName: executable.FileName, - }); - - node = createCallerCalleeNode(callee, frameGroupID, samples); - currentNode.Callees.set(frameGroupID, node); - graph.size++; - } else { - node.Samples += samples; - } - - node.CountInclusive += samples; - - if (i === lenStackTrace - 1) { - // Leaf frame: sum up counts for exclusive CPU. - node.CountExclusive += samples; - } - currentNode = node; - } - } - - root.CountExclusive = 0; - root.CountInclusive = root.Samples; - - return graph; -} - -export function sortCallerCalleeNodes( - nodes: Map -): CallerCalleeNode[] { - const sortedNodes = new Array(); - for (const [_, node] of nodes) { - sortedNodes.push(node); - } - return sortedNodes.sort((n1, n2) => { - if (n1.Samples > n2.Samples) { - return -1; - } - if (n1.Samples < n2.Samples) { - return 1; - } - return n1.FrameGroupID.localeCompare(n2.FrameGroupID); - }); -} diff --git a/x-pack/plugins/profiling/common/flamegraph.test.ts b/x-pack/plugins/profiling/common/flamegraph.test.ts new file mode 100644 index 0000000000000..3852d0152bf12 --- /dev/null +++ b/x-pack/plugins/profiling/common/flamegraph.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sum } from 'lodash'; +import { createCalleeTree } from './callee'; +import { createColumnarViewModel, createFlameGraph } from './flamegraph'; + +import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; + +describe('Flamegraph operations', () => { + test('1', () => { + const totalSamples = sum([...events.values()]); + const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); + + const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); + const graph = createFlameGraph(tree, 60, totalSamples, totalSamples); + + expect(graph.Size).toEqual(totalFrames - 2); + + const viewModel1 = createColumnarViewModel(graph); + + expect(sum(viewModel1.color)).toBeGreaterThan(0); + + const viewModel2 = createColumnarViewModel(graph, false); + + expect(sum(viewModel2.color)).toEqual(0); + }); +}); diff --git a/x-pack/plugins/profiling/common/flamegraph.ts b/x-pack/plugins/profiling/common/flamegraph.ts index 71c4128b01cbb..e392022a18fcf 100644 --- a/x-pack/plugins/profiling/common/flamegraph.ts +++ b/x-pack/plugins/profiling/common/flamegraph.ts @@ -5,37 +5,24 @@ * 2.0. */ -import fnv from 'fnv-plus'; -import { CallerCalleeGraph, sortCallerCalleeNodes } from './callercallee'; -import { getCalleeLabel } from './profiling'; +import { ColumnarViewModel } from '@elastic/charts'; + +import { CalleeTree } from './callee'; + +export interface ElasticFlameGraph { + Size: number; + Edges: number[][]; -interface ColumnarCallerCallee { - Label: string[]; - Value: number[]; - X: number[]; - Y: number[]; - Color: number[]; - CountInclusive: number[]; - CountExclusive: number[]; ID: string[]; + FrameType: number[]; FrameID: string[]; ExecutableID: string[]; -} - -export interface FlameGraph { Label: string[]; - Value: number[]; - Position: number[]; - Size: number[]; - Color: number[]; + + Samples: number[]; CountInclusive: number[]; CountExclusive: number[]; - ID: string[]; - FrameID: string[]; - ExecutableID: string[]; -} -export interface ElasticFlameGraph extends FlameGraph { TotalSeconds: number; TotalTraces: number; SampledTraces: number; @@ -94,110 +81,119 @@ function normalize(n: number, lower: number, upper: number): number { return (n - lower) / (upper - lower); } -// createColumnarCallerCallee flattens the intermediate representation of the diagram -// into a columnar format that is more compact than JSON. This representation will later -// need to be normalized into the response ultimately consumed by the flamegraph. -export function createColumnarCallerCallee(graph: CallerCalleeGraph): ColumnarCallerCallee { - const numCallees = graph.size; - const columnar: ColumnarCallerCallee = { - Label: new Array(numCallees), - Value: new Array(numCallees), - X: new Array(numCallees), - Y: new Array(numCallees), - Color: new Array(numCallees * 4), - CountInclusive: new Array(numCallees), - CountExclusive: new Array(numCallees), - ID: new Array(numCallees), - FrameID: new Array(numCallees), - ExecutableID: new Array(numCallees), +// createFlameGraph encapsulates the tree representation into a serialized form. +export function createFlameGraph( + tree: CalleeTree, + totalSeconds: number, + totalTraces: number, + sampledTraces: number +): ElasticFlameGraph { + const graph: ElasticFlameGraph = { + Size: tree.Size, + Edges: new Array(tree.Size), + + ID: tree.ID.slice(0, tree.Size), + Label: tree.Label.slice(0, tree.Size), + FrameID: tree.FrameID.slice(0, tree.Size), + FrameType: tree.FrameType.slice(0, tree.Size), + ExecutableID: tree.FileID.slice(0, tree.Size), + + Samples: tree.Samples.slice(0, tree.Size), + CountInclusive: tree.CountInclusive.slice(0, tree.Size), + CountExclusive: tree.CountExclusive.slice(0, tree.Size), + + TotalSeconds: totalSeconds, + TotalTraces: totalTraces, + SampledTraces: sampledTraces, }; - const queue = [{ x: 0, depth: 1, node: graph.root, parentID: 'root' }]; - - let idx = 0; - while (queue.length > 0) { - const { x, depth, node, parentID } = queue.pop()!; - - if (x === 0 && depth === 1) { - columnar.Label[idx] = 'root: Represents 100% of CPU time.'; - } else { - columnar.Label[idx] = getCalleeLabel(node.FrameMetadata); + for (let i = 0; i < tree.Size; i++) { + let j = 0; + const nodes = new Array(tree.Edges[i].size); + for (const [, n] of tree.Edges[i]) { + nodes[j] = n; + j++; } - columnar.Value[idx] = node.Samples; - columnar.X[idx] = x; - columnar.Y[idx] = depth; - - const [red, green, blue, alpha] = rgbToRGBA(frameTypeToRGB(node.FrameMetadata.FrameType, x)); - const j = 4 * idx; - columnar.Color[j] = red; - columnar.Color[j + 1] = green; - columnar.Color[j + 2] = blue; - columnar.Color[j + 3] = alpha; + graph.Edges[i] = nodes; + } - columnar.CountInclusive[idx] = node.CountInclusive; - columnar.CountExclusive[idx] = node.CountExclusive; + return graph; +} - const id = fnv.fast1a64utf(`${parentID}${node.FrameGroupID}`).toString(); +// createColumnarViewModel normalizes the columnar representation into a form +// consumed by the flamegraph in the UI. +export function createColumnarViewModel( + flamegraph: ElasticFlameGraph, + assignColors: boolean = true +): ColumnarViewModel { + const numNodes = flamegraph.Size; + const xs = new Float32Array(numNodes); + const ys = new Float32Array(numNodes); - columnar.ID[idx] = id; - columnar.FrameID[idx] = node.FrameMetadata.FrameID; - columnar.ExecutableID[idx] = node.FrameMetadata.FileID; + const queue = [{ x: 0, depth: 1, node: 0 }]; - // For a deterministic result we have to walk the callers / callees in a deterministic - // order. A deterministic result allows deterministic UI views, something that users expect. - const callees = sortCallerCalleeNodes(node.Callees); + while (queue.length > 0) { + const { x, depth, node } = queue.pop()!; + + xs[node] = x; + ys[node] = depth; + + // For a deterministic result we have to walk the callees in a deterministic + // order. A deterministic result allows deterministic UI views, something + // that users expect. + const children = flamegraph.Edges[node].sort((n1, n2) => { + if (flamegraph.Samples[n1] > flamegraph.Samples[n2]) { + return -1; + } + if (flamegraph.Samples[n1] < flamegraph.Samples[n2]) { + return 1; + } + return flamegraph.ID[n1].localeCompare(flamegraph.ID[n2]); + }); let delta = 0; - for (const callee of callees) { - delta += callee.Samples; + for (const child of children) { + delta += flamegraph.Samples[child]; } - for (let i = callees.length - 1; i >= 0; i--) { - delta -= callees[i].Samples; - queue.push({ x: x + delta, depth: depth + 1, node: callees[i], parentID: id }); + for (let i = children.length - 1; i >= 0; i--) { + delta -= flamegraph.Samples[children[i]]; + queue.push({ x: x + delta, depth: depth + 1, node: children[i] }); } - - idx++; } - return columnar; -} + const colors = new Float32Array(numNodes * 4); -// createFlameGraph normalizes the intermediate columnar representation into the -// response ultimately consumed by the flamegraph in the UI. -export function createFlameGraph(columnar: ColumnarCallerCallee): FlameGraph { - const graph: FlameGraph = { - Label: [], - Value: [], - Position: [], - Size: [], - Color: [], - CountInclusive: [], - CountExclusive: [], - ID: [], - FrameID: [], - ExecutableID: [], - }; + if (assignColors) { + for (let i = 0; i < numNodes; i++) { + const rgba = rgbToRGBA(frameTypeToRGB(flamegraph.FrameType[i], xs[i])); + colors.set(rgba, 4 * i); + } + } + + const position = new Float32Array(numNodes * 2); + const maxX = flamegraph.Samples[0]; + const maxY = ys.reduce((max, n) => (n > max ? n : max), 0); - graph.Label = columnar.Label; - graph.Value = columnar.Value; - graph.Color = columnar.Color; - graph.CountInclusive = columnar.CountInclusive; - graph.CountExclusive = columnar.CountExclusive; - graph.ID = columnar.ID; - graph.FrameID = columnar.FrameID; - graph.ExecutableID = columnar.ExecutableID; - - const maxX = columnar.Value[0]; - const maxY = columnar.Y.reduce((max, n) => (n > max ? n : max), 0); - - for (let i = 0; i < columnar.X.length; i++) { - const x = normalize(columnar.X[i], 0, maxX); - const y = normalize(maxY - columnar.Y[i], 0, maxY); - graph.Position.push(x, y); + for (let i = 0; i < numNodes; i++) { + const j = 2 * i; + position[j] = normalize(xs[i], 0, maxX); + position[j + 1] = normalize(maxY - ys[i], 0, maxY); } - graph.Size = graph.Value.map((n) => normalize(n, 0, maxX)); + const size = new Float32Array(numNodes); - return graph; + for (let i = 0; i < numNodes; i++) { + size[i] = normalize(flamegraph.Samples[i], 0, maxX); + } + + return { + label: flamegraph.Label.slice(0, numNodes), + value: Float64Array.from(flamegraph.Samples.slice(0, numNodes)), + color: colors, + position0: position, + position1: position, + size0: size, + size1: size, + } as ColumnarViewModel; } diff --git a/x-pack/plugins/profiling/common/frame_group.test.ts b/x-pack/plugins/profiling/common/frame_group.test.ts index b8dd3c8d03632..15f1e0e4edc7f 100644 --- a/x-pack/plugins/profiling/common/frame_group.test.ts +++ b/x-pack/plugins/profiling/common/frame_group.test.ts @@ -5,40 +5,116 @@ * 2.0. */ -import { createFrameGroup, createFrameGroupID } from './frame_group'; +import { createFrameGroupID } from './frame_group'; -const nonSymbolizedFrameGroups = [ - createFrameGroup('0x0123456789ABCDEF', 102938, '', '', ''), - createFrameGroup('0x0123456789ABCDEF', 1234, '', '', ''), - createFrameGroup('0x0102030405060708', 1234, '', '', ''), +const nonSymbolizedTests = [ + { + params: { + fileID: '0x0123456789ABCDEF', + addressOrLine: 102938, + exeFilename: '', + sourceFilename: '', + functionName: '', + }, + expected: 'empty;0x0123456789ABCDEF;102938', + }, + { + params: { + fileID: '0x0123456789ABCDEF', + addressOrLine: 1234, + exeFilename: 'libpthread', + sourceFilename: '', + functionName: '', + }, + expected: 'empty;0x0123456789ABCDEF;1234', + }, ]; -const elfSymbolizedFrameGroups = [ - createFrameGroup('0x0123456789ABCDEF', 0, 'libc', '', 'strlen()'), - createFrameGroup('0xFEDCBA9876543210', 0, 'libc', '', 'strtok()'), - createFrameGroup('0xFEDCBA9876543210', 0, 'myapp', '', 'main()'), +const elfSymbolizedTests = [ + { + params: { + fileID: '0x0123456789ABCDEF', + addressOrLine: 0, + exeFilename: 'libc', + sourceFilename: '', + functionName: 'strlen()', + }, + expected: 'elf;libc;strlen()', + }, + { + params: { + fileID: '0xFEDCBA9876543210', + addressOrLine: 8888, + exeFilename: 'libc', + sourceFilename: '', + functionName: 'strtok()', + }, + expected: 'elf;libc;strtok()', + }, ]; -const symbolizedFrameGroups = [ - createFrameGroup('', 0, 'chrome', 'strlen()', 'strlen()'), - createFrameGroup('', 0, 'dockerd', 'main()', 'createTask()'), - createFrameGroup('', 0, 'oom_reaper', 'main()', 'crash()'), +const symbolizedTests = [ + { + params: { + fileID: '', + addressOrLine: 0, + exeFilename: 'chrome', + sourceFilename: 'strlen()', + functionName: 'strlen()', + }, + expected: 'full;chrome;strlen();strlen()', + }, + { + params: { + fileID: '', + addressOrLine: 0, + exeFilename: 'oom_reaper', + sourceFilename: 'main()', + functionName: 'crash()', + }, + expected: 'full;oom_reaper;crash();main()', + }, ]; describe('Frame group operations', () => { describe('check serialization for', () => { test('non-symbolized frame', () => { - expect(createFrameGroupID(nonSymbolizedFrameGroups[0])).toEqual( - 'empty;0x0123456789ABCDEF;102938' - ); + for (const test of nonSymbolizedTests) { + const frameGroupID = createFrameGroupID( + test.params.fileID, + test.params.addressOrLine, + test.params.exeFilename, + test.params.sourceFilename, + test.params.functionName + ); + expect(frameGroupID).toEqual(test.expected); + } }); test('non-symbolized ELF frame', () => { - expect(createFrameGroupID(elfSymbolizedFrameGroups[0])).toEqual('elf;libc;strlen()'); + for (const test of elfSymbolizedTests) { + const frameGroupID = createFrameGroupID( + test.params.fileID, + test.params.addressOrLine, + test.params.exeFilename, + test.params.sourceFilename, + test.params.functionName + ); + expect(frameGroupID).toEqual(test.expected); + } }); test('symbolized frame', () => { - expect(createFrameGroupID(symbolizedFrameGroups[0])).toEqual('full;chrome;strlen();strlen()'); + for (const test of symbolizedTests) { + const frameGroupID = createFrameGroupID( + test.params.fileID, + test.params.addressOrLine, + test.params.exeFilename, + test.params.sourceFilename, + test.params.functionName + ); + expect(frameGroupID).toEqual(test.expected); + } }); }); }); diff --git a/x-pack/plugins/profiling/common/frame_group.ts b/x-pack/plugins/profiling/common/frame_group.ts index 5987ecdf2ebdc..30a29fe240e1f 100644 --- a/x-pack/plugins/profiling/common/frame_group.ts +++ b/x-pack/plugins/profiling/common/frame_group.ts @@ -9,86 +9,26 @@ import { StackFrameMetadata } from './profiling'; export type FrameGroupID = string; -enum FrameGroupName { - EMPTY = 'empty', - ELF = 'elf', - FULL = 'full', -} - -interface BaseFrameGroup { - readonly name: FrameGroupName; -} - -interface EmptyFrameGroup extends BaseFrameGroup { - readonly name: FrameGroupName.EMPTY; - readonly fileID: StackFrameMetadata['FileID']; - readonly addressOrLine: StackFrameMetadata['AddressOrLine']; -} - -interface ElfFrameGroup extends BaseFrameGroup { - readonly name: FrameGroupName.ELF; - readonly fileID: StackFrameMetadata['FileID']; - readonly exeFilename: StackFrameMetadata['ExeFileName']; - readonly functionName: StackFrameMetadata['FunctionName']; -} - -interface FullFrameGroup extends BaseFrameGroup { - readonly name: FrameGroupName.FULL; - readonly exeFilename: StackFrameMetadata['ExeFileName']; - readonly functionName: StackFrameMetadata['FunctionName']; - readonly sourceFilename: StackFrameMetadata['SourceFilename']; -} - -export type FrameGroup = EmptyFrameGroup | ElfFrameGroup | FullFrameGroup; - -// createFrameGroup is the "standard" way of grouping frames, by commonly +// createFrameGroupID is the "standard" way of grouping frames, by commonly // shared group identifiers. // // For ELF-symbolized frames, group by FunctionName, ExeFileName and FileID. // For non-symbolized frames, group by FileID and AddressOrLine. // otherwise group by ExeFileName, SourceFilename and FunctionName. -export function createFrameGroup( +export function createFrameGroupID( fileID: StackFrameMetadata['FileID'], addressOrLine: StackFrameMetadata['AddressOrLine'], exeFilename: StackFrameMetadata['ExeFileName'], sourceFilename: StackFrameMetadata['SourceFilename'], functionName: StackFrameMetadata['FunctionName'] -): FrameGroup { +): FrameGroupID { if (functionName === '') { - return { - name: FrameGroupName.EMPTY, - fileID, - addressOrLine, - } as EmptyFrameGroup; + return `empty;${fileID};${addressOrLine}`; } if (sourceFilename === '') { - return { - name: FrameGroupName.ELF, - fileID, - exeFilename, - functionName, - } as ElfFrameGroup; + return `elf;${exeFilename};${functionName}`; } - return { - name: FrameGroupName.FULL, - exeFilename, - functionName, - sourceFilename, - } as FullFrameGroup; -} - -export function createFrameGroupID(frameGroup: FrameGroup): FrameGroupID { - switch (frameGroup.name) { - case FrameGroupName.EMPTY: - return `${frameGroup.name};${frameGroup.fileID};${frameGroup.addressOrLine}`; - break; - case FrameGroupName.ELF: - return `${frameGroup.name};${frameGroup.exeFilename};${frameGroup.functionName}`; - break; - case FrameGroupName.FULL: - return `${frameGroup.name};${frameGroup.exeFilename};${frameGroup.functionName};${frameGroup.sourceFilename}`; - break; - } + return `full;${exeFilename};${functionName};${sourceFilename}`; } diff --git a/x-pack/plugins/profiling/common/functions.ts b/x-pack/plugins/profiling/common/functions.ts index 5ca59163227d1..70aa7cae864d7 100644 --- a/x-pack/plugins/profiling/common/functions.ts +++ b/x-pack/plugins/profiling/common/functions.ts @@ -5,9 +5,11 @@ * 2.0. */ import * as t from 'io-ts'; -import { createFrameGroup, createFrameGroupID, FrameGroupID } from './frame_group'; +import { createFrameGroupID, FrameGroupID } from './frame_group'; import { createStackFrameMetadata, + emptyExecutable, + emptyStackFrame, emptyStackTrace, Executable, FileID, @@ -66,17 +68,16 @@ export function createTopNFunctions( const frameID = stackTrace.FrameIDs[i]; const fileID = stackTrace.FileIDs[i]; const addressOrLine = stackTrace.AddressOrLines[i]; - const frame = stackFrames.get(frameID)!; - const executable = executables.get(fileID)!; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; - const frameGroup = createFrameGroup( + const frameGroupID = createFrameGroupID( fileID, addressOrLine, executable.FileName, frame.FileName, frame.FunctionName ); - const frameGroupID = createFrameGroupID(frameGroup); let topNFunction = topNFunctions.get(frameGroupID); diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts index 8017189280ddd..6779a16774959 100644 --- a/x-pack/plugins/profiling/common/profiling.ts +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -28,18 +28,20 @@ export enum FrameType { JavaScript, } +const frameTypeDescriptions = { + [FrameType.Unsymbolized]: '', + [FrameType.Python]: 'Python', + [FrameType.PHP]: 'PHP', + [FrameType.Native]: 'Native', + [FrameType.Kernel]: 'Kernel', + [FrameType.JVM]: 'JVM/Hotspot', + [FrameType.Ruby]: 'Ruby', + [FrameType.Perl]: 'Perl', + [FrameType.JavaScript]: 'JavaScript', +}; + export function describeFrameType(ft: FrameType): string { - return { - [FrameType.Unsymbolized]: '', - [FrameType.Python]: 'Python', - [FrameType.PHP]: 'PHP', - [FrameType.Native]: 'Native', - [FrameType.Kernel]: 'Kernel', - [FrameType.JVM]: 'JVM/Hotspot', - [FrameType.Ruby]: 'Ruby', - [FrameType.Perl]: 'Perl', - [FrameType.JavaScript]: 'JavaScript', - }[ft]; + return frameTypeDescriptions[ft]; } export interface StackTraceEvent { @@ -69,10 +71,22 @@ export interface StackFrame { SourceType: number; } +export const emptyStackFrame: StackFrame = { + FileName: '', + FunctionName: '', + FunctionOffset: 0, + LineNumber: 0, + SourceType: 0, +}; + export interface Executable { FileName: string; } +export const emptyExecutable: Executable = { + FileName: '', +}; + export interface StackFrameMetadata { // StackTrace.FrameID FrameID: string; @@ -221,8 +235,8 @@ export function groupStackFrameMetadataByStackTrace( const frameID = trace.FrameIDs[i]; const fileID = trace.FileIDs[i]; const addressOrLine = trace.AddressOrLines[i]; - const frame = stackFrames.get(frameID)!; - const executable = executables.get(fileID)!; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; frameMetadata[i] = createStackFrameMetadata({ FrameID: frameID, diff --git a/x-pack/plugins/profiling/public/components/flamegraph.tsx b/x-pack/plugins/profiling/public/components/flamegraph.tsx index afc241394aafb..9abac27ef9fb2 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph.tsx @@ -226,9 +226,9 @@ export const FlameGraph: React.FC = ({ exeFileName: highlightedFrame.ExeFileName, sourceFileName: highlightedFrame.SourceFilename, functionName: highlightedFrame.FunctionName, - samples: primaryFlamegraph.Value[highlightedVmIndex], + samples: primaryFlamegraph.Samples[highlightedVmIndex], childSamples: - primaryFlamegraph.Value[highlightedVmIndex] - + primaryFlamegraph.Samples[highlightedVmIndex] - primaryFlamegraph.CountExclusive[highlightedVmIndex], } : undefined; @@ -275,7 +275,7 @@ export const FlameGraph: React.FC = ({ const valueIndex = props.values[0].valueAccessor as number; const label = primaryFlamegraph.Label[valueIndex]; - const samples = primaryFlamegraph.Value[valueIndex]; + const samples = primaryFlamegraph.Samples[valueIndex]; const countInclusive = primaryFlamegraph.CountInclusive[valueIndex]; const countExclusive = primaryFlamegraph.CountExclusive[valueIndex]; const nodeID = primaryFlamegraph.ID[valueIndex]; @@ -291,8 +291,8 @@ export const FlameGraph: React.FC = ({ comparisonCountInclusive={comparisonNode?.CountInclusive} comparisonCountExclusive={comparisonNode?.CountExclusive} totalSamples={totalSamples} - comparisonTotalSamples={comparisonFlamegraph?.Value[0]} - comparisonSamples={comparisonNode?.Value} + comparisonTotalSamples={comparisonFlamegraph?.Samples[0]} + comparisonSamples={comparisonNode?.Samples} /> ); }, diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts index 1402897b966ac..c63a73185d26b 100644 --- a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -4,10 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ColumnarViewModel } from '@elastic/charts'; import d3 from 'd3'; import { sum, uniqueId } from 'lodash'; -import { ElasticFlameGraph, FlameGraphComparisonMode, rgbToRGBA } from '../../../common/flamegraph'; +import { + createColumnarViewModel, + ElasticFlameGraph, + FlameGraphComparisonMode, + rgbToRGBA, +} from '../../../common/flamegraph'; import { getInterpolationValue } from './get_interpolation_value'; const nullColumnarViewModel = { @@ -37,21 +41,19 @@ export function getFlamegraphModel({ }) { const comparisonNodesById: Record< string, - { Value: number; CountInclusive: number; CountExclusive: number } + { Samples: number; CountInclusive: number; CountExclusive: number } > = {}; if (!primaryFlamegraph || !primaryFlamegraph.Label || primaryFlamegraph.Label.length === 0) { return { key: uniqueId(), viewModel: nullColumnarViewModel, comparisonNodesById }; } - let colors: number[] | undefined = primaryFlamegraph.Color; + const viewModel = createColumnarViewModel(primaryFlamegraph, comparisonFlamegraph === undefined); if (comparisonFlamegraph) { - colors = []; - comparisonFlamegraph.ID.forEach((nodeID, index) => { comparisonNodesById[nodeID] = { - Value: comparisonFlamegraph.Value[index], + Samples: comparisonFlamegraph.Samples[index], CountInclusive: comparisonFlamegraph.CountInclusive[index], CountExclusive: comparisonFlamegraph.CountExclusive[index], }; @@ -86,8 +88,8 @@ export function getFlamegraphModel({ : primaryFlamegraph.TotalSeconds / comparisonFlamegraph.TotalSeconds; primaryFlamegraph.ID.forEach((nodeID, index) => { - const samples = primaryFlamegraph.Value[index]; - const comparisonSamples = comparisonNodesById[nodeID]?.Value as number | undefined; + const samples = primaryFlamegraph.Samples[index]; + const comparisonSamples = comparisonNodesById[nodeID]?.Samples as number | undefined; const foreground = comparisonMode === FlameGraphComparisonMode.Absolute ? samples : samples / totalSamples; @@ -110,26 +112,14 @@ export function getFlamegraphModel({ ? positiveChangeInterpolator(interpolationValue) : negativeChangeInterpolator(Math.abs(interpolationValue)); - colors!.push(...rgbToRGBA(Number(nodeColor.replace('#', '0x')))); + const rgba = rgbToRGBA(Number(nodeColor.replace('#', '0x'))); + viewModel.color.set(rgba, 4 * index); }); } - const value = new Float64Array(primaryFlamegraph.Value); - const position = new Float32Array(primaryFlamegraph.Position); - const size = new Float32Array(primaryFlamegraph.Size); - const color = new Float32Array(colors); - return { key: uniqueId(), - viewModel: { - label: primaryFlamegraph.Label, - value, - color, - position0: position, - position1: position, - size0: size, - size1: size, - } as ColumnarViewModel, + viewModel, comparisonNodesById, }; } diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index 0f37ed4886412..6d27305a82c69 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -8,12 +8,8 @@ import { schema } from '@kbn/config-schema'; import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; -import { createCallerCalleeGraph } from '../../common/callercallee'; -import { - createColumnarCallerCallee, - createFlameGraph, - ElasticFlameGraph, -} from '../../common/flamegraph'; +import { createCalleeTree } from '../../common/callee'; +import { createFlameGraph } from '../../common/flamegraph'; import { createProfilingEsClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; @@ -46,55 +42,57 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP }); const totalSeconds = timeTo - timeFrom; - const { stackTraces, executables, stackFrames, eventsIndex, totalCount, stackTraceEvents } = - await getExecutablesAndStackTraces({ - logger, - client: createProfilingEsClient({ request, esClient }), - filter, - sampleSize: targetSampleSize, - }); + const { + stackTraces, + executables, + stackFrames, + eventsIndex, + totalCount, + totalFrames, + stackTraceEvents, + } = await getExecutablesAndStackTraces({ + logger, + client: createProfilingEsClient({ request, esClient }), + filter, + sampleSize: targetSampleSize, + }); const flamegraph = await withProfilingSpan('create_flamegraph', async () => { const t0 = Date.now(); - const graph = createCallerCalleeGraph( + const tree = createCalleeTree( stackTraceEvents, stackTraces, stackFrames, - executables + executables, + totalFrames ); - logger.info(`creating caller-callee graph took ${Date.now() - t0} ms`); + logger.info(`creating callee tree took ${Date.now() - t0} ms`); - const t1 = Date.now(); - const columnar = createColumnarCallerCallee(graph); - logger.info(`creating columnar caller-callee graph took ${Date.now() - t1} ms`); + // sampleRate is 1/5^N, with N being the downsampled index the events were fetched from. + // N=0: full events table (sampleRate is 1) + // N=1: downsampled by 5 (sampleRate is 0.2) + // ... - const t2 = Date.now(); - const fg = createFlameGraph(columnar); - logger.info(`creating flamegraph took ${Date.now() - t2} ms`); + // totalCount is the sum(Count) of all events in the filter range in the + // downsampled index we were looking at. + // To estimate how many events we have in the full events index: totalCount / sampleRate. + // Do the same for single entries in the events array. + + const t1 = Date.now(); + const fg = createFlameGraph( + tree, + totalSeconds, + Math.floor(totalCount / eventsIndex.sampleRate), + totalCount + ); + logger.info(`creating flamegraph took ${Date.now() - t1} ms`); return fg; }); - // sampleRate is 1/5^N, with N being the downsampled index the events were fetched from. - // N=0: full events table (sampleRate is 1) - // N=1: downsampled by 5 (sampleRate is 0.2) - // ... - - // totalCount is the sum(Count) of all events in the filter range in the - // downsampled index we were looking at. - // To estimate how many events we have in the full events index: totalCount / sampleRate. - // Do the same for single entries in the events array. - - const body: ElasticFlameGraph = { - ...flamegraph, - TotalSeconds: totalSeconds, - TotalTraces: Math.floor(totalCount / eventsIndex.sampleRate), - SampledTraces: totalCount, - }; - logger.info('returning payload response to client'); - return response.ok({ body }); + return response.ok({ body: flamegraph }); } catch (e) { logger.error(e); return response.customError({ diff --git a/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts b/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts index 4025022222177..2c63b82c443f7 100644 --- a/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts +++ b/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts @@ -68,7 +68,7 @@ export async function getExecutablesAndStackTraces({ stackTraceEvents.set(id, Math.floor(count / (eventsIndex.sampleRate * p))); } - const { stackTraces, stackFrameDocIDs, executableDocIDs } = await mgetStackTraces({ + const { stackTraces, totalFrames, stackFrameDocIDs, executableDocIDs } = await mgetStackTraces({ logger, client, events: stackTraceEvents, @@ -86,6 +86,7 @@ export async function getExecutablesAndStackTraces({ stackFrames, stackTraceEvents, totalCount, + totalFrames, eventsIndex, }; }); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index fca1c56ebe711..4ae7d91596f10 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -18,6 +18,8 @@ import { ProfilingStackTrace, } from '../../common/elasticsearch'; import { + emptyExecutable, + emptyStackFrame, Executable, FileID, StackFrame, @@ -310,7 +312,7 @@ export async function mgetStackTraces({ ); } - return { stackTraces, stackFrameDocIDs, executableDocIDs }; + return { stackTraces, totalFrames, stackFrameDocIDs, executableDocIDs }; } export async function mgetStackFrames({ @@ -352,13 +354,7 @@ export async function mgetStackFrames({ }); framesFound++; } else { - stackFrames.set(frame._id, { - FileName: '', - FunctionName: '', - FunctionOffset: 0, - LineNumber: 0, - SourceType: 0, - }); + stackFrames.set(frame._id, emptyStackFrame); } } logger.info(`processing data took ${Date.now() - t0} ms`); @@ -403,9 +399,7 @@ export async function mgetExecutables({ }); exeFound++; } else { - executables.set(exe._id, { - FileName: '', - }); + executables.set(exe._id, emptyExecutable); } } logger.info(`processing data took ${Date.now() - t0} ms`); From 5d482652849191d44836bdd83884ab84ce5a1add Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Mon, 26 Sep 2022 13:35:11 -0400 Subject: [PATCH 031/172] Fix error rendering policy editor (#141705) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/server/services/package_policy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 70f65bb9b7987..7f294ca89d65f 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -801,7 +801,6 @@ class PackagePolicyClientImpl implements PackagePolicyClient { savedObjectsClient: soClient, pkgName: packagePolicy!.package!.name, pkgVersion: pkgVersion ?? '', - skipArchive: true, }); } From 74d39d87d82467720b4e0583fac3ff620f683da6 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 26 Sep 2022 11:00:00 -0700 Subject: [PATCH 032/172] [DOCS] Add ML open API output to appendix (#141556) --- .../machine-learning/ml-apis-passthru.adoc | 192 ++++++++++++++++++ .../machine-learning/ml-apis.asciidoc | 10 + docs/api-generated/template/index.mustache | 170 ++++++++++++++++ docs/apis.asciidoc | 14 ++ docs/index.asciidoc | 3 + docs/redirects.asciidoc | 5 - 6 files changed, 389 insertions(+), 5 deletions(-) create mode 100644 docs/api-generated/machine-learning/ml-apis-passthru.adoc create mode 100644 docs/api-generated/machine-learning/ml-apis.asciidoc create mode 100644 docs/api-generated/template/index.mustache create mode 100644 docs/apis.asciidoc diff --git a/docs/api-generated/machine-learning/ml-apis-passthru.adoc b/docs/api-generated/machine-learning/ml-apis-passthru.adoc new file mode 100644 index 0000000000000..a1275f2212ad6 --- /dev/null +++ b/docs/api-generated/machine-learning/ml-apis-passthru.adoc @@ -0,0 +1,192 @@ +//// +This content is generated from the open API specification. +Any modifications made to this file will be overwritten. +//// + +++++ + +++++ diff --git a/docs/api-generated/machine-learning/ml-apis.asciidoc b/docs/api-generated/machine-learning/ml-apis.asciidoc new file mode 100644 index 0000000000000..3482109d4ab3e --- /dev/null +++ b/docs/api-generated/machine-learning/ml-apis.asciidoc @@ -0,0 +1,10 @@ +[[machine-learning-api]] +== Machine learning APIs + +preview::[] + +//// +This file includes content that has been generated from https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/common/openapi. Any modifications required must be done in that open API specification. +//// + +include::ml-apis-passthru.adoc[] \ No newline at end of file diff --git a/docs/api-generated/template/index.mustache b/docs/api-generated/template/index.mustache new file mode 100644 index 0000000000000..8c1162f909508 --- /dev/null +++ b/docs/api-generated/template/index.mustache @@ -0,0 +1,170 @@ +//// +This content is generated from the open API specification. +Any modifications made to this file will be overwritten. +//// + +++++ +
+

Access

+ {{#hasAuthMethods}} +
    + {{#authMethods}} +
  1. {{#isBasic}}HTTP Basic Authentication{{/isBasic}}{{#isOAuth}}OAuth AuthorizationUrl:{{authorizationUrl}}TokenUrl:{{tokenUrl}}{{/isOAuth}}{{#isApiKey}}APIKey KeyParamName:{{keyParamName}} KeyInQuery:{{isKeyInQuery}} KeyInHeader:{{isKeyInHeader}}{{/isApiKey}}
  2. + {{/authMethods}} +
+ {{/hasAuthMethods}} + +

Methods

+ [ Jump to Models ] + + {{! for the tables of content, I cheat and don't use CSS styles.... }} +

Table of Contents

+
{{access}}
+ {{#apiInfo}} + {{#apis}} + {{#operations}} +

{{baseName}}

+ + {{/operations}} + {{/apis}} + {{/apiInfo}} + + {{#apiInfo}} + {{#apis}} + {{#operations}} +

{{baseName}}

+ {{#operation}} +
+
+ Up +
{{httpMethod}} {{path}}
+
{{summary}} ({{nickname}})
+ {{! notes is operation.description. So why rename it and make it super confusing???? }} +
{{notes}}
+ + {{#hasPathParams}} +

Path parameters

+
+ {{#pathParams}}{{>pathParam}}{{/pathParams}} +
+ {{/hasPathParams}} + + {{#hasConsumes}} +

Consumes

+ This API call consumes the following media types via the Content-Type request header: +
    + {{#consumes}} +
  • {{{mediaType}}}
  • + {{/consumes}} +
+ {{/hasConsumes}} + + {{#hasBodyParam}} +

Request body

+
+ {{#bodyParams}}{{>bodyParam}}{{/bodyParams}} +
+ {{/hasBodyParam}} + + {{#hasHeaderParams}} +

Request headers

+
+ {{#headerParams}}{{>headerParam}}{{/headerParams}} +
+ {{/hasHeaderParams}} + + {{#hasQueryParams}} +

Query parameters

+
+ {{#queryParams}}{{>queryParam}}{{/queryParams}} +
+ {{/hasQueryParams}} + + {{#hasFormParams}} +

Form parameters

+
+ {{#formParams}}{{>formParam}}{{/formParams}} +
+ {{/hasFormParams}} + + {{#returnType}} +

Return type

+
+ {{#hasReference}}{{^returnSimpleType}}{{returnContainer}}[{{/returnSimpleType}}{{returnBaseType}}{{^returnSimpleType}}]{{/returnSimpleType}}{{/hasReference}} + {{^hasReference}}{{returnType}}{{/hasReference}} +
+ {{/returnType}} + + + + {{#hasExamples}} + {{#examples}} +

Example data

+
Content-Type: {{{contentType}}}
+
{{{example}}}
+ {{/examples}} + {{/hasExamples}} + + {{#hasProduces}} +

Produces

+ This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
    + {{#produces}} +
  • {{{mediaType}}}
  • + {{/produces}} +
+ {{/hasProduces}} + +

Responses

+ {{#responses}} +

{{code}}

+ {{message}} + {{^containerType}}{{dataType}}{{/containerType}} + {{#examples}} +

Example data

+
Content-Type: {{{contentType}}}
+
{{example}}
+ {{/examples}} + {{/responses}} +
+
+ {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} + +

Models

+ [ Jump to Methods ] + +

Table of Contents

+
    + {{#models}} + {{#model}} +
  1. {{name}}{{#title}} - {{.}}{{/title}}
  2. + {{/model}} + {{/models}} +
+ + {{#models}} + {{#model}} +
+

{{name}}{{#title}} - {{.}}{{/title}} Up

+ {{#unescapedDescription}}
{{.}}
{{/unescapedDescription}} +
+ {{#vars}}
{{name}} {{^required}}(optional){{/required}}
{{^isPrimitiveType}}{{dataType}}{{/isPrimitiveType}} {{unescapedDescription}} {{#dataFormat}}format: {{{.}}}{{/dataFormat}}
+ {{#isEnum}} +
Enum:
+ {{#_enum}}
{{this}}
{{/_enum}} + {{/isEnum}} + {{/vars}} +
+
+ {{/model}} + {{/models}} +
+++++ diff --git a/docs/apis.asciidoc b/docs/apis.asciidoc new file mode 100644 index 0000000000000..8fb4caa7d3fca --- /dev/null +++ b/docs/apis.asciidoc @@ -0,0 +1,14 @@ +[role="exclude",id="apis"] += APIs + +[partintro] +-- + +preview::[] + +These APIs are documented using the OpenAPI specification. The current supported +version of the specification is 3.0. For more information, go to https://openapi-generator.tech/[OpenAPI Generator] + +-- + +include::api-generated/machine-learning/ml-apis.asciidoc[] \ No newline at end of file diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 41321f991c1b0..2823529df18d3 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -29,4 +29,7 @@ include::CHANGELOG.asciidoc[] include::developer/index.asciidoc[] +include::apis.asciidoc[] + include::redirects.asciidoc[] + diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 1a0757453c855..5c12048315dec 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -420,8 +420,3 @@ This page has been deleted. Refer to <>. == Sync machine learning saved objects API This page has been deleted. Refer to <>. - -[role="exclude",id="machine-learning-api"] -== Machine learning APIs - -This page has been deleted. Refer to <>. \ No newline at end of file From 7a3e3bad44a30e6e3bb3180fafcbbd39c37ff913 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 26 Sep 2022 11:08:44 -0700 Subject: [PATCH 033/172] [DOCS] Omit specs for default spaces and extra tags in case APIs (#138779) --- .../plugins/cases/docs/openapi/bundled.json | 4124 +---------------- .../plugins/cases/docs/openapi/bundled.yaml | 3384 +------------- .../components/parameters/space_id.yaml | 2 +- .../cases/docs/openapi/entrypoint.yaml | 31 - .../cases/docs/openapi/paths/api@cases.yaml | 175 - .../docs/openapi/paths/api@cases@_find.yaml | 158 - .../paths/api@cases@alerts@{alertid}.yaml | 37 - .../openapi/paths/api@cases@configure.yaml | 93 - .../api@cases@configure@connectors@_find.yaml | 28 - ...api@cases@configure@{configurationid}.yaml | 57 - .../openapi/paths/api@cases@reporters.yaml | 34 - .../docs/openapi/paths/api@cases@status.yaml | 34 - .../docs/openapi/paths/api@cases@tags.yaml | 36 - .../openapi/paths/api@cases@{caseid}.yaml | 35 - .../paths/api@cases@{caseid}@alerts.yaml | 29 - .../paths/api@cases@{caseid}@comments.yaml | 126 - ...i@cases@{caseid}@comments@{commentid}.yaml | 50 - ...caseid}@connector@{connectorid}@_push.yaml | 35 - .../api@cases@{caseid}@user_actions.yaml | 29 - .../openapi/paths/s@{spaceid}@api@cases.yaml | 3 - .../paths/s@{spaceid}@api@cases@_find.yaml | 1 - ...@{spaceid}@api@cases@alerts@{alertid}.yaml | 1 - .../s@{spaceid}@api@cases@configure.yaml | 2 - ...@api@cases@configure@connectors@_find.yaml | 1 - ...api@cases@configure@{configurationid}.yaml | 1 - .../s@{spaceid}@api@cases@reporters.yaml | 1 - .../paths/s@{spaceid}@api@cases@status.yaml | 1 - .../paths/s@{spaceid}@api@cases@tags.yaml | 1 - .../paths/s@{spaceid}@api@cases@{caseid}.yaml | 1 - ...s@{spaceid}@api@cases@{caseid}@alerts.yaml | 1 - ...{spaceid}@api@cases@{caseid}@comments.yaml | 4 - ...i@cases@{caseid}@comments@{commentid}.yaml | 2 - ...caseid}@connector@{connectorid}@_push.yaml | 1 - ...ceid}@api@cases@{caseid}@user_actions.yaml | 1 - 34 files changed, 43 insertions(+), 8476 deletions(-) delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml delete mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 3822f2f458a0b..4202c658ee4ff 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -29,4052 +29,13 @@ } ], "paths": { - "/api/cases": { - "post": { - "summary": "Creates a case in the default space.", - "operationId": "createCaseDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "description": { - "description": "The description for the case.", - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "tags": { - "description": "The words and phrases that help categorize cases. It can be an empty array.", - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "description": "A title for the case.", - "type": "string" - } - }, - "required": [ - "connector", - "description", - "owner", - "settings", - "tags", - "title" - ] - }, - "examples": { - "createCaseRequest": { - "$ref": "#/components/examples/create_case_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "createCaseResponse": { - "$ref": "#/components/examples/create_case_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "delete": { - "summary": "Deletes one or more cases from the default space.", - "operationId": "deleteCaseDefaultSpace", - "description": "You must have `read` or `all` privileges and the `delete` sub-feature privilege for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "name": "ids", - "description": "The cases that you want to removed. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded.", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "example": "d4e7abb0-b462-11ec-9a8d-698504725a43" - } - ], - "responses": { - "204": { - "description": "Indicates a successful call." - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "patch": { - "summary": "Updates one or more cases in the default space.", - "operationId": "updateCaseDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "cases": { - "type": "array", - "items": { - "type": "object", - "properties": { - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "description": { - "description": "The description for the case.", - "type": "string" - }, - "id": { - "description": "The identifier for the case.", - "type": "string" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "description": "The words and phrases that help categorize cases.", - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "description": "A title for the case.", - "type": "string" - }, - "version": { - "description": "The current version of the case.", - "type": "string" - } - }, - "required": [ - "id", - "version" - ] - } - } - } - }, - "examples": { - "updateCaseRequest": { - "$ref": "#/components/examples/update_case_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "updateCaseResponse": { - "$ref": "#/components/examples/update_case_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/_find": { - "get": { - "summary": "Retrieves a paginated subset of cases from the default space.", - "operationId": "getCasesDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "name": "defaultSearchOperator", - "in": "query", - "description": "The default operator to use for the simple_query_string.", - "schema": { - "type": "string", - "default": "OR" - }, - "example": "OR" - }, - { - "name": "fields", - "in": "query", - "description": "The fields in the entity to return in the response.", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "from", - "in": "query", - "description": "[preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", - "schema": { - "type": "string" - }, - "example": "now-1d", - "x-technical-preview": true - }, - { - "$ref": "#/components/parameters/owner" - }, - { - "name": "page", - "in": "query", - "description": "The page number to return.", - "schema": { - "type": "integer", - "default": 1 - }, - "example": 1 - }, - { - "name": "perPage", - "in": "query", - "description": "The number of rules to return per page.", - "schema": { - "type": "integer", - "default": 20 - }, - "example": 20 - }, - { - "name": "reporters", - "in": "query", - "description": "Filters the returned cases by the user name of the reporter.", - "schema": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "example": "elastic" - }, - { - "name": "search", - "in": "query", - "description": "An Elasticsearch simple_query_string query that filters the objects in the response.", - "schema": { - "type": "string" - } - }, - { - "name": "searchFields", - "in": "query", - "description": "The fields to perform the simple_query_string parsed query against.", - "schema": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - }, - { - "$ref": "#/components/parameters/severity" - }, - { - "name": "sortField", - "in": "query", - "description": "Determines which field is used to sort the results.", - "schema": { - "type": "string", - "enum": [ - "createdAt", - "updatedAt" - ], - "default": "createdAt" - }, - "example": "updatedAt" - }, - { - "name": "sortOrder", - "in": "query", - "description": "Determines the sort order.", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "default": "desc" - }, - "example": "asc" - }, - { - "in": "query", - "name": "status", - "description": "Filters the returned cases by state.", - "schema": { - "type": "string", - "enum": [ - "closed", - "in-progress", - "open" - ] - }, - "example": "open" - }, - { - "name": "tags", - "in": "query", - "description": "Filters the returned cases by tags.", - "schema": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "example": "tag-1" - }, - { - "name": "to", - "in": "query", - "description": "Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression.", - "schema": { - "type": "string" - }, - "example": "now%2B1d", - "x-technical-preview": true - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "cases": { - "type": "array", - "items": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - } - }, - "count_closed_cases": { - "type": "integer" - }, - "count_in_progress_cases": { - "type": "integer" - }, - "count_open_cases": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "per_page": { - "type": "integer" - }, - "total": { - "type": "integer" - } - } - }, - "examples": { - "findCaseResponse": { - "$ref": "#/components/examples/find_case_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/alerts/{alertId}": { - "get": { - "summary": "Returns the cases associated with a specific alert in the default space.", - "operationId": "getCasesByAlertDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "x-technical-preview": true, - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/alert_id" - }, - { - "$ref": "#/components/parameters/owner" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The case identifier." - }, - "title": { - "type": "string", - "description": "The case title." - } - } - }, - "example": [ - { - "id": "06116b80-e1c3-11ec-be9b-9b1838238ee6", - "title": "security_case" - } - ] - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/configure": { - "get": { - "summary": "Retrieves external connection details, such as the closure type and default connector for cases in the default space.", - "operationId": "getCaseConfigurationDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/owner" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-06-01T17:07:17.767Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - } - }, - "error": { - "type": "string", - "example": null - }, - "id": { - "type": "string", - "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" - }, - "mappings": { - "type": "array", - "items": { - "type": "object", - "properties": { - "action_type": { - "type": "string", - "example": "overwrite" - }, - "source": { - "type": "string", - "example": "title" - }, - "target": { - "type": "string", - "example": "summary" - } - } - } - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": "2022-06-01T19:58:48.169Z" - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - }, - "nullable": true - }, - "version": { - "type": "string", - "example": "WzIwNzMsMV0=" - } - } - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "post": { - "summary": "Sets external connection details, such as the closure type and default connector for cases in the default space.", - "operationId": "setCaseConfigurationDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "description": "An object that contains the case settings.", - "type": "object", - "properties": { - "syncAlerts": { - "description": "Turns alert syncing on or off.", - "type": "boolean", - "example": true - } - }, - "required": [ - "syncAlerts" - ] - } - }, - "required": [ - "closure_type", - "connector", - "owner" - ] - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-06-01T17:07:17.767Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - } - }, - "error": { - "type": "string", - "example": null - }, - "id": { - "type": "string", - "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" - }, - "mappings": { - "type": "array", - "items": { - "type": "object", - "properties": { - "action_type": { - "type": "string", - "example": "overwrite" - }, - "source": { - "type": "string", - "example": "title" - }, - "target": { - "type": "string", - "example": "summary" - } - } - } - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": "2022-06-01T19:58:48.169Z" - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - }, - "nullable": true - }, - "version": { - "type": "string", - "example": "WzIwNzMsMV0=" - } - } - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/configure/{configurationId}": { - "patch": { - "summary": "Updates external connection details, such as the closure type and default connector for cases in the default space.", - "operationId": "updateCaseConfigurationDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/configuration_id" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "version": { - "description": "The version of the connector. To retrieve the version value, use the get configuration API.\n", - "type": "string", - "example": "WzIwMiwxXQ==" - } - }, - "required": [ - "version" - ] - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-06-01T17:07:17.767Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - } - }, - "error": { - "type": "string", - "example": null - }, - "id": { - "type": "string", - "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" - }, - "mappings": { - "type": "array", - "items": { - "type": "object", - "properties": { - "action_type": { - "type": "string", - "example": "overwrite" - }, - "source": { - "type": "string", - "example": "title" - }, - "target": { - "type": "string", - "example": "summary" - } - } - } - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": "2022-06-01T19:58:48.169Z" - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } - }, - "nullable": true - }, - "version": { - "type": "string", - "example": "WzIwNzMsMV0=" - } - } - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/configure/connectors/_find": { - "get": { - "summary": "Retrieves information about connectors for cases in the default space.", - "operationId": "getCaseConnectorsDefaultSpace", - "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", - "tags": [ - "cases", - "kibana" - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "actionTypeId": { - "$ref": "#/components/schemas/connector_types" - }, - "config": { - "type": "object", - "properties": { - "apiUrl": { - "type": "string" - }, - "projectKey": { - "type": "string" - } - }, - "additionalProperties": true - }, - "id": { - "type": "string" - }, - "isDeprecated": { - "type": "boolean" - }, - "isMissingSecrets": { - "type": "boolean" - }, - "isPreconfigured": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "referencedByCount": { - "type": "integer" - } - } - } - }, - "examples": { - "findCaseResponse": { - "$ref": "#/components/examples/find_connector_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/reporters": { - "get": { - "summary": "Returns information about the users who opened cases in the default space.", - "operationId": "getCaseReportersDefaultSpace", - "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/owner" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - } - }, - "examples": { - "getReportersResponse": { - "$ref": "#/components/examples/get_reporters_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/status": { - "get": { - "summary": "Returns the number of cases that are open, closed, and in progress.", - "operationId": "getCaseStatusDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "deprecated": true, - "parameters": [ - { - "$ref": "#/components/parameters/owner" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "count_closed_cases": { - "type": "integer" - }, - "count_in_progress_cases": { - "type": "integer" - }, - "count_open_cases": { - "type": "integer" - } - } - }, - "examples": { - "getStatusResponse": { - "$ref": "#/components/examples/get_status_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/tags": { - "get": { - "summary": "Aggregates and returns a list of case tags in the default space.", - "operationId": "getCaseTagsDefaultSpace", - "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "in": "query", - "name": "owner", - "description": "A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/owners" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/owners" - } - } - ] - } - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - }, - "examples": { - "getTagsResponse": { - "$ref": "#/components/examples/get_tags_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}": { - "get": { - "summary": "Retrieves information about a case in the default space.", - "operationId": "getCaseDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - }, - { - "in": "query", - "name": "includeComments", - "description": "Determines whether case comments are returned.", - "deprecated": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "getCaseResponse": { - "$ref": "#/components/examples/get_case_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}/alerts": { - "get": { - "summary": "Gets all alerts attached to a case in the default space.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "operationId": "getCaseAlertsDefaultSpace", - "tags": [ - "cases", - "kibana" - ], - "x-technical-preview": true, - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/alert_response_properties" - } - }, - "examples": { - "createCaseCommentResponse": { - "$ref": "#/components/examples/get_case_alerts_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}/comments": { - "post": { - "summary": "Adds a comment or alert to a case in the default space.", - "operationId": "addCaseCommentDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/case_id" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/add_alert_comment_request_properties" - }, - { - "$ref": "#/components/schemas/add_user_comment_request_properties" - } - ] - }, - "examples": { - "createCaseCommentRequest": { - "$ref": "#/components/examples/add_comment_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "createCaseCommentResponse": { - "$ref": "#/components/examples/add_comment_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "delete": { - "summary": "Deletes all comments and alerts from a case in the default space.", - "operationId": "deleteCaseCommentsDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/case_id" - } - ], - "responses": { - "204": { - "description": "Indicates a successful call." - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "patch": { - "summary": "Updates a comment or alert in a case in the default space.", - "operationId": "updateCaseCommentDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/case_id" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/update_alert_comment_request_properties" - }, - { - "$ref": "#/components/schemas/update_user_comment_request_properties" - } - ] - }, - "examples": { - "updateCaseCommentRequest": { - "$ref": "#/components/examples/update_comment_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "updateCaseCommentResponse": { - "$ref": "#/components/examples/update_comment_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "get": { - "summary": "Retrieves all the comments from a case in the default space.", - "operationId": "getAllCaseCommentsDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "deprecated": true, - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - } - } - }, - "examples": {} - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}/comments/{commentId}": { - "delete": { - "summary": "Deletes a comment or alert from a case in the default space.", - "operationId": "deleteCaseCommentDefaultSpace", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/case_id" - }, - { - "$ref": "#/components/parameters/comment_id" - } - ], - "responses": { - "204": { - "description": "Indicates a successful call." - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "get": { - "summary": "Retrieves a comment from a case in the default space.", - "operationId": "getCaseCommentDefaultSpace", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - }, - { - "$ref": "#/components/parameters/comment_id" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "examples": { - "getCaseCommentResponse": { - "$ref": "#/components/examples/get_comment_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}/connector/{connectorId}/_push": { - "post": { - "summary": "Pushes a case to an external service.", - "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. You must also have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're pushing.\n", - "operationId": "pushCaseDefaultSpace", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - }, - { - "$ref": "#/components/parameters/connector_id" - }, - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "content": { - "application/json": {} - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/alert_comment_response_properties" - }, - { - "$ref": "#/components/schemas/user_comment_response_properties" - } - ] - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "$ref": "#/components/schemas/external_service" - }, - "id": { - "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "$ref": "#/components/schemas/settings" - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] - }, - "title": { - "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 - }, - "totalComment": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - }, - "profile_uid": { - "type": "string", - "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" - } - }, - "nullable": true, - "example": null - }, - "version": { - "type": "string", - "example": "WzUzMiwxXQ==" - } - } - }, - "examples": { - "pushCaseResponse": { - "$ref": "#/components/examples/push_case_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/cases/{caseId}/user_actions": { - "get": { - "summary": "Returns all user activity for a case in the default space.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", - "deprecated": true, - "operationId": "getCaseActivityDefaultSpace", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/case_id" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/user_actions_response_properties" - } - }, - "examples": { - "getCaseActivityResponse": { - "$ref": "#/components/examples/get_case_activity_response" - } - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, "/s/{spaceId}/api/cases": { "post": { "summary": "Creates a case.", "operationId": "createCase", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -4498,8 +459,7 @@ "operationId": "deleteCase", "description": "You must have `read` or `all` privileges and the `delete` sub-feature privilege for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -4535,8 +495,7 @@ "operationId": "updateCase", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -4979,8 +938,7 @@ "operationId": "getCases", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -5462,8 +1420,7 @@ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "x-technical-preview": true, "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -5525,8 +1482,7 @@ "operationId": "getCaseConfiguration", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -5737,8 +1693,7 @@ "operationId": "setCaseConfiguration", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6088,8 +2043,7 @@ "operationId": "updateCaseConfiguration", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6428,8 +2382,7 @@ "operationId": "getCaseConnectors", "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6509,8 +2462,7 @@ "operationId": "getCaseReporters", "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6577,8 +2529,7 @@ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "deprecated": true, "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6634,8 +2585,7 @@ "operationId": "getCaseTags", "description": "You must have read privileges for the **Cases*** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6698,8 +2648,7 @@ "operationId": "getCase", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -6997,8 +2946,7 @@ "x-technical-preview": true, "operationId": "getCaseAlerts", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7046,8 +2994,7 @@ "operationId": "addCaseComment", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7351,8 +3298,7 @@ "operationId": "deleteCaseComments", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7381,8 +3327,7 @@ "operationId": "updateCaseComment", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7687,8 +3632,7 @@ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", "deprecated": true, "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7739,8 +3683,7 @@ "operationId": "deleteCaseComment", "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7772,8 +3715,7 @@ "operationId": "getCaseComment", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security*** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -7828,8 +3770,7 @@ "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. You must also have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're pushing.\n", "operationId": "pushCase", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -8128,8 +4069,7 @@ "deprecated": true, "operationId": "getCaseActivity", "tags": [ - "cases", - "kibana" + "cases" ], "parameters": [ { @@ -8193,6 +4133,16 @@ "name": "kbn-xsrf", "required": true }, + "space_id": { + "in": "path", + "name": "spaceId", + "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", + "required": true, + "schema": { + "type": "string", + "example": "default" + } + }, "owner": { "in": "query", "name": "owner", @@ -8275,16 +4225,6 @@ "type": "string", "example": "abed3a70-71bd-11ea-a0b2-c51ea50a58e2" } - }, - "space_id": { - "in": "path", - "name": "spaceId", - "description": "An identifier for the space.", - "required": true, - "schema": { - "type": "string", - "example": "default" - } } }, "schemas": { diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index a328d0282a515..7f02703b161a6 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -17,3350 +17,6 @@ servers: - url: http://localhost:5601 description: local paths: - /api/cases: - post: - summary: Creates a case in the default space. - operationId: createCaseDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're creating. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - content: - application/json: - schema: - type: object - properties: - connector: - description: An object that contains the connector configuration. - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM and - ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type is - sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM Resilient - connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for ServiceNow - SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow ITSM - connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - tags: - description: >- - The words and phrases that help categorize cases. It can be - an empty array. - type: array - items: - type: string - title: - description: A title for the case. - type: string - required: - - connector - - description - - owner - - settings - - tags - - title - examples: - createCaseRequest: - $ref: '#/components/examples/create_case_request' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - createCaseResponse: - $ref: '#/components/examples/create_case_response' - servers: - - url: https://localhost:5601 - delete: - summary: Deletes one or more cases from the default space. - operationId: deleteCaseDefaultSpace - description: > - You must have `read` or `all` privileges and the `delete` sub-feature - privilege for the **Cases** feature in the **Management**, - **Observability**, or **Security** section of the Kibana feature - privileges, depending on the owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - name: ids - description: >- - The cases that you want to removed. To retrieve case IDs, use the - find cases API. All non-ASCII characters must be URL encoded. - in: query - required: true - schema: - type: string - example: d4e7abb0-b462-11ec-9a8d-698504725a43 - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - patch: - summary: Updates one or more cases in the default space. - operationId: updateCaseDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're updating. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - content: - application/json: - schema: - type: object - properties: - cases: - type: array - items: - type: object - properties: - connector: - description: An object that contains the connector configuration. - type: object - properties: - fields: - description: >- - An object containing the connector fields. To - create a case without a connector, specify null. - If you want to omit any individual field, specify - null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow - ITSM and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue - type is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow - ITSM connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution - can be delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case - without a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - id: - description: The identifier for the case. - type: string - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - description: The words and phrases that help categorize cases. - type: array - items: - type: string - title: - description: A title for the case. - type: string - version: - description: The current version of the case. - type: string - required: - - id - - version - examples: - updateCaseRequest: - $ref: '#/components/examples/update_case_request' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - updateCaseResponse: - $ref: '#/components/examples/update_case_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/_find: - get: - summary: Retrieves a paginated subset of cases from the default space. - operationId: getCasesDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - parameters: - - name: defaultSearchOperator - in: query - description: The default operator to use for the simple_query_string. - schema: - type: string - default: OR - example: OR - - name: fields - in: query - description: The fields in the entity to return in the response. - schema: - type: array - items: - type: string - - name: from - in: query - description: > - [preview] Returns only cases that were created after a specific - date. The date must be specified as a KQL data range or date match - expression. This functionality is in technical preview and may be - changed or removed in a future release. Elastic will apply best - effort to fix any issues, but features in technical preview are not - subject to the support SLA of official GA features. - schema: - type: string - example: now-1d - x-technical-preview: true - - $ref: '#/components/parameters/owner' - - name: page - in: query - description: The page number to return. - schema: - type: integer - default: 1 - example: 1 - - name: perPage - in: query - description: The number of rules to return per page. - schema: - type: integer - default: 20 - example: 20 - - name: reporters - in: query - description: Filters the returned cases by the user name of the reporter. - schema: - oneOf: - - type: string - - type: array - items: - type: string - example: elastic - - name: search - in: query - description: >- - An Elasticsearch simple_query_string query that filters the objects - in the response. - schema: - type: string - - name: searchFields - in: query - description: The fields to perform the simple_query_string parsed query against. - schema: - oneOf: - - type: string - - type: array - items: - type: string - - $ref: '#/components/parameters/severity' - - name: sortField - in: query - description: Determines which field is used to sort the results. - schema: - type: string - enum: - - createdAt - - updatedAt - default: createdAt - example: updatedAt - - name: sortOrder - in: query - description: Determines the sort order. - schema: - type: string - enum: - - asc - - desc - default: desc - example: asc - - in: query - name: status - description: Filters the returned cases by state. - schema: - type: string - enum: - - closed - - in-progress - - open - example: open - - name: tags - in: query - description: Filters the returned cases by tags. - schema: - oneOf: - - type: string - - type: array - items: - type: string - example: tag-1 - - name: to - in: query - description: >- - Returns only cases that were created before a specific date. The - date must be specified as a KQL data range or date match expression. - schema: - type: string - example: now%2B1d - x-technical-preview: true - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - cases: - type: array - items: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To - create a case without a connector, specify null. - If you want to omit any individual field, - specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow - ITSM and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs - for ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue - type is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow - ITSM connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for - ServiceNow ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution - can be delayed for ServiceNow ITSM - connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a - case without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case - without a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to - its closure (in seconds). If the case has not been - closed, the duration is set to null. If the case was - closed after less than half a second, the duration - is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - count_closed_cases: - type: integer - count_in_progress_cases: - type: integer - count_open_cases: - type: integer - page: - type: integer - per_page: - type: integer - total: - type: integer - examples: - findCaseResponse: - $ref: '#/components/examples/find_case_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/alerts/{alertId}: - get: - summary: Returns the cases associated with a specific alert in the default space. - operationId: getCasesByAlertDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - x-technical-preview: true - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/alert_id' - - $ref: '#/components/parameters/owner' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - id: - type: string - description: The case identifier. - title: - type: string - description: The case title. - example: - - id: 06116b80-e1c3-11ec-be9b-9b1838238ee6 - title: security_case - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/configure: - get: - summary: >- - Retrieves external connection details, such as the closure type and - default connector for cases in the default space. - operationId: getCaseConfigurationDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/owner' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - closure_type: - $ref: '#/components/schemas/closure_types' - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create - a case without a connector, specify null. If you - want to omit any individual field, specify null as - its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can - be delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without - a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-06-01T17:07:17.767Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - error: - type: string - example: null - id: - type: string - example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6 - mappings: - type: array - items: - type: object - properties: - action_type: - type: string - example: overwrite - source: - type: string - example: title - target: - type: string - example: summary - owner: - $ref: '#/components/schemas/owners' - updated_at: - type: string - format: date-time - nullable: true - example: '2022-06-01T19:58:48.169Z' - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - nullable: true - version: - type: string - example: WzIwNzMsMV0= - servers: - - url: https://localhost:5601 - post: - summary: >- - Sets external connection details, such as the closure type and default - connector for cases in the default space. - operationId: setCaseConfigurationDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - Connectors are used to interface with external systems. You must create - a connector before you can use it in your cases. Refer to the add - connectors API. If you set a default connector, it is automatically - selected when you create cases in Kibana. If you use the create case - API, however, you must still specify all of the connector details. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - content: - application/json: - schema: - type: object - properties: - closure_type: - $ref: '#/components/schemas/closure_types' - connector: - description: An object that contains the connector configuration. - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM and - ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type is - sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM Resilient - connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for ServiceNow - SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow ITSM - connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - owner: - $ref: '#/components/schemas/owners' - settings: - description: An object that contains the case settings. - type: object - properties: - syncAlerts: - description: Turns alert syncing on or off. - type: boolean - example: true - required: - - syncAlerts - required: - - closure_type - - connector - - owner - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - closure_type: - $ref: '#/components/schemas/closure_types' - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create - a case without a connector, specify null. If you - want to omit any individual field, specify null as - its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can - be delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without - a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-06-01T17:07:17.767Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - error: - type: string - example: null - id: - type: string - example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6 - mappings: - type: array - items: - type: object - properties: - action_type: - type: string - example: overwrite - source: - type: string - example: title - target: - type: string - example: summary - owner: - $ref: '#/components/schemas/owners' - updated_at: - type: string - format: date-time - nullable: true - example: '2022-06-01T19:58:48.169Z' - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - nullable: true - version: - type: string - example: WzIwNzMsMV0= - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/configure/{configurationId}: - patch: - summary: >- - Updates external connection details, such as the closure type and - default connector for cases in the default space. - operationId: updateCaseConfigurationDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - Connectors are used to interface with external systems. You must create - a connector before you can use it in your cases. Refer to the add - connectors API. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/configuration_id' - requestBody: - content: - application/json: - schema: - type: object - properties: - closure_type: - $ref: '#/components/schemas/closure_types' - connector: - description: An object that contains the connector configuration. - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM and - ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type is - sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM Resilient - connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for ServiceNow - SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow ITSM - connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - version: - description: > - The version of the connector. To retrieve the version value, - use the get configuration API. - type: string - example: WzIwMiwxXQ== - required: - - version - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - closure_type: - $ref: '#/components/schemas/closure_types' - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create - a case without a connector, specify null. If you - want to omit any individual field, specify null as - its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can - be delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without - a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-06-01T17:07:17.767Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - error: - type: string - example: null - id: - type: string - example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6 - mappings: - type: array - items: - type: object - properties: - action_type: - type: string - example: overwrite - source: - type: string - example: title - target: - type: string - example: summary - owner: - $ref: '#/components/schemas/owners' - updated_at: - type: string - format: date-time - nullable: true - example: '2022-06-01T19:58:48.169Z' - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - nullable: true - version: - type: string - example: WzIwNzMsMV0= - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/configure/connectors/_find: - get: - summary: Retrieves information about connectors for cases in the default space. - operationId: getCaseConnectorsDefaultSpace - description: > - In particular, only the connectors that are supported for use in cases - are returned. You must have `read` privileges for the **Actions and - Connectors** feature in the **Management** section of the Kibana feature - privileges. - tags: - - cases - - kibana - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - actionTypeId: - $ref: '#/components/schemas/connector_types' - config: - type: object - properties: - apiUrl: - type: string - projectKey: - type: string - additionalProperties: true - id: - type: string - isDeprecated: - type: boolean - isMissingSecrets: - type: boolean - isPreconfigured: - type: boolean - name: - type: string - referencedByCount: - type: integer - examples: - findCaseResponse: - $ref: '#/components/examples/find_connector_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/reporters: - get: - summary: >- - Returns information about the users who opened cases in the default - space. - operationId: getCaseReportersDefaultSpace - description: > - You must have read privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases. The API returns - information about the users as they existed at the time of the case - creation, including their name, full name, and email address. If any of - those details change thereafter or if a user is deleted, the information - returned by this API is unchanged. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/owner' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - examples: - getReportersResponse: - $ref: '#/components/examples/get_reporters_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/status: - get: - summary: Returns the number of cases that are open, closed, and in progress. - operationId: getCaseStatusDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - deprecated: true - parameters: - - $ref: '#/components/parameters/owner' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - count_closed_cases: - type: integer - count_in_progress_cases: - type: integer - count_open_cases: - type: integer - examples: - getStatusResponse: - $ref: '#/components/examples/get_status_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/tags: - get: - summary: Aggregates and returns a list of case tags in the default space. - operationId: getCaseTagsDefaultSpace - description: > - You must have read privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - parameters: - - in: query - name: owner - description: >- - A filter to limit the retrieved case statistics to a specific set of - applications. If this parameter is omitted, the response contains - tags from all cases that the user has access to read. - schema: - oneOf: - - $ref: '#/components/schemas/owners' - - type: array - items: - $ref: '#/components/schemas/owners' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: string - examples: - getTagsResponse: - $ref: '#/components/examples/get_tags_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}: - get: - summary: Retrieves information about a case in the default space. - operationId: getCaseDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're seeking. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/case_id' - - in: query - name: includeComments - description: Determines whether case comments are returned. - deprecated: true - schema: - type: boolean - default: true - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - getCaseResponse: - $ref: '#/components/examples/get_case_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}/alerts: - get: - summary: Gets all alerts attached to a case in the default space. - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - operationId: getCaseAlertsDefaultSpace - tags: - - cases - - kibana - x-technical-preview: true - parameters: - - $ref: '#/components/parameters/case_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - $ref: '#/components/schemas/alert_response_properties' - examples: - createCaseCommentResponse: - $ref: '#/components/examples/get_case_alerts_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}/comments: - post: - summary: Adds a comment or alert to a case in the default space. - operationId: addCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're creating. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/case_id' - requestBody: - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/add_alert_comment_request_properties' - - $ref: '#/components/schemas/add_user_comment_request_properties' - examples: - createCaseCommentRequest: - $ref: '#/components/examples/add_comment_request' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - createCaseCommentResponse: - $ref: '#/components/examples/add_comment_response' - servers: - - url: https://localhost:5601 - delete: - summary: Deletes all comments and alerts from a case in the default space. - operationId: deleteCaseCommentsDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/case_id' - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - patch: - summary: Updates a comment or alert in a case in the default space. - operationId: updateCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're updating. - NOTE: You cannot change the comment type or the owner of a comment. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/case_id' - requestBody: - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/update_alert_comment_request_properties' - - $ref: '#/components/schemas/update_user_comment_request_properties' - examples: - updateCaseCommentRequest: - $ref: '#/components/examples/update_comment_request' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - updateCaseCommentResponse: - $ref: '#/components/examples/update_comment_response' - servers: - - url: https://localhost:5601 - get: - summary: Retrieves all the comments from a case in the default space. - operationId: getAllCaseCommentsDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases with the - comments you're seeking. - tags: - - cases - - kibana - deprecated: true - parameters: - - $ref: '#/components/parameters/case_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - anyOf: - - $ref: '#/components/schemas/alert_comment_response_properties' - - $ref: '#/components/schemas/user_comment_response_properties' - examples: {} - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}/comments/{commentId}: - delete: - summary: Deletes a comment or alert from a case in the default space. - operationId: deleteCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/case_id' - - $ref: '#/components/parameters/comment_id' - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - get: - summary: Retrieves a comment from a case in the default space. - operationId: getCaseCommentDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases with the - comments you're seeking. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/case_id' - - $ref: '#/components/parameters/comment_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - oneOf: - - $ref: '#/components/schemas/alert_comment_response_properties' - - $ref: '#/components/schemas/user_comment_response_properties' - examples: - getCaseCommentResponse: - $ref: '#/components/examples/get_comment_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}/connector/{connectorId}/_push: - post: - summary: Pushes a case to an external service. - description: > - You must have `all` privileges for the **Actions and Connectors** - feature in the **Management** section of the Kibana feature privileges. - You must also have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're pushing. - operationId: pushCaseDefaultSpace - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/case_id' - - $ref: '#/components/parameters/connector_id' - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - content: - application/json: {} - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - comments: - type: array - items: - oneOf: - - $ref: >- - #/components/schemas/alert_comment_response_properties - - $ref: >- - #/components/schemas/user_comment_response_properties - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM - and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type - is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - created_at: - type: string - format: date-time - example: '2022-05-13T09:16:17.416Z' - created_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - description: - type: string - example: A case description. - duration: - type: integer - description: > - The elapsed time from the creation of the case to its - closure (in seconds). If the case has not been closed, the - duration is set to null. If the case was closed after less - than half a second, the duration is rounded down to zero. - example: 120 - external_service: - $ref: '#/components/schemas/external_service' - id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 - owner: - $ref: '#/components/schemas/owners' - settings: - $ref: '#/components/schemas/settings' - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - type: array - items: - type: string - example: - - tag-1 - title: - type: string - example: Case title 1 - totalAlerts: - type: integer - example: 0 - totalComment: - type: integer - example: 0 - updated_at: - type: string - format: date-time - nullable: true - example: null - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - profile_uid: - type: string - example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 - nullable: true - example: null - version: - type: string - example: WzUzMiwxXQ== - examples: - pushCaseResponse: - $ref: '#/components/examples/push_case_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/cases/{caseId}/user_actions: - get: - summary: Returns all user activity for a case in the default space. - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're seeking. - deprecated: true - operationId: getCaseActivityDefaultSpace - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/case_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - $ref: '#/components/schemas/user_actions_response_properties' - examples: - getCaseActivityResponse: - $ref: '#/components/examples/get_case_activity_response' - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 /s/{spaceId}/api/cases: post: summary: Creates a case. @@ -3372,7 +28,6 @@ paths: creating. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' @@ -3748,7 +403,6 @@ paths: privileges, depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' @@ -3776,7 +430,6 @@ paths: updating. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' @@ -4162,7 +815,6 @@ paths: feature privileges, depending on the owner of the cases you're seeking. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' - name: defaultSearchOperator @@ -4544,7 +1196,6 @@ paths: x-technical-preview: true tags: - cases - - kibana parameters: - $ref: '#/components/parameters/alert_id' - $ref: '#/components/parameters/space_id' @@ -4584,7 +1235,6 @@ paths: feature privileges, depending on the owner of the case configuration. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' - $ref: '#/components/parameters/owner' @@ -4777,7 +1427,6 @@ paths: API, however, you must still specify all of the connector details. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' @@ -5098,7 +1747,6 @@ paths: connectors API. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/configuration_id' @@ -5408,7 +2056,6 @@ paths: privileges. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' responses: @@ -5464,7 +2111,6 @@ paths: returned by this API is unchanged. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' - $ref: '#/components/parameters/owner' @@ -5508,7 +2154,6 @@ paths: deprecated: true tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' - $ref: '#/components/parameters/owner' @@ -5543,7 +2188,6 @@ paths: feature privileges, depending on the owner of the cases you're seeking. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/space_id' - in: query @@ -5584,7 +2228,6 @@ paths: feature privileges, depending on the owner of the case you're seeking. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/space_id' @@ -5832,7 +2475,6 @@ paths: operationId: getCaseAlerts tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/space_id' @@ -5862,7 +2504,6 @@ paths: feature privileges, depending on the owner of the case you're creating. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/case_id' @@ -6110,7 +2751,6 @@ paths: feature privileges, depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/case_id' @@ -6130,7 +2770,6 @@ paths: NOTE: You cannot change the comment type or the owner of a comment. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/case_id' @@ -6380,7 +3019,6 @@ paths: deprecated: true tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/space_id' @@ -6410,7 +3048,6 @@ paths: feature privileges, depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/case_id' @@ -6431,7 +3068,6 @@ paths: comments you're seeking. tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/comment_id' @@ -6464,7 +3100,6 @@ paths: operationId: pushCase tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/connector_id' @@ -6710,7 +3345,6 @@ paths: operationId: getCaseActivity tags: - cases - - kibana parameters: - $ref: '#/components/parameters/case_id' - $ref: '#/components/parameters/space_id' @@ -6746,6 +3380,16 @@ components: in: header name: kbn-xsrf required: true + space_id: + in: path + name: spaceId + description: >- + An identifier for the space. If `/s/` and the identifier are omitted + from the path, the default space is used. + required: true + schema: + type: string + example: default owner: in: query name: owner @@ -6817,14 +3461,6 @@ components: schema: type: string example: abed3a70-71bd-11ea-a0b2-c51ea50a58e2 - space_id: - in: path - name: spaceId - description: An identifier for the space. - required: true - schema: - type: string - example: default schemas: connector_types: type: string diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/space_id.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/space_id.yaml index 0ff325b08a082..0a9fba457e3e7 100644 --- a/x-pack/plugins/cases/docs/openapi/components/parameters/space_id.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/space_id.yaml @@ -1,6 +1,6 @@ in: path name: spaceId -description: An identifier for the space. +description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. required: true schema: type: string diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml index 3866c4fe4edcb..6995d1482e0a9 100644 --- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml @@ -17,37 +17,6 @@ servers: - url: 'http://localhost:5601' description: local paths: - /api/cases: - $ref: paths/api@cases.yaml - /api/cases/_find: - $ref: paths/api@cases@_find.yaml - '/api/cases/alerts/{alertId}': - $ref: 'paths/api@cases@alerts@{alertid}.yaml' - '/api/cases/configure': - $ref: paths/api@cases@configure.yaml - '/api/cases/configure/{configurationId}': - $ref: paths/api@cases@configure@{configurationid}.yaml - '/api/cases/configure/connectors/_find': - $ref: paths/api@cases@configure@connectors@_find.yaml - '/api/cases/reporters': - $ref: 'paths/api@cases@reporters.yaml' - '/api/cases/status': - $ref: 'paths/api@cases@status.yaml' - '/api/cases/tags': - $ref: 'paths/api@cases@tags.yaml' - '/api/cases/{caseId}': - $ref: 'paths/api@cases@{caseid}.yaml' - '/api/cases/{caseId}/alerts': - $ref: 'paths/api@cases@{caseid}@alerts.yaml' - '/api/cases/{caseId}/comments': - $ref: 'paths/api@cases@{caseid}@comments.yaml' - '/api/cases/{caseId}/comments/{commentId}': - $ref: 'paths/api@cases@{caseid}@comments@{commentid}.yaml' - '/api/cases/{caseId}/connector/{connectorId}/_push': - $ref: 'paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml' - '/api/cases/{caseId}/user_actions': - $ref: 'paths/api@cases@{caseid}@user_actions.yaml' - '/s/{spaceId}/api/cases': $ref: 'paths/s@{spaceid}@api@cases.yaml' '/s/{spaceId}/api/cases/_find': diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml deleted file mode 100644 index 96a9c473557c5..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml +++ /dev/null @@ -1,175 +0,0 @@ -post: - summary: Creates a case in the default space. - operationId: createCaseDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're creating. - tags: - - cases - - kibana - parameters: - - $ref: ../components/headers/kbn_xsrf.yaml - requestBody: - content: - application/json: - schema: - type: object - properties: - connector: - description: An object that contains the connector configuration. - type: object - properties: - $ref: '../components/schemas/connector_properties.yaml' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - owner: - $ref: '../components/schemas/owners.yaml' - settings: - $ref: '../components/schemas/settings.yaml' - severity: - $ref: '../components/schemas/severity_property.yaml' - tags: - description: The words and phrases that help categorize cases. It can be an empty array. - type: array - items: - type: string - title: - description: A title for the case. - type: string - required: - - connector - - description - - owner - - settings - - tags - - title - examples: - createCaseRequest: - $ref: '../components/examples/create_case_request.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - createCaseResponse: - $ref: '../components/examples/create_case_response.yaml' - servers: - - url: https://localhost:5601 - -delete: - summary: Deletes one or more cases from the default space. - operationId: deleteCaseDefaultSpace - description: > - You must have `read` or `all` privileges and the `delete` sub-feature - privilege for the **Cases** feature in the **Management**, **Observability**, - or **Security** section of the Kibana feature privileges, depending on the - owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: ../components/headers/kbn_xsrf.yaml - - name: ids - description: The cases that you want to removed. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. - in: query - required: true - schema: - type: string - example: d4e7abb0-b462-11ec-9a8d-698504725a43 - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - -patch: - summary: Updates one or more cases in the default space. - operationId: updateCaseDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're updating. - tags: - - cases - - kibana - parameters: - - $ref: ../components/headers/kbn_xsrf.yaml - requestBody: - content: - application/json: - schema: - type: object - properties: - cases: - type: array - items: - type: object - properties: - connector: - description: An object that contains the connector configuration. - type: object - properties: - $ref: '../components/schemas/connector_properties.yaml' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - id: - description: The identifier for the case. - type: string - settings: - $ref: '../components/schemas/settings.yaml' - severity: - $ref: '../components/schemas/severity_property.yaml' - status: - $ref: '../components/schemas/status.yaml' - tags: - description: The words and phrases that help categorize cases. - type: array - items: - type: string - title: - description: A title for the case. - type: string - version: - description: The current version of the case. - type: string - required: - - id - - version - examples: - updateCaseRequest: - $ref: '../components/examples/update_case_request.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - updateCaseResponse: - $ref: '../components/examples/update_case_response.yaml' - servers: - - url: https://localhost:5601 - -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml deleted file mode 100644 index 266e00101aad5..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml +++ /dev/null @@ -1,158 +0,0 @@ -get: - summary: Retrieves a paginated subset of cases from the default space. - operationId: getCasesDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - parameters: - - name: defaultSearchOperator - in: query - description: The default operator to use for the simple_query_string. - schema: - type: string - default: OR - example: OR - - name: fields - in: query - description: The fields in the entity to return in the response. - schema: - type: array - items: - type: string - - name: from - in: query - description: > - [preview] Returns only cases that were created after a specific date. - The date must be specified as a KQL data range or date match expression. - This functionality is in technical preview and may be changed or removed - in a future release. Elastic will apply best effort to fix any issues, - but features in technical preview are not subject to the support SLA of - official GA features. - schema: - type: string - example: now-1d - x-technical-preview: true - - $ref: '../components/parameters/owner.yaml' - - name: page - in: query - description: The page number to return. - schema: - type: integer - default: 1 - example: 1 - - name: perPage - in: query - description: The number of rules to return per page. - schema: - type: integer - default: 20 - example: 20 - - name: reporters - in: query - description: Filters the returned cases by the user name of the reporter. - schema: - oneOf: - - type: string - - type: array - items: - type: string - example: elastic - - name: search - in: query - description: An Elasticsearch simple_query_string query that filters the objects in the response. - schema: - type: string - - name: searchFields - in: query - description: The fields to perform the simple_query_string parsed query against. - schema: - oneOf: - - type: string - - type: array - items: - type: string - - $ref: '../components/parameters/severity.yaml' - - name: sortField - in: query - description: Determines which field is used to sort the results. - schema: - type: string - enum: - - createdAt - - updatedAt - default: createdAt - example: updatedAt - - name: sortOrder - in: query - description: Determines the sort order. - schema: - type: string - enum: - - asc - - desc - default: desc - example: asc - - in: query - name: status - description: Filters the returned cases by state. - schema: - type: string - enum: - - closed - - in-progress - - open - example: open - - name: tags - in: query - description: Filters the returned cases by tags. - schema: - oneOf: - - type: string - - type: array - items: - type: string - example: tag-1 - - name: to - in: query - description: Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. - schema: - type: string - example: now%2B1d - x-technical-preview: true - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - cases: - type: array - items: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - count_closed_cases: - type: integer - count_in_progress_cases: - type: integer - count_open_cases: - type: integer - page: - type: integer - per_page: - type: integer - total: - type: integer - examples: - findCaseResponse: - $ref: '../components/examples/find_case_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml deleted file mode 100644 index e020ae577cd96..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml +++ /dev/null @@ -1,37 +0,0 @@ -get: - summary: Returns the cases associated with a specific alert in the default space. - operationId: getCasesByAlertDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - x-technical-preview: true - tags: - - cases - - kibana - parameters: - - $ref: ../components/parameters/alert_id.yaml - - $ref: '../components/parameters/owner.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - id: - type: string - description: The case identifier. - title: - type: string - description: The case title. - example: - - id: 06116b80-e1c3-11ec-be9b-9b1838238ee6 - title: security_case - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml deleted file mode 100644 index 527f67b3cbf22..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml +++ /dev/null @@ -1,93 +0,0 @@ -get: - summary: Retrieves external connection details, such as the closure type and default connector for cases in the default space. - operationId: getCaseConfigurationDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - tags: - - cases - - kibana - parameters: - - $ref: '../components/parameters/owner.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - $ref: '../components/schemas/case_configure_response_properties.yaml' - servers: - - url: https://localhost:5601 - -post: - summary: Sets external connection details, such as the closure type and default connector for cases in the default space. - operationId: setCaseConfigurationDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - Connectors are used to interface with external systems. You must create a - connector before you can use it in your cases. Refer to the add connectors - API. If you set a default connector, it is automatically selected when you - create cases in Kibana. If you use the create case API, however, you must - still specify all of the connector details. - tags: - - cases - - kibana - parameters: - - $ref: ../components/headers/kbn_xsrf.yaml - requestBody: - content: - application/json: - schema: - type: object - properties: - closure_type: - $ref: '../components/schemas/closure_types.yaml' - connector: - description: An object that contains the connector configuration. - type: object - properties: - $ref: '../components/schemas/connector_properties.yaml' - required: - - fields - - id - - name - - type - owner: - $ref: '../components/schemas/owners.yaml' - settings: - description: An object that contains the case settings. - type: object - properties: - syncAlerts: - description: Turns alert syncing on or off. - type: boolean - example: true - required: - - syncAlerts - required: - - closure_type - - connector - - owner - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - $ref: '../components/schemas/case_configure_response_properties.yaml' - servers: - - url: https://localhost:5601 - -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml deleted file mode 100644 index 8e6bddd6c681d..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml +++ /dev/null @@ -1,28 +0,0 @@ -get: - summary: Retrieves information about connectors for cases in the default space. - operationId: getCaseConnectorsDefaultSpace - description: > - In particular, only the connectors that are supported for use in cases are - returned. You must have `read` privileges for the **Actions and Connectors** - feature in the **Management** section of the Kibana feature privileges. - tags: - - cases - - kibana - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - $ref: '../components/schemas/connector_response_properties.yaml' - examples: - findCaseResponse: - $ref: '../components/examples/find_connector_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml deleted file mode 100644 index 204541dced9c1..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml +++ /dev/null @@ -1,57 +0,0 @@ -patch: - summary: Updates external connection details, such as the closure type and default connector for cases in the default space. - operationId: updateCaseConfigurationDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case configuration. - Connectors are used to interface with external systems. You must create a - connector before you can use it in your cases. Refer to the add connectors - API. - tags: - - cases - - kibana - parameters: - - $ref: ../components/headers/kbn_xsrf.yaml - - $ref: ../components/parameters/configuration_id.yaml - requestBody: - content: - application/json: - schema: - type: object - properties: - closure_type: - $ref: '../components/schemas/closure_types.yaml' - connector: - description: An object that contains the connector configuration. - type: object - properties: - $ref: '../components/schemas/connector_properties.yaml' - required: - - fields - - id - - name - - type - version: - description: > - The version of the connector. To retrieve the version value, use - the get configuration API. - type: string - example: WzIwMiwxXQ== - required: - - version - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - $ref: '../components/schemas/case_configure_response_properties.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml deleted file mode 100644 index 2578f59c0ec6d..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml +++ /dev/null @@ -1,34 +0,0 @@ -get: - summary: Returns information about the users who opened cases in the default space. - operationId: getCaseReportersDefaultSpace - description: > - You must have read privileges for the **Cases** feature in the **Management**, - **Observability**, or **Security** section of the Kibana feature privileges, - depending on the owner of the cases. - The API returns information about the users as they existed at the time of - the case creation, including their name, full name, and email address. If - any of those details change thereafter or if a user is deleted, the - information returned by this API is unchanged. - tags: - - cases - - kibana - parameters: - - $ref: '../components/parameters/owner.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - $ref: '../components/schemas/user_properties.yaml' - examples: - getReportersResponse: - $ref: '../components/examples/get_reporters_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml deleted file mode 100644 index 580248e0d99c1..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml +++ /dev/null @@ -1,34 +0,0 @@ -get: - summary: Returns the number of cases that are open, closed, and in progress. - operationId: getCaseStatusDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - deprecated: true - parameters: - - $ref: '../components/parameters/owner.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - count_closed_cases: - type: integer - count_in_progress_cases: - type: integer - count_open_cases: - type: integer - examples: - getStatusResponse: - $ref: '../components/examples/get_status_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml deleted file mode 100644 index f74dabea5bd0c..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml +++ /dev/null @@ -1,36 +0,0 @@ -get: - summary: Aggregates and returns a list of case tags in the default space. - operationId: getCaseTagsDefaultSpace - description: > - You must have read privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - tags: - - cases - - kibana - parameters: - - in: query - name: owner - description: A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. - schema: - oneOf: - - $ref: '../components/schemas/owners.yaml' - - type: array - items: - $ref: '../components/schemas/owners.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: string - examples: - getTagsResponse: - $ref: '../components/examples/get_tags_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml deleted file mode 100644 index 7290e5f5fdfba..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml +++ /dev/null @@ -1,35 +0,0 @@ -get: - summary: Retrieves information about a case in the default space. - operationId: getCaseDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're seeking. - tags: - - cases - - kibana - parameters: - - $ref: ../components/parameters/case_id.yaml - - in: query - name: includeComments - description: Determines whether case comments are returned. - deprecated: true - schema: - type: boolean - default: true - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - getCaseResponse: - $ref: '../components/examples/get_case_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml deleted file mode 100644 index e6b3ffbd8faad..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml +++ /dev/null @@ -1,29 +0,0 @@ -get: - summary: Gets all alerts attached to a case in the default space. - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. - operationId: getCaseAlertsDefaultSpace - tags: - - cases - - kibana - x-technical-preview: true - parameters: - - $ref: ../components/parameters/case_id.yaml - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - $ref: '../components/schemas/alert_response_properties.yaml' - examples: - createCaseCommentResponse: - $ref: '../components/examples/get_case_alerts_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml deleted file mode 100644 index 95e49981d729d..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml +++ /dev/null @@ -1,126 +0,0 @@ -post: - summary: Adds a comment or alert to a case in the default space. - operationId: addCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're creating. - tags: - - cases - - kibana - parameters: - - $ref: '../components/headers/kbn_xsrf.yaml' - - $ref: '../components/parameters/case_id.yaml' - requestBody: - content: - application/json: - schema: - oneOf: - - $ref: '../components/schemas/add_alert_comment_request_properties.yaml' - - $ref: '../components/schemas/add_user_comment_request_properties.yaml' - examples: - createCaseCommentRequest: - $ref: '../components/examples/add_comment_request.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - createCaseCommentResponse: - $ref: '../components/examples/add_comment_response.yaml' - servers: - - url: https://localhost:5601 - -delete: - summary: Deletes all comments and alerts from a case in the default space. - operationId: deleteCaseCommentsDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: '../components/headers/kbn_xsrf.yaml' - - $ref: '../components/parameters/case_id.yaml' - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - -patch: - summary: Updates a comment or alert in a case in the default space. - operationId: updateCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're updating. - NOTE: You cannot change the comment type or the owner of a comment. - tags: - - cases - - kibana - parameters: - - $ref: '../components/headers/kbn_xsrf.yaml' - - $ref: '../components/parameters/case_id.yaml' - requestBody: - content: - application/json: - schema: - oneOf: - - $ref: '../components/schemas/update_alert_comment_request_properties.yaml' - - $ref: '../components/schemas/update_user_comment_request_properties.yaml' - examples: - updateCaseCommentRequest: - $ref: '../components/examples/update_comment_request.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - updateCaseCommentResponse: - $ref: '../components/examples/update_comment_response.yaml' - servers: - - url: https://localhost:5601 - -get: - summary: Retrieves all the comments from a case in the default space. - operationId: getAllCaseCommentsDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the **Management**, - **Observability**, or **Security** section of the Kibana feature privileges, - depending on the owner of the cases with the comments you're seeking. - tags: - - cases - - kibana - deprecated: true - parameters: - - $ref: ../components/parameters/case_id.yaml - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - anyOf: - - $ref: '../components/schemas/alert_comment_response_properties.yaml' - - $ref: '../components/schemas/user_comment_response_properties.yaml' - examples: {} - servers: - - url: https://localhost:5601 - -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml deleted file mode 100644 index f76bd93cd8510..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml +++ /dev/null @@ -1,50 +0,0 @@ -delete: - summary: Deletes a comment or alert from a case in the default space. - operationId: deleteCaseCommentDefaultSpace - description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're deleting. - tags: - - cases - - kibana - parameters: - - $ref: '../components/headers/kbn_xsrf.yaml' - - $ref: '../components/parameters/case_id.yaml' - - $ref: '../components/parameters/comment_id.yaml' - responses: - '204': - description: Indicates a successful call. - servers: - - url: https://localhost:5601 - -get: - summary: Retrieves a comment from a case in the default space. - operationId: getCaseCommentDefaultSpace - description: > - You must have `read` privileges for the **Cases** feature in the **Management**, - **Observability**, or **Security** section of the Kibana feature privileges, - depending on the owner of the cases with the comments you're seeking. - tags: - - cases - - kibana - parameters: - - $ref: '../components/parameters/case_id.yaml' - - $ref: '../components/parameters/comment_id.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - oneOf: - - $ref: '../components/schemas/alert_comment_response_properties.yaml' - - $ref: '../components/schemas/user_comment_response_properties.yaml' - examples: - getCaseCommentResponse: - $ref: '../components/examples/get_comment_response.yaml' - servers: - - url: https://localhost:5601 - -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml deleted file mode 100644 index 7fc3a73db00b5..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml +++ /dev/null @@ -1,35 +0,0 @@ -post: - summary: Pushes a case to an external service. - description: > - You must have `all` privileges for the **Actions and Connectors** feature in - the **Management** section of the Kibana feature privileges. You must also - have `all` privileges for the **Cases** feature in the **Management**, - **Observability**, or **Security** section of the Kibana feature privileges, - depending on the owner of the case you're pushing. - operationId: pushCaseDefaultSpace - tags: - - cases - - kibana - parameters: - - $ref: '../components/parameters/case_id.yaml' - - $ref: '../components/parameters/connector_id.yaml' - - $ref: '../components/headers/kbn_xsrf.yaml' - requestBody: - content: - application/json: {} - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' - examples: - pushCaseResponse: - $ref: '../components/examples/push_case_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml deleted file mode 100644 index 66a3d389aee14..0000000000000 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml +++ /dev/null @@ -1,29 +0,0 @@ -get: - summary: Returns all user activity for a case in the default space. - description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the case you're seeking. - deprecated: true - operationId: getCaseActivityDefaultSpace - tags: - - cases - - kibana - parameters: - - $ref: '../components/parameters/case_id.yaml' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - $ref: '../components/schemas/user_actions_response_properties.yaml' - examples: - getCaseActivityResponse: - $ref: '../components/examples/get_case_activity_response.yaml' - servers: - - url: https://localhost:5601 -servers: - - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml index 5e891285ac8ba..59abb58531821 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml @@ -7,7 +7,6 @@ post: feature privileges, depending on the owner of the case you're creating. tags: - cases - - kibana parameters: - $ref: ../components/headers/kbn_xsrf.yaml - $ref: '../components/parameters/space_id.yaml' @@ -79,7 +78,6 @@ delete: depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: ../components/headers/kbn_xsrf.yaml - $ref: '../components/parameters/space_id.yaml' @@ -105,7 +103,6 @@ patch: feature privileges, depending on the owner of the case you're updating. tags: - cases - - kibana parameters: - $ref: ../components/headers/kbn_xsrf.yaml - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml index 48801a11dea66..a260321248357 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml @@ -7,7 +7,6 @@ get: feature privileges, depending on the owner of the cases you're seeking. tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' - name: defaultSearchOperator diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml index 20c365947e351..24615d772b3ef 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml @@ -8,7 +8,6 @@ get: x-technical-preview: true tags: - cases - - kibana parameters: - $ref: ../components/parameters/alert_id.yaml - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml index 2df6edb39c1ce..97306b5490956 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml @@ -7,7 +7,6 @@ get: feature privileges, depending on the owner of the case configuration. tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' - $ref: '../components/parameters/owner.yaml' @@ -39,7 +38,6 @@ post: still specify all of the connector details. tags: - cases - - kibana parameters: - $ref: ../components/headers/kbn_xsrf.yaml - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml index dfbbc498d836d..0755225bad71a 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml @@ -7,7 +7,6 @@ get: feature in the **Management** section of the Kibana feature privileges. tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' responses: diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml index 18336746e830b..f727cbb0b4274 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml @@ -9,7 +9,6 @@ patch: connector before you can use it in your cases. Refer to the add connectors API. tags: - cases - - kibana parameters: - $ref: ../components/headers/kbn_xsrf.yaml - $ref: ../components/parameters/configuration_id.yaml diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@reporters.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@reporters.yaml index e35c2ceaf5063..93b7ac3863c99 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@reporters.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@reporters.yaml @@ -11,7 +11,6 @@ get: information returned by this API is unchanged. tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' - $ref: '../components/parameters/owner.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml index 5d8aa302fd76f..dad05ad967728 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml @@ -8,7 +8,6 @@ get: deprecated: true tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' - $ref: '../components/parameters/owner.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml index 58f1e7369d718..6787c075cb19f 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml @@ -7,7 +7,6 @@ get: feature privileges, depending on the owner of the cases you're seeking. tags: - cases - - kibana parameters: - $ref: '../components/parameters/space_id.yaml' - in: query diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml index 30c33a27a37f6..9e8ca4660c44d 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml @@ -7,7 +7,6 @@ get: feature privileges, depending on the owner of the case you're seeking. tags: - cases - - kibana parameters: - $ref: ../components/parameters/case_id.yaml - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml index 6236078853e71..66430fd219d25 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml @@ -8,7 +8,6 @@ get: operationId: getCaseAlerts tags: - cases - - kibana parameters: - $ref: ../components/parameters/case_id.yaml - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml index 9d139bed703e4..c8a045267a492 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml @@ -7,7 +7,6 @@ post: feature privileges, depending on the owner of the case you're creating. tags: - cases - - kibana parameters: - $ref: '../components/headers/kbn_xsrf.yaml' - $ref: '../components/parameters/case_id.yaml' @@ -46,7 +45,6 @@ delete: feature privileges, depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: '../components/headers/kbn_xsrf.yaml' - $ref: '../components/parameters/case_id.yaml' @@ -67,7 +65,6 @@ patch: NOTE: You cannot change the comment type or the owner of a comment. tags: - cases - - kibana parameters: - $ref: '../components/headers/kbn_xsrf.yaml' - $ref: '../components/parameters/case_id.yaml' @@ -107,7 +104,6 @@ get: deprecated: true tags: - cases - - kibana parameters: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/space_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml index 2ed9456aed4db..3aac8f33bc68b 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml @@ -7,7 +7,6 @@ delete: feature privileges, depending on the owner of the cases you're deleting. tags: - cases - - kibana parameters: - $ref: '../components/headers/kbn_xsrf.yaml' - $ref: '../components/parameters/case_id.yaml' @@ -28,7 +27,6 @@ get: depending on the owner of the cases with the comments you're seeking. tags: - cases - - kibana parameters: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/comment_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml index d2291b59ec088..32caad2bc4086 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml @@ -9,7 +9,6 @@ post: operationId: pushCase tags: - cases - - kibana parameters: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/connector_id.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml index 723265cfbe495..71301cf67a731 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml @@ -8,7 +8,6 @@ get: operationId: getCaseActivity tags: - cases - - kibana parameters: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/space_id.yaml' From fbf343a4e92733553bf5c025b6a65e3d3a16a422 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 26 Sep 2022 12:17:59 -0600 Subject: [PATCH 034/172] [Maps] hide/show all layers (#141495) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../maps/public/actions/layer_actions.ts | 26 +++++ .../__snapshots__/layer_control.test.tsx.snap | 102 ++++++++++++++++++ .../layer_control/index.ts | 15 ++- .../layer_control/layer_control.test.tsx | 2 + .../layer_control/layer_control.tsx | 38 +++++++ 5 files changed, 182 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 7a4377f0bfc27..317f6e09053e5 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -284,6 +284,32 @@ export function toggleLayerVisible(layerId: string) { }; } +export function hideAllLayers() { + return ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + getLayerList(getState()).forEach((layer: ILayer, index: number) => { + if (layer.isVisible() && !layer.isBasemap(index)) { + dispatch(setLayerVisibility(layer.getId(), false)); + } + }); + }; +} + +export function showAllLayers() { + return ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + getLayerList(getState()).forEach((layer: ILayer, index: number) => { + if (!layer.isVisible()) { + dispatch(setLayerVisibility(layer.getId(), true)); + } + }); + }; +} + export function showThisLayerOnly(layerId: string) { return ( dispatch: ThunkDispatch, diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap index 7d0c67ff41797..e043a8d723e14 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap @@ -31,6 +31,40 @@ exports[`LayerControl is rendered 1`] = `
+

Access

+
    +
  1. APIKey KeyParamName:ApiKey KeyInQuery:false KeyInHeader:true
  2. +
  3. HTTP Basic Authentication
  4. +
+ +

Methods

+ [ Jump to Models ] + +

Table of Contents

+
+

Ml

+ + +

Ml

+
+
+ Up +
get /s/{spaceId}/api/ml/saved_objects/sync
+
Synchronizes Kibana saved objects for machine learning jobs and trained models. (mlSync)
+
You must have all privileges for the Machine Learning feature in the Analytics section of the Kibana feature privileges. This API runs automatically when you start Kibana and periodically thereafter.
+ +

Path parameters

+
+
spaceId (required)
+ +
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
+ + + + +

Query parameters

+
+
simulate (optional)
+ +
Query Parameter — When true, simulates the synchronization by returning only the list actions that would be performed. default: null
+
+ + +

Return type

+ + + + +

Example data

+
Content-Type: application/json
+
{
+  "datafeedsAdded" : {
+    "key" : {
+      "success" : true
+    }
+  },
+  "savedObjectsCreated" : {
+    "anomaly-detector" : {
+      "key" : {
+        "success" : true
+      }
+    },
+    "data-frame-analytics" : {
+      "key" : {
+        "success" : true
+      }
+    },
+    "trained-model" : {
+      "key" : {
+        "success" : true
+      }
+    }
+  },
+  "savedObjectsDeleted" : {
+    "anomaly-detector" : {
+      "key" : {
+        "success" : true
+      }
+    },
+    "data-frame-analytics" : {
+      "key" : {
+        "success" : true
+      }
+    },
+    "trained-model" : {
+      "key" : {
+        "success" : true
+      }
+    }
+  },
+  "datafeedsRemoved" : {
+    "key" : {
+      "success" : true
+    }
+  }
+}
+ +

Produces

+ This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
    +
  • application/json
  • +
+ +

Responses

+

200

+ Indicates a successful call + mlSyncResponse +
+
+ +

Models

+ [ Jump to Methods ] + +

Table of Contents

+
    +
  1. mlSyncResponse - Sync API response
  2. +
  3. mlSyncResponseAnomalyDetectors - Sync API response for anomaly detection jobs
  4. +
  5. mlSyncResponseDataFrameAnalytics - Sync API response for data frame analytics jobs
  6. +
  7. mlSyncResponseDatafeeds - Sync API response for datafeeds
  8. +
  9. mlSyncResponseSavedObjectsCreated - Sync API response for created saved objects
  10. +
  11. mlSyncResponseSavedObjectsDeleted - Sync API response for deleted saved objects
  12. +
  13. mlSyncResponseTrainedModels - Sync API response for trained models
  14. +
+ +
+

mlSyncResponse - Sync API response Up

+
+
+
datafeedsAdded (optional)
map[String, mlSyncResponseDatafeeds] If a saved object for an anomaly detection job is missing a datafeed identifier, it is added when you run the sync machine learning saved objects API.
+
datafeedsRemoved (optional)
map[String, mlSyncResponseDatafeeds] If a saved object for an anomaly detection job references a datafeed that no longer exists, it is deleted when you run the sync machine learning saved objects API.
+
savedObjectsCreated (optional)
+
savedObjectsDeleted (optional)
+
+
+
+

mlSyncResponseAnomalyDetectors - Sync API response for anomaly detection jobs Up

+
The sync machine learning saved objects API response contains this object when there are anomaly detection jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status.
+
+
success (optional)
Boolean The success or failure of the synchronization.
+
+
+
+

mlSyncResponseDataFrameAnalytics - Sync API response for data frame analytics jobs Up

+
The sync machine learning saved objects API response contains this object when there are data frame analytics jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status.
+
+
success (optional)
Boolean The success or failure of the synchronization.
+
+
+
+

mlSyncResponseDatafeeds - Sync API response for datafeeds Up

+
The sync machine learning saved objects API response contains this object when there are datafeeds affected by the synchronization. There is an object for each relevant datafeed, which contains the synchronization status.
+
+
success (optional)
Boolean The success or failure of the synchronization.
+
+
+
+

mlSyncResponseSavedObjectsCreated - Sync API response for created saved objects Up

+
If saved objects are missing for machine learning jobs or trained models, they are created when you run the sync machine learning saved objects API.
+
+
anomalyMinusdetector (optional)
map[String, mlSyncResponseAnomalyDetectors] If saved objects are missing for anomaly detection jobs, they are created.
+
dataMinusframeMinusanalytics (optional)
map[String, mlSyncResponseDataFrameAnalytics] If saved objects are missing for data frame analytics jobs, they are created.
+
trainedMinusmodel (optional)
map[String, mlSyncResponseTrainedModels] If saved objects are missing for trained models, they are created.
+
+
+
+

mlSyncResponseSavedObjectsDeleted - Sync API response for deleted saved objects Up

+
If saved objects exist for machine learning jobs or trained models that no longer exist, they are deleted when you run the sync machine learning saved objects API.
+
+
anomalyMinusdetector (optional)
map[String, mlSyncResponseAnomalyDetectors] If there are saved objects exist for nonexistent anomaly detection jobs, they are deleted.
+
dataMinusframeMinusanalytics (optional)
map[String, mlSyncResponseDataFrameAnalytics] If there are saved objects exist for nonexistent data frame analytics jobs, they are deleted.
+
trainedMinusmodel (optional)
map[String, mlSyncResponseTrainedModels] If there are saved objects exist for nonexistent trained models, they are deleted.
+
+
+
+

mlSyncResponseTrainedModels - Sync API response for trained models Up

+
The sync machine learning saved objects API response contains this object when there are trained models affected by the synchronization. There is an object for each relevant trained model, which contains the synchronization status.
+
+
success (optional)
Boolean The success or failure of the synchronization.
+
+
+
+ + + + + + + + + + @@ -184,6 +218,40 @@ exports[`LayerControl isReadOnly 1`] = ` + + + + + + + + + + @@ -245,6 +313,40 @@ exports[`LayerControl should disable buttons when flyout is open 1`] = ` + + + + + + + + + + diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts index c525523047c40..d5dc63ecb8e8b 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts @@ -11,7 +11,14 @@ import { connect } from 'react-redux'; import { LayerControl } from './layer_control'; import { FLYOUT_STATE } from '../../../reducers/ui'; -import { setSelectedLayer, updateFlyout, setIsLayerTOCOpen, setDrawMode } from '../../../actions'; +import { + hideAllLayers, + setSelectedLayer, + updateFlyout, + setIsLayerTOCOpen, + setDrawMode, + showAllLayers, +} from '../../../actions'; import { getIsReadOnly, getIsLayerTOCOpen, @@ -43,6 +50,12 @@ function mapDispatchToProps(dispatch: ThunkDispatch { dispatch(setIsLayerTOCOpen(true)); }, + hideAllLayers: () => { + dispatch(hideAllLayers()); + }, + showAllLayers: () => { + dispatch(showAllLayers()); + }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx index 649999ab49a9d..1fc9fa6b9755e 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx @@ -28,6 +28,8 @@ const defaultProps = { showAddLayerWizard: async () => {}, closeLayerTOC: () => {}, openLayerTOC: () => {}, + hideAllLayers: () => {}, + showAllLayers: () => {}, isLayerTOCOpen: true, layerList: [], isFlyoutOpen: false, diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx index d131bf9b98026..297d7ccfd5063 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx @@ -31,6 +31,8 @@ export interface Props { showAddLayerWizard: () => Promise; closeLayerTOC: () => void; openLayerTOC: () => void; + hideAllLayers: () => void; + showAllLayers: () => void; } function renderExpandButton({ @@ -81,6 +83,8 @@ export function LayerControl({ openLayerTOC, layerList, isFlyoutOpen, + hideAllLayers, + showAllLayers, }: Props) { if (!isLayerTOCOpen) { if (isScreenshotMode()) { @@ -152,6 +156,40 @@ export function LayerControl({ + + + + + + + + + + Date: Mon, 26 Sep 2022 19:24:21 +0100 Subject: [PATCH 035/172] [Enterprise Search] [Behavioural analytics] BUG hide UI settings option (#141786) * hides behavioural analytics ui setting * update test for uiSettings call --- .../public/applications/analytics/index.tsx | 3 +-- .../applications/shared/layout/nav.test.tsx | 5 ++++- .../public/applications/shared/layout/nav.tsx | 3 +-- .../enterprise_search/public/plugin.ts | 3 ++- .../enterprise_search/server/ui_settings.ts | 22 +------------------ 5 files changed, 9 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx index eaed153a01b7f..f1228682fe888 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx @@ -29,8 +29,7 @@ export const Analytics: React.FC = (props) => { const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); const { uiSettings } = useValues(KibanaLogic); - const analyticsSectionEnabled = - uiSettings?.get(enableBehavioralAnalyticsSection) ?? false; + const analyticsSectionEnabled = uiSettings?.get(enableBehavioralAnalyticsSection, false); if (!analyticsSectionEnabled) { return ; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 743efc3274a44..67fb93ae643f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -86,7 +86,10 @@ describe('useEnterpriseSearchContentNav', () => { name: 'Search', }, ]); - expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith(enableBehavioralAnalyticsSection); + expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith( + enableBehavioralAnalyticsSection, + false + ); }); it('excludes legacy products when the user has no access to them', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index f5a2d17d7dd94..967095d9674ba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -27,8 +27,7 @@ import { generateNavLink } from './nav_link_helpers'; export const useEnterpriseSearchNav = () => { const { productAccess, uiSettings } = useValues(KibanaLogic); - const analyticsSectionEnabled = - uiSettings?.get(enableBehavioralAnalyticsSection) ?? false; + const analyticsSectionEnabled = uiSettings?.get(enableBehavioralAnalyticsSection, false); const navItems: Array> = [ { diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index ac7581814242e..f193016e73f0d 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -72,7 +72,8 @@ export class EnterpriseSearchPlugin implements Plugin { const { cloud } = plugins; const bahavioralAnalyticsEnabled = core.uiSettings?.get( - enableBehavioralAnalyticsSection + enableBehavioralAnalyticsSection, + false ); core.application.register({ diff --git a/x-pack/plugins/enterprise_search/server/ui_settings.ts b/x-pack/plugins/enterprise_search/server/ui_settings.ts index 3334e625bc08f..99fb5906a3050 100644 --- a/x-pack/plugins/enterprise_search/server/ui_settings.ts +++ b/x-pack/plugins/enterprise_search/server/ui_settings.ts @@ -5,29 +5,9 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from '@kbn/core/types'; -import { i18n } from '@kbn/i18n'; - -import { - enterpriseSearchFeatureId, - enableBehavioralAnalyticsSection, -} from '../common/ui_settings_keys'; /** * uiSettings definitions for Enterprise Search */ -export const uiSettings: Record> = { - [enableBehavioralAnalyticsSection]: { - category: [enterpriseSearchFeatureId], - description: i18n.translate('xpack.enterpriseSearch.uiSettings.analytics.description', { - defaultMessage: 'Enable the new Analytics section in Enterprise Search.', - }), - name: i18n.translate('xpack.enterpriseSearch.uiSettings.analytics.name', { - defaultMessage: 'Enable Behavioral Analytics', - }), - requiresPageReload: true, - schema: schema.boolean(), - value: false, - }, -}; +export const uiSettings: Record> = {}; From fd3a688cec29c656e55e94b29a99f6874845cc54 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:25:45 +0200 Subject: [PATCH 036/172] [Security Solution] Fixes unprocessed exceptions logging (#141523) --- .../new_terms/create_new_terms_alert_type.ts | 9 ++++++--- .../detection_engine/signals/executors/eql.test.ts | 11 ++++++----- .../lib/detection_engine/signals/executors/eql.ts | 8 +++++--- .../signals/executors/threshold.test.ts | 11 ++++++----- .../detection_engine/signals/executors/threshold.ts | 7 +++++-- .../lib/detection_engine/signals/utils.test.ts | 11 +++++------ .../server/lib/detection_engine/signals/utils.ts | 13 ++++++------- 7 files changed, 39 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts index d1fa77857c1f7..2e22d3a5798bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts @@ -32,7 +32,7 @@ import { parseDateString, validateHistoryWindowStart } from './utils'; import { addToSearchAfterReturn, createSearchAfterReturnType, - logUnprocessedExceptionsWarnings, + getUnprocessedExceptionsWarnings, } from '../../signals/utils'; import { createEnrichEventsFunction } from '../../signals/enrichments'; @@ -114,8 +114,6 @@ export const createNewTermsAlertType = ( from: params.from, }); - logUnprocessedExceptionsWarnings(unprocessedExceptions, ruleExecutionLogger); - const esFilter = await getFilter({ filters: params.filters, index: inputIndex, @@ -137,6 +135,11 @@ export const createNewTermsAlertType = ( const result = createSearchAfterReturnType(); + const exceptionsWarning = getUnprocessedExceptionsWarnings(unprocessedExceptions); + if (exceptionsWarning) { + result.warningMessages.push(exceptionsWarning); + } + // There are 2 conditions that mean we're finished: either there were still too many alerts to create // after deduplication and the array of alerts was truncated before being submitted to ES, or there were // exactly enough new alerts to hit maxSignals without truncating the array of alerts. We check both because diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index d19e7cabe6769..93e37c41c8ab3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -45,7 +45,7 @@ describe('eql_executor', () => { describe('eqlExecutor', () => { it('should set a warning when exception list for eql rule contains value list exceptions', async () => { - await eqlExecutor({ + const result = await eqlExecutor({ inputIndex: DEFAULT_INDEX_PATTERN, runtimeMappings: {}, completeRule: eqlCompleteRule, @@ -60,10 +60,11 @@ describe('eql_executor', () => { exceptionFilter: undefined, unprocessedExceptions: [getExceptionListItemSchemaMock()], }); - expect(ruleExecutionLogger.warn).toHaveBeenCalled(); - expect(ruleExecutionLogger.warn.mock.calls[0][0]).toContain( - "The following exceptions won't be applied to rule execution" - ); + expect(result.warningMessages).toEqual([ + `The following exceptions won't be applied to rule execution: ${ + getExceptionListItemSchemaMock().name + }`, + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index 9622e09d37374..436ebec9c27c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -29,7 +29,7 @@ import { addToSearchAfterReturn, createSearchAfterReturnType, makeFloatString, - logUnprocessedExceptionsWarnings, + getUnprocessedExceptionsWarnings, } from '../utils'; import { buildReasonMessageForEqlAlert } from '../reason_formatters'; import type { CompleteRule, EqlRuleParams } from '../../schemas/rule_schemas'; @@ -93,8 +93,10 @@ export const eqlExecutor = async ({ }); ruleExecutionLogger.debug(`EQL query request: ${JSON.stringify(request)}`); - logUnprocessedExceptionsWarnings(unprocessedExceptions, ruleExecutionLogger); - + const exceptionsWarning = getUnprocessedExceptionsWarnings(unprocessedExceptions); + if (exceptionsWarning) { + result.warningMessages.push(exceptionsWarning); + } const eqlSignalSearchStart = performance.now(); const response = await services.scopedClusterClient.asCurrentUser.eql.search( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index dd62af6bfea13..832ffa1e5b5c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -141,7 +141,7 @@ describe('threshold_executor', () => { [`${getThresholdTermsHash(terms2)}`]: signalHistoryRecord2, }, }; - await thresholdExecutor({ + const result = await thresholdExecutor({ completeRule: thresholdCompleteRule, tuple, services: alertServices, @@ -165,10 +165,11 @@ describe('threshold_executor', () => { exceptionFilter: undefined, unprocessedExceptions: [getExceptionListItemSchemaMock()], }); - expect(ruleExecutionLogger.warn).toHaveBeenCalled(); - expect(ruleExecutionLogger.warn.mock.calls[0][0]).toContain( - "The following exceptions won't be applied to rule execution" - ); + expect(result.warningMessages).toEqual([ + `The following exceptions won't be applied to rule execution: ${ + getExceptionListItemSchemaMock().name + }`, + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 398bce577689b..ed82f7cdb1f8a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -34,7 +34,7 @@ import type { import { addToSearchAfterReturn, createSearchAfterReturnType, - logUnprocessedExceptionsWarnings, + getUnprocessedExceptionsWarnings, } from '../utils'; import { withSecuritySpan } from '../../../../utils/with_security_span'; import { buildThresholdSignalHistory } from '../threshold/build_signal_history'; @@ -81,7 +81,10 @@ export const thresholdExecutor = async ({ const ruleParams = completeRule.ruleParams; return withSecuritySpan('thresholdExecutor', async () => { - logUnprocessedExceptionsWarnings(unprocessedExceptions, ruleExecutionLogger); + const exceptionsWarning = getUnprocessedExceptionsWarnings(unprocessedExceptions); + if (exceptionsWarning) { + result.warningMessages.push(exceptionsWarning); + } // Get state or build initial state (on upgrade) const { signalHistory, searchErrors: previousSearchErrors } = state.initialized diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index afbcb355da05f..d5603130400c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -45,7 +45,7 @@ import { isDetectionAlert, getField, addToSearchAfterReturn, - logUnprocessedExceptionsWarnings, + getUnprocessedExceptionsWarnings, } from './utils'; import type { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { @@ -1667,14 +1667,13 @@ describe('utils', () => { describe('logUnprocessedExceptionsWarnings', () => { test('does not log anything when the array is empty', () => { - logUnprocessedExceptionsWarnings([], ruleExecutionLogger); - expect(ruleExecutionLogger.warn).not.toHaveBeenCalled(); + const result = getUnprocessedExceptionsWarnings([]); + expect(result).toBeUndefined(); }); test('logs the exception names when there are unprocessed exceptions', () => { - logUnprocessedExceptionsWarnings([getExceptionListItemSchemaMock()], ruleExecutionLogger); - expect(ruleExecutionLogger.warn).toHaveBeenCalled(); - expect(ruleExecutionLogger.warn.mock.calls[0][0]).toContain( + const result = getUnprocessedExceptionsWarnings([getExceptionListItemSchemaMock()]); + expect(result).toEqual( `The following exceptions won't be applied to rule execution: ${ getExceptionListItemSchemaMock().name }` diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index a7fe9dfaf6c30..6030400da2b75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -972,14 +972,13 @@ export const getField = (event: SimpleHit, field: string): SearchTypes | undefin } }; -export const logUnprocessedExceptionsWarnings = ( - unprocessedExceptions: ExceptionListItemSchema[], - ruleExecutionLogger: IRuleExecutionLogForExecutors -) => { +export const getUnprocessedExceptionsWarnings = ( + unprocessedExceptions: ExceptionListItemSchema[] +): string | undefined => { if (unprocessedExceptions.length > 0) { const exceptionNames = unprocessedExceptions.map((exception) => exception.name); - ruleExecutionLogger.warn( - `The following exceptions won't be applied to rule execution: ${exceptionNames.join(', ')}` - ); + return `The following exceptions won't be applied to rule execution: ${exceptionNames.join( + ', ' + )}`; } }; From 16589af4ca67c7136d5511fa22a9a37d8929c317 Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Mon, 26 Sep 2022 15:01:20 -0400 Subject: [PATCH 037/172] Observability a11y tests (#141266) * Updated test to use uiSettings. * Added initial files for observability a11y tests. * Observability test for the guided setup. * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * fixed dashboard merge conflict. * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Changed the wait condition to reduce flakiness. * Removed .only(). * Skipped a11y test after finding violation. Co-authored-by: cuffs Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../overview_page/overview_page.tsx | 5 ++- .../test/accessibility/apps/observability.ts | 42 +++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/accessibility/apps/observability.ts diff --git a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx index 1f09cfc38cf4c..e8a90cac969c6 100644 --- a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx @@ -226,7 +226,10 @@ export function OverviewPage() { > -

+

{ + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await PageObjects.common.navigateToApp('observability'); + }); + + describe('Overview', async () => { + before(async () => { + await observability.overview.common.openAlertsSectionAndWaitToAppear(); + await a11y.testAppSnapshot(); + }); + it('Guided Setup', async () => { + await PageObjects.infraHome.clickGuidedSetupButton(); + await retry.waitFor('Guided setup header to be visible', async () => { + return await testSubjects.isDisplayed('statusVisualizationFlyoutTitle'); + }); + await a11y.testAppSnapshot(); + }); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index be4fe6c531bed..524608b130779 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -54,6 +54,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/stack_monitoring'), require.resolve('./apps/watcher'), require.resolve('./apps/rollup_jobs'), + require.resolve('./apps/observability'), ], pageObjects, From d1498ac1fb3ea0e29632b540cf23ef1d092b35f4 Mon Sep 17 00:00:00 2001 From: Andrew Tate Date: Mon, 26 Sep 2022 14:16:31 -0500 Subject: [PATCH 038/172] [Lens] optimize duplicate formula functions (#140859) --- src/plugins/data/public/index.ts | 2 + .../dedupe_aggs.test.ts | 108 ++++++ .../indexpattern_datasource/dedupe_aggs.ts | 98 +++++ .../indexpattern.test.ts | 347 ++++++++++++------ .../definitions/cardinality.test.ts | 111 ++++++ .../operations/definitions/cardinality.tsx | 8 + .../operations/definitions/count.test.ts | 122 ++++++ .../operations/definitions/count.tsx | 9 + .../definitions/get_group_by_key.ts | 81 ++++ .../operations/definitions/index.ts | 23 ++ .../definitions/last_value.test.tsx | 105 ++++++ .../operations/definitions/last_value.tsx | 9 + .../operations/definitions/metrics.test.ts | 132 +++++++ .../operations/definitions/metrics.tsx | 9 + .../definitions/percentile.test.tsx | 109 +++++- .../operations/definitions/percentile.tsx | 15 + .../definitions/terms/helpers.test.ts | 134 ------- .../operations/definitions/terms/helpers.ts | 26 -- .../operations/definitions/terms/index.tsx | 11 +- .../definitions/terms/terms.test.tsx | 43 --- .../indexpattern_datasource/to_expression.ts | 21 +- 21 files changed, 1173 insertions(+), 350 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/get_group_by_key.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.test.ts diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index c8eefbdd92c6e..fa545d7648df1 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -139,6 +139,8 @@ export type { ParsedInterval, // expressions ExecutionContextSearch, + ExpressionFunctionKql, + ExpressionFunctionLucene, ExpressionFunctionKibana, ExpressionFunctionKibanaContext, ExpressionValueSearchContext, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.test.ts new file mode 100644 index 0000000000000..eb98d7411491c --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.test.ts @@ -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 { + buildExpression, + ExpressionAstExpressionBuilder, + parseExpression, +} from '@kbn/expressions-plugin/common'; +import { dedupeAggs } from './dedupe_aggs'; +import { operationDefinitionMap } from './operations'; +import { OriginalColumn } from './to_expression'; + +describe('dedupeAggs', () => { + const buildMapsFromAggBuilders = (aggs: ExpressionAstExpressionBuilder[]) => { + const esAggsIdMap: Record = {}; + const aggsToIdsMap = new Map(); + aggs.forEach((builder, i) => { + const esAggsId = `col-${i}-${i}`; + esAggsIdMap[esAggsId] = [{ id: `original-${i}` } as OriginalColumn]; + aggsToIdsMap.set(builder, esAggsId); + }); + return { + esAggsIdMap, + aggsToIdsMap, + }; + }; + + it('removes duplicate aggregations', () => { + const aggs = [ + 'aggSum id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false', + 'aggSum id="1" enabled=true schema="metric" field="bytes" emptyAsNull=false', + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="hour_of_day: *"}} \n customMetric={aggTopMetrics id="2-metric" enabled=true schema="metric" field="hour_of_day" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="3" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="hour_of_day: *"}} \n customMetric={aggTopMetrics id="3-metric" enabled=true schema="metric" field="hour_of_day" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggAvg id="4" enabled=true schema="metric" field="bytes"', + 'aggAvg id="5" enabled=true schema="metric" field="bytes"', + ].map((expression) => buildExpression(parseExpression(expression))); + + const { esAggsIdMap, aggsToIdsMap } = buildMapsFromAggBuilders(aggs); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { sum, last_value, average } = operationDefinitionMap; + + const operations = [sum, last_value, average]; + + operations.forEach((op) => expect(op.getGroupByKey).toBeDefined()); + + const { esAggsIdMap: newIdMap, aggs: newAggs } = dedupeAggs( + aggs, + esAggsIdMap, + aggsToIdsMap, + operations + ); + + expect(newAggs).toHaveLength(3); + + expect(newIdMap).toMatchInlineSnapshot(` + Object { + "col-0-0": Array [ + Object { + "id": "original-0", + }, + Object { + "id": "original-1", + }, + ], + "col-2-2": Array [ + Object { + "id": "original-2", + }, + Object { + "id": "original-3", + }, + ], + "col-4-4": Array [ + Object { + "id": "original-4", + }, + Object { + "id": "original-5", + }, + ], + } + `); + }); + + it('should update any terms order-by reference', () => { + const aggs = [ + 'aggTerms id="0" enabled=true schema="segment" field="clientip" orderBy="3" order="desc" size=5 includeIsRegex=false excludeIsRegex=false otherBucket=true otherBucketLabel="Other" missingBucket=false missingBucketLabel="(missing value)"', + 'aggMedian id="1" enabled=true schema="metric" field="bytes"', + 'aggMedian id="2" enabled=true schema="metric" field="bytes"', + 'aggMedian id="3" enabled=true schema="metric" field="bytes"', + ].map((expression) => buildExpression(parseExpression(expression))); + + const { esAggsIdMap, aggsToIdsMap } = buildMapsFromAggBuilders(aggs); + + const { aggs: newAggs } = dedupeAggs(aggs, esAggsIdMap, aggsToIdsMap, [ + operationDefinitionMap.median, + ]); + + expect(newAggs).toHaveLength(2); + + expect(newAggs[0].functions[0].getArgument('orderBy')?.[0]).toBe('1'); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.ts new file mode 100644 index 0000000000000..ce5b2b0f41f23 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dedupe_aggs.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 { AggFunctionsMapping } from '@kbn/data-plugin/public'; +import { + ExpressionAstExpressionBuilder, + ExpressionAstFunctionBuilder, +} from '@kbn/expressions-plugin/common'; +import { GenericOperationDefinition } from './operations'; +import { extractAggId, OriginalColumn } from './to_expression'; + +function groupByKey(items: T[], getKey: (item: T) => string | undefined): Record { + const groups: Record = {}; + + items.forEach((item) => { + const key = getKey(item); + if (key) { + if (!(key in groups)) { + groups[key] = []; + } + groups[key].push(item); + } + }); + + return groups; +} + +/** + * Consolidates duplicate agg expression builders to increase performance + */ +export function dedupeAggs( + _aggs: ExpressionAstExpressionBuilder[], + _esAggsIdMap: Record, + aggExpressionToEsAggsIdMap: Map, + allOperations: GenericOperationDefinition[] +): { + aggs: ExpressionAstExpressionBuilder[]; + esAggsIdMap: Record; +} { + let aggs = [..._aggs]; + const esAggsIdMap = { ..._esAggsIdMap }; + + const aggsByArgs = groupByKey(aggs, (expressionBuilder) => { + for (const operation of allOperations) { + const groupKey = operation.getGroupByKey?.(expressionBuilder); + if (groupKey) { + return `${operation.type}-${groupKey}`; + } + } + }); + + const termsFuncs = aggs + .map((agg) => agg.functions[0]) + .filter((func) => func.name === 'aggTerms') as Array< + ExpressionAstFunctionBuilder + >; + + // collapse each group into a single agg expression builder + Object.values(aggsByArgs).forEach((expressionBuilders) => { + if (expressionBuilders.length <= 1) { + // don't need to optimize if there aren't more than one + return; + } + + const [firstExpressionBuilder, ...restExpressionBuilders] = expressionBuilders; + + // throw away all but the first expression builder + aggs = aggs.filter((aggBuilder) => !restExpressionBuilders.includes(aggBuilder)); + + const firstEsAggsId = aggExpressionToEsAggsIdMap.get(firstExpressionBuilder); + if (firstEsAggsId === undefined) { + throw new Error('Could not find current column ID for expression builder'); + } + + restExpressionBuilders.forEach((expressionBuilder) => { + const currentEsAggsId = aggExpressionToEsAggsIdMap.get(expressionBuilder); + if (currentEsAggsId === undefined) { + throw new Error('Could not find current column ID for expression builder'); + } + + esAggsIdMap[firstEsAggsId].push(...esAggsIdMap[currentEsAggsId]); + + delete esAggsIdMap[currentEsAggsId]; + + termsFuncs.forEach((func) => { + if (func.getArgument('orderBy')?.[0] === extractAggId(currentEsAggsId)) { + func.replaceArgument('orderBy', [extractAggId(firstEsAggsId)]); + } + }); + }); + }); + + return { aggs, esAggsIdMap }; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index c19338dd156f0..481701b1e7824 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -1220,138 +1220,261 @@ describe('IndexPattern Data Source', () => { expect(ast.chain[1].arguments.timeFields).not.toContain('timefield'); }); - it('should call optimizeEsAggs once per operation for which it is available', () => { - const queryBaseState: IndexPatternPrivateState = { - currentIndexPatternId: '1', - layers: { - first: { - indexPatternId: '1', - columns: { - col1: { - label: 'timestamp', - dataType: 'date', - operationType: 'date_histogram', - sourceField: 'timestamp', - isBucketed: true, - scale: 'interval', - params: { - interval: 'auto', - includeEmptyRows: true, - dropPartials: false, - }, - } as DateHistogramIndexPatternColumn, - col2: { - label: '95th percentile of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { - percentile: 95, + describe('optimizations', () => { + it('should call optimizeEsAggs once per operation for which it is available', () => { + const queryBaseState: IndexPatternPrivateState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columns: { + col1: { + label: 'timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: 'timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + } as DateHistogramIndexPatternColumn, + col2: { + label: '95th percentile of bytes', + dataType: 'number', + operationType: 'percentile', + sourceField: 'bytes', + isBucketed: false, + scale: 'ratio', + params: { + percentile: 95, + }, + } as PercentileIndexPatternColumn, + col3: { + label: '95th percentile of bytes', + dataType: 'number', + operationType: 'percentile', + sourceField: 'bytes', + isBucketed: false, + scale: 'ratio', + params: { + percentile: 95, + }, + } as PercentileIndexPatternColumn, + }, + columnOrder: ['col1', 'col2', 'col3'], + incompleteColumns: {}, + }, + }, + }; + + const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); + + indexPatternDatasource.toExpression(queryBaseState, 'first', indexPatterns); + + expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); + + optimizeMock.mockRestore(); + }); + + it('should update anticipated esAggs column IDs based on the order of the optimized agg expression builders', () => { + const queryBaseState: IndexPatternPrivateState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columns: { + col1: { + label: 'timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: 'timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + } as DateHistogramIndexPatternColumn, + col2: { + label: '95th percentile of bytes', + dataType: 'number', + operationType: 'percentile', + sourceField: 'bytes', + isBucketed: false, + scale: 'ratio', + params: { + percentile: 95, + }, + } as PercentileIndexPatternColumn, + col3: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + timeScale: 'h', }, - } as PercentileIndexPatternColumn, - col3: { - label: '95th percentile of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { - percentile: 95, + col4: { + label: 'Count of records2', + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + operationType: 'count', + timeScale: 'h', }, - } as PercentileIndexPatternColumn, + }, + columnOrder: ['col1', 'col2', 'col3', 'col4'], + incompleteColumns: {}, }, - columnOrder: ['col1', 'col2', 'col3'], - incompleteColumns: {}, }, - }, - }; + }; - const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); + const optimizeMock = jest + .spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs') + .mockImplementation((aggs, esAggsIdMap) => { + // change the order of the aggregations + return { aggs: aggs.reverse(), esAggsIdMap }; + }); - indexPatternDatasource.toExpression(queryBaseState, 'first', indexPatterns); + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; - expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); + expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); - optimizeMock.mockRestore(); - }); + const idMap = JSON.parse(ast.chain[2].arguments.idMap as unknown as string); - it('should update anticipated esAggs column IDs based on the order of the optimized agg expression builders', () => { - const queryBaseState: IndexPatternPrivateState = { - currentIndexPatternId: '1', - layers: { - first: { - indexPatternId: '1', - columns: { - col1: { - label: 'timestamp', - dataType: 'date', - operationType: 'date_histogram', - sourceField: 'timestamp', - isBucketed: true, - scale: 'interval', - params: { - interval: 'auto', - includeEmptyRows: true, - dropPartials: false, + expect(Object.keys(idMap)).toEqual(['col-0-3', 'col-1-2', 'col-2-1', 'col-3-0']); + + optimizeMock.mockRestore(); + }); + + it('should deduplicate aggs for supported operations', () => { + const queryBaseState: IndexPatternPrivateState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columns: { + col1: { + label: 'My Op', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + sourceField: 'op', + params: { + size: 5, + orderBy: { + type: 'column', + columnId: 'col4', // col4 will disappear + }, + orderDirection: 'asc', + }, + } as TermsIndexPatternColumn, + col2: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + timeScale: 'h', }, - } as DateHistogramIndexPatternColumn, - col2: { - label: '95th percentile of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { - percentile: 95, + col3: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + timeScale: 'h', + }, + col4: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + timeScale: 'h', }, - } as PercentileIndexPatternColumn, - col3: { - label: 'Count of records', - dataType: 'number', - isBucketed: false, - sourceField: '___records___', - operationType: 'count', - timeScale: 'h', - }, - col4: { - label: 'Count of records2', - dataType: 'number', - isBucketed: false, - sourceField: '___records___', - operationType: 'count', - timeScale: 'h', }, + columnOrder: ['col1', 'col2', 'col3', 'col4'], + incompleteColumns: {}, }, - columnOrder: ['col1', 'col2', 'col3', 'col4'], - incompleteColumns: {}, }, - }, - }; + }; - const optimizeMock = jest - .spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs') - .mockImplementation((aggs, esAggsIdMap) => { - // change the order of the aggregations - return { aggs: aggs.reverse(), esAggsIdMap }; - }); + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; - const ast = indexPatternDatasource.toExpression( - queryBaseState, - 'first', - indexPatterns - ) as Ast; + const idMap = JSON.parse(ast.chain[2].arguments.idMap as unknown as string); - expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); + const aggs = ast.chain[1].arguments.aggs; - const idMap = JSON.parse(ast.chain[2].arguments.idMap as unknown as string); + expect(aggs).toHaveLength(2); - expect(Object.keys(idMap)).toEqual(['col-0-3', 'col-1-2', 'col-2-1', 'col-3-0']); + // orderby reference updated + expect((aggs[0] as Ast).chain[0].arguments.orderBy[0]).toBe('1'); - optimizeMock.mockRestore(); + expect(idMap).toMatchInlineSnapshot(` + Object { + "col-0-0": Array [ + Object { + "dataType": "string", + "id": "col1", + "isBucketed": true, + "label": "My Op", + "operationType": "terms", + "params": Object { + "orderBy": Object { + "columnId": "col4", + "type": "column", + }, + "orderDirection": "asc", + "size": 5, + }, + "sourceField": "op", + }, + ], + "col-1-1": Array [ + Object { + "dataType": "number", + "id": "col2", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "sourceField": "___records___", + "timeScale": "h", + }, + Object { + "dataType": "number", + "id": "col3", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "sourceField": "___records___", + "timeScale": "h", + }, + Object { + "dataType": "number", + "id": "col4", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "sourceField": "___records___", + "timeScale": "h", + }, + ], + } + `); + }); }); describe('references', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.test.ts new file mode 100644 index 0000000000000..10d26cc3dbbd2 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildExpression, parseExpression } from '@kbn/expressions-plugin/common'; +import { operationDefinitionMap } from '.'; + +describe('unique values function', () => { + describe('getGroupByKey', () => { + const getKey = operationDefinitionMap.unique_count.getGroupByKey!; + const expressionToKey = (expression: string) => + getKey(buildExpression(parseExpression(expression))) as string; + describe('generates unique keys based on configuration', () => { + const keys = [ + // group 1 + [ + 'aggCardinality id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false', + 'aggCardinality id="1" enabled=true schema="metric" field="bytes" emptyAsNull=false', + ], + // group 2 + [ + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggCardinality id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="3" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggCardinality id="3-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + // group 3 + [ + 'aggFilteredMetric id="4" enabled=true schema="metric" \n customBucket={aggFilter id="4-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggCardinality id="4-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="5" enabled=true schema="metric" \n customBucket={aggFilter id="5-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggCardinality id="5-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + // group 4 + [ + 'aggFilteredMetric id="6" enabled=true schema="metric" \n customBucket={aggFilter id="6-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggCardinality id="6-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="7" enabled=true schema="metric" \n customBucket={aggFilter id="7-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggCardinality id="7-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + // group 5 + [ + 'aggFilteredMetric id="8" enabled=true schema="metric" \n customBucket={aggFilter id="8-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggCardinality id="8-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="9" enabled=true schema="metric" \n customBucket={aggFilter id="9-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggCardinality id="9-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + // check emptyAsNull cases + [ + 'aggCardinality id="10" enabled=true schema="metric" field="bytes" emptyAsNull=true', + 'aggCardinality id="11" enabled=true schema="metric" field="bytes" emptyAsNull=true', + ], + [ + 'aggFilteredMetric id="12" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggCardinality id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=true}', + 'aggFilteredMetric id="13" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggCardinality id="3-metric" enabled=true schema="metric" field="bytes" emptyAsNull=true}', + ], + ].map((group) => group.map(expressionToKey)); + + it.each(keys.map((group, i) => ({ group })))('%#', ({ group: thisGroup }) => { + expect(thisGroup[0]).toEqual(thisGroup[1]); + const otherGroups = keys.filter((group) => group !== thisGroup); + for (const otherGroup of otherGroups) { + expect(thisGroup[0]).not.toEqual(otherGroup[0]); + } + }); + + it('snapshot', () => { + expect(keys).toMatchInlineSnapshot(` + Array [ + Array [ + "aggCardinality-bytes-false-undefined", + "aggCardinality-bytes-false-undefined", + ], + Array [ + "aggCardinality-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggCardinality-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + Array [ + "aggCardinality-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + "aggCardinality-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggCardinality-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + "aggCardinality-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + ], + Array [ + "aggCardinality-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + "aggCardinality-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggCardinality-bytes-true-undefined", + "aggCardinality-bytes-true-undefined", + ], + Array [ + "aggCardinality-filtered-bytes-true-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggCardinality-filtered-bytes-true-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + ] + `); + }); + }); + + it('returns undefined for aggs from different operation classes', () => { + expect( + expressionToKey( + 'aggSum id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false' + ) + ).toBeUndefined(); + expect( + expressionToKey( + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}' + ) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 44474fba14dac..b08fbf8f6c89a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -26,6 +26,7 @@ import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; import { updateColumnParam } from '../layer_helpers'; import { getColumnReducedTimeRangeError } from '../../reduced_time_range_utils'; +import { getGroupByKey } from './get_group_by_key'; const supportedTypes = new Set([ 'string', @@ -186,6 +187,13 @@ export const cardinalityOperation: OperationDefinition< emptyAsNull: column.params?.emptyAsNull, }).toAst(); }, + getGroupByKey: (agg) => { + return getGroupByKey( + agg, + ['aggCardinality'], + [{ name: 'field' }, { name: 'emptyAsNull', transformer: (val) => String(Boolean(val)) }] + ); + }, onFieldChange: (oldColumn, field) => { return { ...oldColumn, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.test.ts new file mode 100644 index 0000000000000..9f89834548837 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.test.ts @@ -0,0 +1,122 @@ +/* + * 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 { buildExpression, parseExpression } from '@kbn/expressions-plugin/common'; +import { operationDefinitionMap } from '.'; + +describe('count operation', () => { + describe('getGroupByKey', () => { + const getKey = operationDefinitionMap.count.getGroupByKey!; + const expressionToKey = (expression: string) => + getKey(buildExpression(parseExpression(expression))) as string; + + describe('generates unique keys based on configuration', () => { + const keys = [ + [ + 'aggValueCount id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false', + 'aggValueCount id="1" enabled=true schema="metric" field="bytes" emptyAsNull=false', + ], + [ + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggValueCount id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="3" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggValueCount id="3-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="4" enabled=true schema="metric" \n customBucket={aggFilter id="4-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggValueCount id="4-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="5" enabled=true schema="metric" \n customBucket={aggFilter id="5-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggValueCount id="5-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="6" enabled=true schema="metric" \n customBucket={aggFilter id="6-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggValueCount id="6-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="7" enabled=true schema="metric" \n customBucket={aggFilter id="7-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggValueCount id="7-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="8" enabled=true schema="metric" \n customBucket={aggFilter id="8-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggValueCount id="8-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="9" enabled=true schema="metric" \n customBucket={aggFilter id="9-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggValueCount id="9-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggCount id="10" enabled=true schema="metric" emptyAsNull=true', + 'aggCount id="11" enabled=true schema="metric" emptyAsNull=true', + ], + [ + 'aggCount id="10" enabled=true schema="metric" emptyAsNull=false', + 'aggCount id="11" enabled=true schema="metric" emptyAsNull=false', + ], + [ + 'aggValueCount id="12" enabled=true schema="metric" field="agent.keyword" emptyAsNull=false', + 'aggValueCount id="13" enabled=true schema="metric" field="agent.keyword" emptyAsNull=false', + ], + [ + 'aggValueCount id="12" enabled=true schema="metric" field="agent.keyword" emptyAsNull=true', + 'aggValueCount id="13" enabled=true schema="metric" field="agent.keyword" emptyAsNull=true', + ], + ].map((group) => group.map(expressionToKey)); + + it.each(keys.map((group, i) => ({ group })))('%#', ({ group: thisGroup }) => { + expect(thisGroup[0]).toEqual(thisGroup[1]); + const otherGroups = keys.filter((group) => group !== thisGroup); + for (const otherGroup of otherGroups) { + expect(thisGroup[0]).not.toEqual(otherGroup[0]); + } + }); + + it('snapshot', () => { + expect(keys).toMatchInlineSnapshot(` + Array [ + Array [ + "aggValueCount-bytes-false-undefined", + "aggValueCount-bytes-false-undefined", + ], + Array [ + "aggValueCount-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggValueCount-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + Array [ + "aggValueCount-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + "aggValueCount-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggValueCount-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + "aggValueCount-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + ], + Array [ + "aggValueCount-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + "aggValueCount-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggCount-undefined-true-undefined", + "aggCount-undefined-true-undefined", + ], + Array [ + "aggCount-undefined-false-undefined", + "aggCount-undefined-false-undefined", + ], + Array [ + "aggValueCount-agent.keyword-false-undefined", + "aggValueCount-agent.keyword-false-undefined", + ], + Array [ + "aggValueCount-agent.keyword-true-undefined", + "aggValueCount-agent.keyword-true-undefined", + ], + ] + `); + }); + }); + + it('returns undefined for aggs from different operation classes', () => { + expect( + expressionToKey( + 'aggSum id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false' + ) + ).toBeUndefined(); + expect( + expressionToKey( + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}' + ) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index e2eedc5a2d3c6..4c325e604f64c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -26,6 +26,7 @@ import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; import { updateColumnParam } from '../layer_helpers'; import { getColumnReducedTimeRangeError } from '../../reduced_time_range_utils'; +import { getGroupByKey } from './get_group_by_key'; const countLabel = i18n.translate('xpack.lens.indexPattern.countOf', { defaultMessage: 'Count of records', @@ -206,6 +207,14 @@ export const countOperation: OperationDefinition { + return getGroupByKey( + agg, + ['aggCount', 'aggValueCount'], + [{ name: 'field' }, { name: 'emptyAsNull', transformer: (val) => String(Boolean(val)) }] + ); + }, + isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.getFieldByName(column.sourceField); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/get_group_by_key.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/get_group_by_key.ts new file mode 100644 index 0000000000000..92bcfdb2e7450 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/get_group_by_key.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AggFunctionsMapping, + ExpressionFunctionKql, + ExpressionFunctionLucene, +} from '@kbn/data-plugin/public'; +import { + AnyExpressionFunctionDefinition, + ExpressionAstExpressionBuilder, + ExpressionAstFunctionBuilder, +} from '@kbn/expressions-plugin/common'; +import { Primitive } from 'utility-types'; + +/** + * Computes a group-by key for an agg expression builder based on distinctive expression function arguments + */ +export const getGroupByKey = ( + agg: ExpressionAstExpressionBuilder, + aggNames: string[], + importantExpressionArgs: Array<{ name: string; transformer?: (value: Primitive) => string }> +) => { + const { + functions: [fnBuilder], + } = agg; + + const pieces = []; + + if (aggNames.includes(fnBuilder.name)) { + pieces.push(fnBuilder.name); + + importantExpressionArgs.forEach(({ name, transformer }) => { + const arg = fnBuilder.getArgument(name)?.[0]; + pieces.push(transformer ? transformer(arg) : arg); + }); + + pieces.push(fnBuilder.getArgument('timeShift')?.[0]); + } + + if (fnBuilder.name === 'aggFilteredMetric') { + const metricFnBuilder = fnBuilder.getArgument('customMetric')?.[0].functions[0] as + | ExpressionAstFunctionBuilder + | undefined; + + if (metricFnBuilder && aggNames.includes(metricFnBuilder.name)) { + pieces.push(metricFnBuilder.name); + pieces.push('filtered'); + + const aggFilterFnBuilder = ( + fnBuilder.getArgument('customBucket')?.[0] as ExpressionAstExpressionBuilder + ).functions[0] as ExpressionAstFunctionBuilder; + + importantExpressionArgs.forEach(({ name, transformer }) => { + const arg = metricFnBuilder.getArgument(name)?.[0]; + pieces.push(transformer ? transformer(arg) : arg); + }); + + pieces.push(aggFilterFnBuilder.getArgument('timeWindow')); + pieces.push(fnBuilder.getArgument('timeShift')); + + const filterExpression = aggFilterFnBuilder.getArgument('filter')?.[0] as + | ExpressionAstExpressionBuilder + | undefined; + + if (filterExpression) { + const filterFnBuilder = filterExpression.functions[0] as + | ExpressionAstFunctionBuilder + | undefined; + + pieces.push(filterFnBuilder?.name, filterFnBuilder?.getArgument('q')?.[0]); + } + } + } + + return pieces.length ? pieces.map(String).join('-') : undefined; +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 31292218078e2..5ce13adbf5ca8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -408,6 +408,29 @@ interface BaseOperationDefinitionProps< * Title for the help component */ helpComponentTitle?: string; + + /** + * Used to remove duplicate aggregations for performance reasons. + * + * This method should return a key only if the provided agg is generated by this particular operation. + * The key should represent the important configurations of the operation, such that + * + * const column1 = ...; + * const column2 = ...; // different configuration! + * + * const agg1 = operation.toEsAggsFn(column1); + * const agg2 = operation.toEsAggsFn(column1); + * const agg3 = operation.toEsAggsFn(column2); + * + * const key1 = operation.getGroupByKey(agg1); + * const key2 = operation.getGroupByKey(agg2); + * const key3 = operation.getGroupByKey(agg3); + * + * key1 === key2 + * key1 !== key3 + */ + getGroupByKey?: (agg: ExpressionAstExpressionBuilder) => string | undefined; + /** * Optimizes EsAggs expression. Invoked only once per operation type. */ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index df219c1e851be..05386492544f4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -21,6 +21,7 @@ import type { IndexPatternLayer } from '../../types'; import type { IndexPattern } from '../../../types'; import { TermsIndexPatternColumn } from './terms'; import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { buildExpression, parseExpression } from '@kbn/expressions-plugin/common'; const uiSettingsMock = {} as IUiSettingsClient; @@ -917,4 +918,108 @@ describe('last_value', () => { ]); }); }); + + describe('getGroupByKey', () => { + const getKey = lastValueOperation.getGroupByKey!; + const expressionToKey = (expression: string) => + getKey(buildExpression(parseExpression(expression))); + + describe('collapses duplicate aggs', () => { + const keys = [ + [ + 'aggFilteredMetric id="0" enabled=true schema="metric" \n customBucket={aggFilter id="0-filter" enabled=true schema="bucket" filter={kql q="bytes: *"}} \n customMetric={aggTopMetrics id="0-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="1" enabled=true schema="metric" \n customBucket={aggFilter id="1-filter" enabled=true schema="bucket" filter={kql q="bytes: *"}} \n customMetric={aggTopMetrics id="1-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + ], + [ + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="machine.ram: *"}} \n customMetric={aggTopMetrics id="2-metric" enabled=true schema="metric" field="machine.ram" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="3" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="machine.ram: *"}} \n customMetric={aggTopMetrics id="3-metric" enabled=true schema="metric" field="machine.ram" size=1 sortOrder="desc" sortField="timestamp"}', + ], + [ + 'aggFilteredMetric id="4" enabled=true schema="metric" \n customBucket={aggFilter id="4-filter" enabled=true schema="bucket" filter={kql q="machine.ram: *"} timeShift="1h"} \n customMetric={aggTopMetrics id="4-metric" enabled=true schema="metric" field="machine.ram" size=1 sortOrder="desc" sortField="timestamp"} timeShift="1h"', + 'aggFilteredMetric id="5" enabled=true schema="metric" \n customBucket={aggFilter id="5-filter" enabled=true schema="bucket" filter={kql q="machine.ram: *"} timeShift="1h"} \n customMetric={aggTopMetrics id="5-metric" enabled=true schema="metric" field="machine.ram" size=1 sortOrder="desc" sortField="timestamp"} timeShift="1h"', + ], + [ + 'aggFilteredMetric id="6" enabled=true schema="metric" \n customBucket={aggFilter id="6-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggTopMetrics id="6-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="7" enabled=true schema="metric" \n customBucket={aggFilter id="7-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggTopMetrics id="7-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + ], + [ + 'aggFilteredMetric id="8" enabled=true schema="metric" \n customBucket={aggFilter id="8-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggTopMetrics id="8-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="9" enabled=true schema="metric" \n customBucket={aggFilter id="9-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggTopMetrics id="9-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + ], + [ + 'aggFilteredMetric id="10" enabled=true schema="metric" \n customBucket={aggFilter id="10-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggTopMetrics id="10-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="11" enabled=true schema="metric" \n customBucket={aggFilter id="11-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggTopMetrics id="11-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + ], + [ + 'aggFilteredMetric id="12" enabled=true schema="metric" \n customBucket={aggFilter id="12-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggTopMetrics id="12-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + 'aggFilteredMetric id="13" enabled=true schema="metric" \n customBucket={aggFilter id="13-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggTopMetrics id="13-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp"}', + ], + // uses aggTopHit + [ + 'aggFilteredMetric id="14" enabled=true schema="metric" \n customBucket={aggFilter id="14-filter" enabled=true schema="bucket" filter={kql q="bytes: *"}} \n customMetric={aggTopHit id="14-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp" aggregate="concat"}', + 'aggFilteredMetric id="15" enabled=true schema="metric" \n customBucket={aggFilter id="15-filter" enabled=true schema="bucket" filter={kql q="bytes: *"}} \n customMetric={aggTopHit id="15-metric" enabled=true schema="metric" field="bytes" size=1 sortOrder="desc" sortField="timestamp" aggregate="concat"}', + ], + ].map((group) => group.map(expressionToKey)); + + it.each(keys.map((group, i) => ({ group })))('%#', ({ group: thisGroup }) => { + expect(thisGroup[0]).toEqual(thisGroup[1]); + const otherGroups = keys.filter((group) => group !== thisGroup); + for (const otherGroup of otherGroups) { + expect(thisGroup[0]).not.toEqual(otherGroup[0]); + } + }); + + it('snapshot', () => { + expect(keys).toMatchInlineSnapshot(` + Array [ + Array [ + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-bytes: *", + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-bytes: *", + ], + Array [ + "aggTopMetrics-filtered-machine.ram-timestamp-undefined-undefined-kql-machine.ram: *", + "aggTopMetrics-filtered-machine.ram-timestamp-undefined-undefined-kql-machine.ram: *", + ], + Array [ + "aggTopMetrics-filtered-machine.ram-timestamp-undefined-1h-kql-machine.ram: *", + "aggTopMetrics-filtered-machine.ram-timestamp-undefined-1h-kql-machine.ram: *", + ], + Array [ + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + Array [ + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-geo.dest: \\"AL\\" ", + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + "aggTopMetrics-filtered-bytes-timestamp-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + ], + Array [ + "aggTopMetrics-filtered-bytes-timestamp-1m-undefined-kql-geo.dest: \\"AL\\" ", + "aggTopMetrics-filtered-bytes-timestamp-1m-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggTopHit-filtered-bytes-timestamp-undefined-undefined-kql-bytes: *", + "aggTopHit-filtered-bytes-timestamp-undefined-undefined-kql-bytes: *", + ], + ] + `); + }); + }); + + it('returns undefined for aggs from different operation classes', () => { + expect( + expressionToKey( + 'aggSum id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false' + ) + ).toBeUndefined(); + expect( + expressionToKey( + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}' + ) + ).toBeUndefined(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index b7fd3a40fdbff..2709d22b2a323 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -33,6 +33,7 @@ import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; import { isScriptedField } from './terms/helpers'; import { FormRow } from './shared_components/form_row'; import { getColumnReducedTimeRangeError } from '../../reduced_time_range_utils'; +import { getGroupByKey } from './get_group_by_key'; function ofName(name: string, timeShift: string | undefined, reducedTimeRange: string | undefined) { return adjustTimeScaleLabelSuffix( @@ -258,6 +259,14 @@ export const lastValueOperation: OperationDefinition< ).toAst(); }, + getGroupByKey: (agg) => { + return getGroupByKey( + agg, + ['aggTopHit', 'aggTopMetrics'], + [{ name: 'field' }, { name: 'sortField' }] + ); + }, + isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.getFieldByName(column.sourceField); const newTimeField = newIndexPattern.getFieldByName(column.params.sortField); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.test.ts new file mode 100644 index 0000000000000..b98125d0bbd2b --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildExpression, parseExpression } from '@kbn/expressions-plugin/common'; +import { operationDefinitionMap } from '.'; + +const sumOperation = operationDefinitionMap.sum; + +describe('metrics', () => { + describe('getGroupByKey', () => { + const getKey = sumOperation.getGroupByKey!; + const expressionToKey = (expression: string) => + getKey(buildExpression(parseExpression(expression))); + + describe('should collapse filtered aggs with matching parameters', () => { + const keys = [ + [ + 'aggSum id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false', + 'aggSum id="1" enabled=true schema="metric" field="bytes" emptyAsNull=false', + ], + [ + 'aggSum id="2" enabled=true schema="metric" field="bytes" emptyAsNull=true', + 'aggSum id="3" enabled=true schema="metric" field="bytes" emptyAsNull=true', + ], + [ + 'aggSum id="4" enabled=true schema="metric" field="hour_of_day" emptyAsNull=false', + 'aggSum id="5" enabled=true schema="metric" field="hour_of_day" emptyAsNull=false', + ], + [ + 'aggSum id="6" enabled=true schema="metric" field="machine.ram" timeShift="1h" emptyAsNull=false', + 'aggSum id="7" enabled=true schema="metric" field="machine.ram" timeShift="1h" emptyAsNull=false', + ], + [ + 'aggSum id="8" enabled=true schema="metric" field="machine.ram" timeShift="2h" emptyAsNull=false', + 'aggSum id="9" enabled=true schema="metric" field="machine.ram" timeShift="2h" emptyAsNull=false', + ], + [ + 'aggFilteredMetric id="10" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=true}', + 'aggFilteredMetric id="11" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="3-metric" enabled=true schema="metric" field="bytes" emptyAsNull=true}', + ], + [ + 'aggFilteredMetric id="12" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="13" enabled=true schema="metric" \n customBucket={aggFilter id="3-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggSum id="3-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="14" enabled=true schema="metric" \n customBucket={aggFilter id="4-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggSum id="4-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="15" enabled=true schema="metric" \n customBucket={aggFilter id="5-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "}} \n customMetric={aggSum id="5-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="16" enabled=true schema="metric" \n customBucket={aggFilter id="6-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggSum id="6-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="17" enabled=true schema="metric" \n customBucket={aggFilter id="7-filter" enabled=true schema="bucket" filter={lucene q="\\"geo.dest: \\\\\\"AL\\\\\\" \\""}} \n customMetric={aggSum id="7-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + [ + 'aggFilteredMetric id="18" enabled=true schema="metric" \n customBucket={aggFilter id="8-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggSum id="8-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + 'aggFilteredMetric id="19" enabled=true schema="metric" \n customBucket={aggFilter id="9-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"AL\\" "} timeWindow="1m"} \n customMetric={aggSum id="9-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}', + ], + ].map((group) => group.map(expressionToKey)); + + it.each(keys.map((group, i) => ({ group })))('%#', ({ group: thisGroup }) => { + expect(thisGroup[0]).toEqual(thisGroup[1]); + const otherGroups = keys.filter((group) => group !== thisGroup); + for (const otherGroup of otherGroups) { + expect(thisGroup[0]).not.toEqual(otherGroup[0]); + } + }); + + it('snapshot', () => { + expect(keys).toMatchInlineSnapshot(` + Array [ + Array [ + "aggSum-bytes-false-undefined", + "aggSum-bytes-false-undefined", + ], + Array [ + "aggSum-bytes-true-undefined", + "aggSum-bytes-true-undefined", + ], + Array [ + "aggSum-hour_of_day-false-undefined", + "aggSum-hour_of_day-false-undefined", + ], + Array [ + "aggSum-machine.ram-false-1h", + "aggSum-machine.ram-false-1h", + ], + Array [ + "aggSum-machine.ram-false-2h", + "aggSum-machine.ram-false-2h", + ], + Array [ + "aggSum-filtered-bytes-true-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggSum-filtered-bytes-true-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + Array [ + "aggSum-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + "aggSum-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"GA\\" ", + ], + Array [ + "aggSum-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + "aggSum-filtered-bytes-false-undefined-undefined-kql-geo.dest: \\"AL\\" ", + ], + Array [ + "aggSum-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + "aggSum-filtered-bytes-false-undefined-undefined-lucene-\\"geo.dest: \\\\\\"AL\\\\\\" \\"", + ], + Array [ + "aggSum-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + "aggSum-filtered-bytes-false-1m-undefined-kql-geo.dest: \\"AL\\" ", + ], + ] + `); + }); + }); + + it('returns undefined for aggs from different operation classes', () => { + expect( + expressionToKey( + 'aggCardinality id="0" enabled=true schema="metric" field="bytes" emptyAsNull=false' + ) + ).toBeUndefined(); + expect( + expressionToKey( + 'aggFilteredMetric id="2" enabled=true schema="metric" \n customBucket={aggFilter id="2-filter" enabled=true schema="bucket" filter={kql q="geo.dest: \\"GA\\" "}} \n customMetric={aggCardinality id="2-metric" enabled=true schema="metric" field="bytes" emptyAsNull=false}' + ) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index 9b8f790e51b90..09dc8576a042a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -28,6 +28,7 @@ import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; import { updateColumnParam } from '../layer_helpers'; import { getColumnReducedTimeRangeError } from '../../reduced_time_range_utils'; +import { getGroupByKey } from './get_group_by_key'; type MetricColumn = FieldBasedIndexPatternColumn & { operationType: T; @@ -198,6 +199,14 @@ function buildMetricOperation>({ ...aggConfigParams, }).toAst(); }, + getGroupByKey: (agg) => { + return getGroupByKey( + agg, + [typeToFn[type]], + [{ name: 'field' }, { name: 'emptyAsNull', transformer: (val) => String(Boolean(val)) }] + ); + }, + getErrorMessage: (layer, columnId, indexPattern) => combineErrorMessages([ getInvalidFieldMessage( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index f72342f79bfc6..77434d7ecc7ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -28,6 +28,7 @@ import { } from '@kbn/expressions-plugin/public'; import type { OriginalColumn } from '../../to_expression'; import { IndexPattern } from '../../../types'; +import faker from 'faker'; jest.mock('lodash', () => { const original = jest.requireActual('lodash'); @@ -234,7 +235,7 @@ describe('percentile', () => { const aggs = [ // group 1 makeEsAggBuilder('aggSinglePercentile', { - id: 1, + id: '1', enabled: true, schema: 'metric', field: field1, @@ -242,7 +243,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 2, + id: '2', enabled: true, schema: 'metric', field: field1, @@ -250,7 +251,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 3, + id: '3', enabled: true, schema: 'metric', field: field1, @@ -259,7 +260,7 @@ describe('percentile', () => { }), // group 2 makeEsAggBuilder('aggSinglePercentile', { - id: 4, + id: '4', enabled: true, schema: 'metric', field: field2, @@ -267,7 +268,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 5, + id: '5', enabled: true, schema: 'metric', field: field2, @@ -276,7 +277,7 @@ describe('percentile', () => { }), // group 3 makeEsAggBuilder('aggSinglePercentile', { - id: 6, + id: '6', enabled: true, schema: 'metric', field: field2, @@ -284,7 +285,7 @@ describe('percentile', () => { timeShift: timeShift1, }), makeEsAggBuilder('aggSinglePercentile', { - id: 7, + id: '7', enabled: true, schema: 'metric', field: field2, @@ -293,7 +294,7 @@ describe('percentile', () => { }), // group 4 makeEsAggBuilder('aggSinglePercentile', { - id: 8, + id: '8', enabled: true, schema: 'metric', field: field2, @@ -301,7 +302,7 @@ describe('percentile', () => { timeShift: timeShift2, }), makeEsAggBuilder('aggSinglePercentile', { - id: 9, + id: '9', enabled: true, schema: 'metric', field: field2, @@ -344,7 +345,7 @@ describe('percentile', () => { "foo", ], "id": Array [ - 1, + "1", ], "percents": Array [ 10, @@ -381,7 +382,7 @@ describe('percentile', () => { "bar", ], "id": Array [ - 4, + "4", ], "percents": Array [ 10, @@ -417,7 +418,7 @@ describe('percentile', () => { "bar", ], "id": Array [ - 6, + "6", ], "percents": Array [ 50, @@ -456,7 +457,7 @@ describe('percentile', () => { "bar", ], "id": Array [ - 8, + "8", ], "percents": Array [ 70, @@ -544,7 +545,7 @@ describe('percentile', () => { const aggs = [ // group 1 makeEsAggBuilder('aggSinglePercentile', { - id: 1, + id: '1', enabled: true, schema: 'metric', field: field1, @@ -552,7 +553,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 2, + id: '2', enabled: true, schema: 'metric', field: field1, @@ -560,7 +561,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 4, + id: '4', enabled: true, schema: 'metric', field: field2, @@ -568,7 +569,7 @@ describe('percentile', () => { timeShift: undefined, }), makeEsAggBuilder('aggSinglePercentile', { - id: 3, + id: '3', enabled: true, schema: 'metric', field: field1, @@ -609,17 +610,87 @@ describe('percentile', () => { `); }); + it('should update order-by references for any terms columns', () => { + const field1 = 'foo'; + const field2 = 'bar'; + const percentile = faker.random.number(100); + + const aggs = [ + makeEsAggBuilder('aggTerms', { + id: '1', + enabled: true, + schema: 'metric', + field: field1, + orderBy: '4', + timeShift: undefined, + }), + makeEsAggBuilder('aggTerms', { + id: '2', + enabled: true, + schema: 'metric', + field: field1, + orderBy: '6', + timeShift: undefined, + }), + makeEsAggBuilder('aggSinglePercentile', { + id: '3', + enabled: true, + schema: 'metric', + field: field1, + percentile, + timeShift: undefined, + }), + makeEsAggBuilder('aggSinglePercentile', { + id: '4', + enabled: true, + schema: 'metric', + field: field1, + percentile, + timeShift: undefined, + }), + makeEsAggBuilder('aggSinglePercentile', { + id: '5', + enabled: true, + schema: 'metric', + field: field2, + percentile, + timeShift: undefined, + }), + makeEsAggBuilder('aggSinglePercentile', { + id: '6', + enabled: true, + schema: 'metric', + field: field2, + percentile, + timeShift: undefined, + }), + ]; + + const { esAggsIdMap, aggsToIdsMap } = buildMapsFromAggBuilders(aggs); + + const { aggs: newAggs } = percentileOperation.optimizeEsAggs!( + aggs, + esAggsIdMap, + aggsToIdsMap + ); + + expect(newAggs.length).toBe(4); + + expect(newAggs[0].functions[0].getArgument('orderBy')?.[0]).toBe(`3.${percentile}`); + expect(newAggs[1].functions[0].getArgument('orderBy')?.[0]).toBe(`5.${percentile}`); + }); + it("shouldn't touch non-percentile aggs or single percentiles with no siblings", () => { const aggs = [ makeEsAggBuilder('aggSinglePercentile', { - id: 1, + id: '1', enabled: true, schema: 'metric', field: 'foo', percentile: 30, }), makeEsAggBuilder('aggMax', { - id: 1, + id: '1', enabled: true, schema: 'metric', field: 'bar', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index 88b941b2bb7c9..3bc91b91ed3d6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -13,6 +13,7 @@ import { buildExpression, buildExpressionFunction, ExpressionAstExpressionBuilder, + ExpressionAstFunctionBuilder, } from '@kbn/expressions-plugin/public'; import { AggExpressionFunctionArgs } from '@kbn/data-plugin/common'; import { OperationDefinition } from '.'; @@ -195,6 +196,12 @@ export const percentileOperation: OperationDefinition< } }); + const termsFuncs = aggs + .map((agg) => agg.functions[0]) + .filter((func) => func.name === 'aggTerms') as Array< + ExpressionAstFunctionBuilder + >; + // collapse them into a single esAggs expression builder Object.values(percentileExpressionsByArgs).forEach((expressionBuilders) => { if (expressionBuilders.length <= 1) { @@ -224,6 +231,7 @@ export const percentileOperation: OperationDefinition< const percentileToBuilder: Record = {}; for (const builder of expressionBuilders) { const percentile = builder.functions[0].getArgument('percentile')![0] as number; + if (percentile in percentileToBuilder) { // found a duplicate percentile so let's optimize @@ -248,6 +256,13 @@ export const percentileOperation: OperationDefinition< percentileToBuilder[percentile] = builder; aggPercentilesConfig.percents!.push(percentile); } + + // update any terms order-bys + termsFuncs.forEach((func) => { + if (func.getArgument('orderBy')?.[0] === builder.functions[0].getArgument('id')?.[0]) { + func.replaceArgument('orderBy', [`${esAggsColumnId}.${percentile}`]); + } + }); } const multiPercentilesAst = buildExpressionFunction( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts index 703156418b364..c544121a935f5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts @@ -16,11 +16,9 @@ import { getDisallowedTermsMessage, getMultiTermsScriptedFieldErrorMessage, isSortableByColumn, - computeOrderForMultiplePercentiles, } from './helpers'; import { ReferenceBasedIndexPatternColumn } from '../column_types'; import type { PercentileRanksIndexPatternColumn } from '../percentile_ranks'; -import type { PercentileIndexPatternColumn } from '../percentile'; import { MULTI_KEY_VISUAL_SEPARATOR } from './constants'; import { MovingAverageIndexPatternColumn } from '../calculations'; @@ -410,138 +408,6 @@ describe('getDisallowedTermsMessage()', () => { }); }); -describe('computeOrderForMultiplePercentiles()', () => { - it('should return null for no percentile orderColumn', () => { - expect( - computeOrderForMultiplePercentiles( - { - label: 'Percentile rank (1024.5) of bytes', - dataType: 'number', - operationType: 'percentile_rank', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { value: 1024.5 }, - } as PercentileRanksIndexPatternColumn, - getLayer(getStringBasedOperationColumn(), [ - { - label: 'Percentile rank (1024.5) of bytes', - dataType: 'number', - operationType: 'percentile_rank', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { value: 1024.5 }, - } as PercentileRanksIndexPatternColumn, - ]), - ['col1', 'col2'] - ) - ).toBeNull(); - }); - - it('should return null for single percentile', () => { - expect( - computeOrderForMultiplePercentiles( - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - getLayer(getStringBasedOperationColumn(), [ - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - ]), - ['col1', 'col2'] - ) - ).toBeNull(); - }); - - it('should return correct orderBy for multiple percentile on the same field', () => { - expect( - computeOrderForMultiplePercentiles( - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - getLayer(getStringBasedOperationColumn(), [ - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - { - label: 'Percentile 65 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 65 }, - } as PercentileIndexPatternColumn, - ]), - ['col1', 'col2', 'col3'] - ) - ).toBe('1.95'); - }); - - it('should return null for multiple percentile on different field', () => { - expect( - computeOrderForMultiplePercentiles( - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - getLayer(getStringBasedOperationColumn(), [ - { - label: 'Percentile 95 of bytes', - dataType: 'number', - operationType: 'percentile', - sourceField: 'bytes', - isBucketed: false, - scale: 'ratio', - params: { percentile: 95 }, - } as PercentileIndexPatternColumn, - { - label: 'Percentile 65 of geo', - dataType: 'number', - operationType: 'percentile', - sourceField: 'geo', - isBucketed: false, - scale: 'ratio', - params: { percentile: 65 }, - } as PercentileIndexPatternColumn, - ]), - ['col1', 'col2', 'col3'] - ) - ).toBeNull(); - }); -}); - describe('isSortableByColumn()', () => { it('should sort by the given column', () => { expect( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts index 7139a8effd4ca..77d52664c0e54 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts @@ -21,7 +21,6 @@ import type { FiltersIndexPatternColumn } from '..'; import type { TermsIndexPatternColumn } from './types'; import type { LastValueIndexPatternColumn } from '../last_value'; import type { PercentileRanksIndexPatternColumn } from '../percentile_ranks'; -import type { PercentileIndexPatternColumn } from '../percentile'; import type { IndexPatternLayer } from '../../../types'; import { MULTI_KEY_VISUAL_SEPARATOR, supportedTypes } from './constants'; @@ -241,31 +240,6 @@ export function isPercentileRankSortable(column: GenericIndexPatternColumn) { ); } -export function computeOrderForMultiplePercentiles( - column: GenericIndexPatternColumn, - layer: IndexPatternLayer, - orderedColumnIds: string[] -) { - // compute the percentiles orderBy correctly for multiple percentiles - if (column.operationType === 'percentile') { - const percentileColumns = []; - for (const [key, value] of Object.entries(layer.columns)) { - if ( - value.operationType === 'percentile' && - (value as PercentileIndexPatternColumn).sourceField === - (column as PercentileIndexPatternColumn).sourceField - ) { - percentileColumns.push(key); - } - } - if (percentileColumns.length > 1) { - const parentColumn = String(orderedColumnIds.indexOf(percentileColumns[0])); - return `${parentColumn}.${(column as PercentileIndexPatternColumn).params?.percentile}`; - } - } - return null; -} - export function isSortableByColumn(layer: IndexPatternLayer, columnId: string) { const column = layer.columns[columnId]; return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 3f8496d31f678..9017e91bff089 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -49,7 +49,6 @@ import { getFieldsByValidationState, isSortableByColumn, isPercentileRankSortable, - computeOrderForMultiplePercentiles, } from './helpers'; import { DEFAULT_MAX_DOC_COUNT, @@ -266,7 +265,7 @@ export const termsOperation: OperationDefinition< max_doc_count: column.params.orderBy.maxDocCount, }).toAst(); } - let orderBy: string = '_key'; + let orderBy = '_key'; if (column.params?.orderBy.type === 'column') { const orderColumn = layer.columns[column.params.orderBy.columnId]; @@ -275,14 +274,6 @@ export const termsOperation: OperationDefinition< if (!isPercentileRankSortable(orderColumn)) { orderBy = '_key'; } - - const orderByMultiplePercentiles = computeOrderForMultiplePercentiles( - orderColumn, - layer, - orderedColumnIds - ); - - orderBy = orderByMultiplePercentiles ?? orderBy; } // To get more accurate results, we set shard_size to a minimum of 1000 diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 8c3b7f90d57d3..584498189fd06 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -350,49 +350,6 @@ describe('terms', () => { ); }); - it('should reflect correct orderBy for multiple percentiles', () => { - const newLayer = { - ...layer, - columns: { - ...layer.columns, - col2: { - ...layer.columns.col2, - operationType: 'percentile', - params: { - percentile: 95, - }, - }, - col3: { - ...layer.columns.col2, - operationType: 'percentile', - params: { - percentile: 65, - }, - }, - }, - }; - const termsColumn = layer.columns.col1 as TermsIndexPatternColumn; - const esAggsFn = termsOperation.toEsAggsFn( - { - ...termsColumn, - params: { ...termsColumn.params, orderBy: { type: 'column', columnId: 'col3' } }, - }, - 'col1', - {} as IndexPattern, - newLayer, - uiSettingsMock, - ['col1', 'col2', 'col3'] - ); - expect(esAggsFn).toEqual( - expect.objectContaining({ - function: 'aggTerms', - arguments: expect.objectContaining({ - orderBy: ['1.65'], - }), - }) - ); - }); - it('should not enable missing bucket if other bucket is not set', () => { const termsColumn = layer.columns.col1 as TermsIndexPatternColumn; const esAggsFn = termsOperation.toEsAggsFn( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index bf816e5891486..72cb2a2ab729e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -27,6 +27,7 @@ import { DateHistogramIndexPatternColumn, RangeIndexPatternColumn } from './oper import { FormattedIndexPatternColumn } from './operations/definitions/column_types'; import { isColumnFormatted, isColumnOfType } from './operations/definitions/helpers'; import type { IndexPattern, IndexPatternMap } from '../types'; +import { dedupeAggs } from './dedupe_aggs'; export type OriginalColumn = { id: string } & GenericIndexPatternColumn; @@ -40,7 +41,7 @@ declare global { } // esAggs column ID manipulation functions -const extractEsAggId = (id: string) => id.split('.')[0].split('-')[2]; +export const extractAggId = (id: string) => id.split('.')[0].split('-')[2]; const updatePositionIndex = (currentId: string, newIndex: number) => { const [fullId, percentile] = currentId.split('.'); const idParts = fullId.split('-'); @@ -214,10 +215,18 @@ function getExpressionForLayer( ); } - uniq(esAggEntries.map(([_, column]) => column.operationType)).forEach((type) => { - const optimizeAggs = operationDefinitionMap[type].optimizeEsAggs?.bind( - operationDefinitionMap[type] - ); + const allOperations = uniq( + esAggEntries.map(([_, column]) => operationDefinitionMap[column.operationType]) + ); + + // De-duplicate aggs for supported operations + const dedupedResult = dedupeAggs(aggs, esAggsIdMap, aggExpressionToEsAggsIdMap, allOperations); + aggs = dedupedResult.aggs; + esAggsIdMap = dedupedResult.esAggsIdMap; + + // Apply any operation-specific custom optimizations + allOperations.forEach((operation) => { + const optimizeAggs = operation.optimizeEsAggs?.bind(operation); if (optimizeAggs) { const { aggs: newAggs, esAggsIdMap: newIdMap } = optimizeAggs( aggs, @@ -257,7 +266,7 @@ function getExpressionForLayer( const esAggsIds = Object.keys(esAggsIdMap); aggs.forEach((builder) => { const esAggId = builder.functions[0].getArgument('id')?.[0]; - const matchingEsAggColumnIds = esAggsIds.filter((id) => extractEsAggId(id) === esAggId); + const matchingEsAggColumnIds = esAggsIds.filter((id) => extractAggId(id) === esAggId); matchingEsAggColumnIds.forEach((currentId) => { const currentColumn = esAggsIdMap[currentId][0]; From 0edfb0b1f3407d9802069a2ddfddbed5f6597dfd Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 26 Sep 2022 14:34:32 -0500 Subject: [PATCH 039/172] [DOCS] Moves advanced settings page (#141834) --- docs/user/management.asciidoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 908cdc792431c..02261d062e826 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -176,8 +176,6 @@ see the https://www.elastic.co/subscriptions[subscription page]. -- -include::{kib-repo-dir}/management/advanced-options.asciidoc[] - include::{kib-repo-dir}/management/cases/index.asciidoc[] include::{kib-repo-dir}/management/action-types.asciidoc[] @@ -196,6 +194,10 @@ include::security/index.asciidoc[] include::{kib-repo-dir}/spaces/index.asciidoc[] +include::{kib-repo-dir}/management/advanced-options.asciidoc[] + include::{kib-repo-dir}/management/managing-tags.asciidoc[] include::{kib-repo-dir}/management/watcher-ui/index.asciidoc[] + + From c494c0402f6bbd2f8b12fcca572ba96ce541a2f8 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 26 Sep 2022 14:50:28 -0500 Subject: [PATCH 040/172] [stack_functional_integration] remove esArchiver service (#141842) --- .../services/es_archiver.ts | 1 + .../lib/config/schema.ts | 7 ++++ ...onfig.stack_functional_integration_base.js | 9 +++-- .../services/es_archiver.js | 40 ------------------- .../services/index.js | 14 ------- 5 files changed, 14 insertions(+), 57 deletions(-) delete mode 100644 x-pack/test/stack_functional_integration/services/es_archiver.js delete mode 100644 x-pack/test/stack_functional_integration/services/index.js diff --git a/packages/kbn-ftr-common-functional-services/services/es_archiver.ts b/packages/kbn-ftr-common-functional-services/services/es_archiver.ts index 8a81297bf1784..abb0b89544bc1 100644 --- a/packages/kbn-ftr-common-functional-services/services/es_archiver.ts +++ b/packages/kbn-ftr-common-functional-services/services/es_archiver.ts @@ -18,6 +18,7 @@ export function EsArchiverProvider({ getService }: FtrProviderContext): EsArchiv const retry = getService('retry'); const esArchiver = new EsArchiver({ + baseDir: config.get('esArchiver.baseDirectory'), client, log, kbnClient: kibanaServer, diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index ce44dd3cc0496..6d5dc75b8d969 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -260,6 +260,13 @@ export const schema = Joi.object() // definition of apps that work with `common.navigateToApp()` apps: Joi.object().pattern(ID_PATTERN, appUrlPartsSchema()).default(), + // settings for the saved objects svc + esArchiver: Joi.object() + .keys({ + baseDirectory: Joi.string().optional(), + }) + .default(), + // settings for the saved objects svc kbnArchiver: Joi.object() .keys({ diff --git a/x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js b/x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js index 1658bcbf6cd35..00dd89acbc9ef 100644 --- a/x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js +++ b/x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js @@ -12,7 +12,6 @@ import { REPO_ROOT } from '@kbn/utils'; import chalk from 'chalk'; import { esTestConfig, kbnTestConfig } from '@kbn/test'; import { TriggersActionsPageProvider } from '../../functional_with_es_ssl/page_objects/triggers_actions_ui_page'; -import { services } from '../services'; const log = new ToolingLog({ level: 'info', @@ -28,13 +27,14 @@ export default async ({ readConfigFile }) => { const xpackFunctionalConfig = await readConfigFile( require.resolve('../../functional/config.base.js') ); - const externalConf = consumeState(resolve(__dirname, stateFilePath)); + const externalConf = consumeState(resolve(__dirname, stateFilePath)) ?? { + TESTS_LIST: 'alerts', + }; process.env.stack_functional_integration = true; logAll(log); const settings = { ...xpackFunctionalConfig.getAll(), - services, pageObjects: { triggersActionsUI: TriggersActionsPageProvider, ...xpackFunctionalConfig.get('pageObjects'), @@ -53,6 +53,9 @@ export default async ({ readConfigFile }) => { ...xpackFunctionalConfig.get('kbnTestServer'), serverArgs: [...xpackFunctionalConfig.get('kbnTestServer.serverArgs')], }, + esArchiver: { + baseDirectory: INTEGRATION_TEST_ROOT, + }, testFiles: tests(externalConf.TESTS_LIST).map(prepend).map(logTest), // testFiles: ['alerts'].map(prepend).map(logTest), // If we need to do things like disable animations, we can do it in configure_start_kibana.sh, in the provisioner...which lives in the integration-test private repo diff --git a/x-pack/test/stack_functional_integration/services/es_archiver.js b/x-pack/test/stack_functional_integration/services/es_archiver.js deleted file mode 100644 index 821cf72e2c6bc..0000000000000 --- a/x-pack/test/stack_functional_integration/services/es_archiver.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Path from 'path'; - -import { EsArchiver } from '@kbn/es-archiver'; -import { REPO_ROOT } from '@kbn/utils'; - -import { KibanaServer } from '@kbn/ftr-common-functional-services'; - -const INTEGRATION_TEST_ROOT = - process.env.WORKSPACE || Path.resolve(REPO_ROOT, '../integration-test'); - -export function EsArchiverProvider({ getService }) { - const config = getService('config'); - const client = getService('es'); - const log = getService('log'); - const kibanaServer = getService('kibanaServer'); - const retry = getService('retry'); - - const esArchiver = new EsArchiver({ - baseDir: INTEGRATION_TEST_ROOT, - client, - log, - kbnClient: kibanaServer, - }); - - KibanaServer.extendEsArchiver({ - esArchiver, - kibanaServer, - retry, - defaults: config.get('uiSettings.defaults'), - }); - - return esArchiver; -} diff --git a/x-pack/test/stack_functional_integration/services/index.js b/x-pack/test/stack_functional_integration/services/index.js deleted file mode 100644 index e311dd8b38f7e..0000000000000 --- a/x-pack/test/stack_functional_integration/services/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { services as xpackFunctionalServices } from '../../functional/services'; -import { EsArchiverProvider } from './es_archiver'; - -export const services = { - ...xpackFunctionalServices, - esArchiver: EsArchiverProvider, -}; From ab06783505956cca82ead6708bd00c2b078f341c Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 26 Sep 2022 12:52:17 -0700 Subject: [PATCH 041/172] Update CODEOWNERS for app-services test files (#141841) --- .github/CODEOWNERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 51323cee4a112..480acad008f00 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -84,10 +84,12 @@ /x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-services /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-services /x-pack/plugins/runtime_fields @elastic/kibana-app-services -/x-pack/test/search_sessions_integration/ @elastic/kibana-app-services /src/plugins/dashboard/public/application/embeddable/viewport/print_media @elastic/kibana-app-services x-pack/plugins/files @elastic/kibana-app-services x-pack/examples/files_example @elastic/kibana-app-services +/x-pack/test/search_sessions_integration/ @elastic/kibana-app-services +/test/plugin_functional/test_suites/panel_actions @elastic/kibana-app-services +/test/plugin_functional/test_suites/data_plugin @elastic/kibana-app-services ### Observability Plugins From cedbf8076f570133a39b1469e346541de0a1d142 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 26 Sep 2022 17:00:46 -0400 Subject: [PATCH 042/172] [RAM] rmv public validation around our search strategy for alerts (#141850) * rmv public validation * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../search_strategy/search_strategy.test.ts | 48 +------------------ .../server/search_strategy/search_strategy.ts | 9 ---- .../tests/basic/search_strategy.ts | 18 ------- 3 files changed, 1 insertion(+), 74 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts index 1b32f688ee8c0..ffcf973f028a2 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts @@ -8,11 +8,7 @@ import { of } from 'rxjs'; import { merge } from 'lodash'; import { loggerMock } from '@kbn/logging-mocks'; import { AlertConsumers } from '@kbn/rule-data-utils'; -import { - ruleRegistrySearchStrategyProvider, - EMPTY_RESPONSE, - RULE_SEARCH_STRATEGY_NAME, -} from './search_strategy'; +import { ruleRegistrySearchStrategyProvider, EMPTY_RESPONSE } from './search_strategy'; import { ruleDataServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { SearchStrategyDependencies } from '@kbn/data-plugin/server'; @@ -385,48 +381,6 @@ describe('ruleRegistrySearchStrategyProvider()', () => { ).toStrictEqual([{ test: { order: 'desc' } }]); }); - it('should reject, to the best of our ability, public requests', async () => { - (getIsKibanaRequest as jest.Mock).mockImplementation(() => { - return false; - }); - const request: RuleRegistrySearchRequest = { - featureIds: [AlertConsumers.LOGS], - sort: [ - { - test: { - order: 'desc', - }, - }, - ], - }; - const options = {}; - const deps = { - request: {}, - }; - - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); - - let err = null; - try { - await strategy - .search(request, options, deps as unknown as SearchStrategyDependencies) - .toPromise(); - } catch (e) { - err = e; - } - expect(err).not.toBeNull(); - expect(err.message).toBe( - `The ${RULE_SEARCH_STRATEGY_NAME} search strategy is currently only available for internal use.` - ); - }); - it('passes the query ids if provided', async () => { const request: RuleRegistrySearchRequest = { featureIds: [AlertConsumers.SIEM], diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts index 6b9ac15cd8d6a..49a6439ef9b59 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts @@ -5,7 +5,6 @@ * 2.0. */ import { map, mergeMap, catchError } from 'rxjs/operators'; -import Boom from '@hapi/boom'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Logger } from '@kbn/core/server'; import { from, of } from 'rxjs'; @@ -25,7 +24,6 @@ import { Dataset } from '../rule_data_plugin_service/index_options'; import { MAX_ALERT_SEARCH_SIZE } from '../../common/constants'; import { AlertAuditAction, alertAuditEvent } from '..'; import { getSpacesFilter, getAuthzFilter } from '../lib'; -import { getIsKibanaRequest } from '../lib/get_is_kibana_request'; export const EMPTY_RESPONSE: RuleRegistrySearchResponse = { rawResponse: {} as RuleRegistrySearchResponse['rawResponse'], @@ -47,13 +45,6 @@ export const ruleRegistrySearchStrategyProvider = ( const requestUserEs = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); return { search: (request, options, deps) => { - // We want to ensure this request came from our UI. We can't really do this - // but we have a best effort we can try - if (!getIsKibanaRequest(deps.request.headers)) { - throw Boom.notFound( - `The ${RULE_SEARCH_STRATEGY_NAME} search strategy is currently only available for internal use.` - ); - } // SIEM uses RBAC fields in their alerts but also utilizes ES DLS which // is different than every other solution so we need to special case // those requests. diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index a04899d68d585..11982a8b51425 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -110,24 +110,6 @@ export default ({ getService }: FtrProviderContext) => { const second = result.rawResponse.hits.hits[1].fields?.['kibana.alert.evaluation.value']; expect(first > second).to.be(true); }); - - it('should reject public requests', async () => { - const result = await secureBsearch.send({ - supertestWithoutAuth, - auth: { - username: logsOnlySpacesAll.username, - password: logsOnlySpacesAll.password, - }, - options: { - featureIds: [AlertConsumers.LOGS], - }, - strategy: 'privateRuleRegistryAlertsSearchStrategy', - }); - expect(result.statusCode).to.be(500); - expect(result.message).to.be( - `The privateRuleRegistryAlertsSearchStrategy search strategy is currently only available for internal use.` - ); - }); }); describe('siem', () => { From 605c958297abe950588658650bd321804ac38acc Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 26 Sep 2022 14:06:00 -0700 Subject: [PATCH 043/172] [DOCS] Add v3 open API spec for ml sync (#141554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: István Zoltán Szabó --- x-pack/plugins/ml/common/openapi/README.md | 2 + .../plugins/ml/common/openapi/ml_apis_v3.yaml | 169 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml diff --git a/x-pack/plugins/ml/common/openapi/README.md b/x-pack/plugins/ml/common/openapi/README.md index 256b3be6a8cc4..450f95cd52071 100644 --- a/x-pack/plugins/ml/common/openapi/README.md +++ b/x-pack/plugins/ml/common/openapi/README.md @@ -5,6 +5,7 @@ The current self-contained spec file can be used for online tools like those fou A guide about the openApi specification can be found at [https://swagger.io/docs/specification/about/](https://swagger.io/docs/specification/about/). The `ml_apis_v2.json` file uses OpenAPI Specification Version 2.0. +The `ml_apis_v3.yaml` file uses OpenAPI Specification Version 3.0.1. ## Tools @@ -12,4 +13,5 @@ It is possible to validate the docs before bundling them by running the followin ``` npx swagger-cli validate ml_apis_v2.json +npx swagger-cli validate ml_apis_v3.yaml ``` diff --git a/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml b/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml new file mode 100644 index 0000000000000..938b3c312c0c4 --- /dev/null +++ b/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml @@ -0,0 +1,169 @@ +openapi: 3.0.1 +info: + title: Machine learning APIs + description: Kibana APIs for the machine learning feature + version: "1.0.1" + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: ml + description: Machine learning +servers: + - url: https://localhost:5601/ +paths: + /s/{spaceId}/api/ml/saved_objects/sync: + get: + summary: Synchronizes Kibana saved objects for machine learning jobs and trained models. + description: > + You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically thereafter. + operationId: ml-sync + tags: + - ml + parameters: + - $ref: '#/components/parameters/spaceParam' + - $ref: '#/components/parameters/simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + $ref: '#/components/schemas/mlSyncResponse' + examples: + syncExample: + $ref: '#/components/examples/mlSyncExample' +components: + parameters: + spaceParam: + in: path + name: spaceId + description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. + required: true + schema: + type: string + simulateParam: + in: query + name: simulate + description: When true, simulates the synchronization by returning only the list of actions that would be performed. + required: false + schema: + type: boolean + example: 'true' + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey + schemas: + mlSyncResponseSuccess: + type: boolean + description: The success or failure of the synchronization. + mlSyncResponseAnomalyDetectors: + type: object + title: Sync API response for anomaly detection jobs + description: The sync machine learning saved objects API response contains this object when there are anomaly detection jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/mlSyncResponseSuccess' + mlSyncResponseDatafeeds: + type: object + title: Sync API response for datafeeds + description: The sync machine learning saved objects API response contains this object when there are datafeeds affected by the synchronization. There is an object for each relevant datafeed, which contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/mlSyncResponseSuccess' + mlSyncResponseDataFrameAnalytics: + type: object + title: Sync API response for data frame analytics jobs + description: The sync machine learning saved objects API response contains this object when there are data frame analytics jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/mlSyncResponseSuccess' + mlSyncResponseSavedObjectsCreated: + type: object + title: Sync API response for created saved objects + description: If saved objects are missing for machine learning jobs or trained models, they are created when you run the sync machine learning saved objects API. + properties: + anomaly-detector: + type: object + description: If saved objects are missing for anomaly detection jobs, they are created. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseAnomalyDetectors' + data-frame-analytics: + type: object + description: If saved objects are missing for data frame analytics jobs, they are created. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseDataFrameAnalytics' + trained-model: + type: object + description: If saved objects are missing for trained models, they are created. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseTrainedModels' + mlSyncResponseSavedObjectsDeleted: + type: object + title: Sync API response for deleted saved objects + description: If saved objects exist for machine learning jobs or trained models that no longer exist, they are deleted when you run the sync machine learning saved objects API. + properties: + anomaly-detector: + type: object + description: If there are saved objects exist for nonexistent anomaly detection jobs, they are deleted. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseAnomalyDetectors' + data-frame-analytics: + type: object + description: If there are saved objects exist for nonexistent data frame analytics jobs, they are deleted. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseDataFrameAnalytics' + trained-model: + type: object + description: If there are saved objects exist for nonexistent trained models, they are deleted. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseTrainedModels' + mlSyncResponseTrainedModels: + type: object + title: Sync API response for trained models + description: The sync machine learning saved objects API response contains this object when there are trained models affected by the synchronization. There is an object for each relevant trained model, which contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/mlSyncResponseSuccess' + mlSyncResponse: + type: object + title: Sync API response + properties: + datafeedsAdded: + type: object + description: If a saved object for an anomaly detection job is missing a datafeed identifier, it is added when you run the sync machine learning saved objects API. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseDatafeeds' + datafeedsRemoved: + type: object + description: If a saved object for an anomaly detection job references a datafeed that no longer exists, it is deleted when you run the sync machine learning saved objects API. + additionalProperties: + $ref: '#/components/schemas/mlSyncResponseDatafeeds' + savedObjectsCreated: + $ref: '#/components/schemas/mlSyncResponseSavedObjectsCreated' + savedObjectsDeleted: + $ref: '#/components/schemas/mlSyncResponseSavedObjectsDeleted' + examples: + mlSyncExample: + summary: Two anomaly detection jobs required synchronization in this example. + value: + { + "savedObjectsCreated": { + "anomaly_detector": { + "myjob1": { "success":true }, + "myjob2":{ "success":true } + } + }, + "savedObjectsDeleted": {}, + "datafeedsAdded":{}, + "datafeedsRemoved":{} + } +security: + - basicAuth: [ ] + - ApiKeyAuth: [ ] \ No newline at end of file From d36cde0d3b0ed1dd03fdca007948de8e8a4ffb85 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Mon, 26 Sep 2022 20:12:14 -0500 Subject: [PATCH 044/172] [Enterprise Search] update ml pipeline target field help text (#141863) Updated the help text for the target field to tell the user the field will be defaults to "ml.inference.pipeline-name" if not set. --- .../pipelines/ml_inference/configure_pipeline.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx index 0d8c5b7c71f1f..199d62f914f03 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -182,7 +182,13 @@ export const ConfigurePipeline: React.FC = () => { formErrors.destinationField === undefined && i18n.translate( 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.destinationField.helpText', - { defaultMessage: 'Your field name will be prefixed with "ml.inference."' } + { + defaultMessage: + 'Your field name will be prefixed with "ml.inference.", if not set it will be defaulted to "ml.inference.{pipelineName}"', + values: { + pipelineName, + }, + } ) } error={formErrors.destinationField} From d09e40fa250109d7bcc1aca3834304502e18a8d4 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Mon, 26 Sep 2022 21:23:07 -0400 Subject: [PATCH 045/172] [Detection Rules] Add 8.5 rules (#141839) --- .../collection_email_powershell_exchange_mailbox.json | 3 ++- .../prepackaged_rules/collection_posh_audio_capture.json | 5 +++-- .../prepackaged_rules/collection_posh_keylogger.json | 5 +++-- .../prepackaged_rules/collection_posh_screen_grabber.json | 5 +++-- .../prepackaged_rules/collection_winrar_encryption.json | 3 ++- .../command_and_control_certutil_network_connection.json | 7 ++++--- .../command_and_control_common_webservices.json | 7 ++++--- .../command_and_control_dns_tunneling_nslookup.json | 5 +++-- ...ommand_and_control_port_forwarding_added_registry.json | 5 +++-- .../command_and_control_rdp_tunnel_plink.json | 3 ++- ...nd_and_control_remote_file_copy_desktopimgdownldr.json | 5 +++-- .../command_and_control_remote_file_copy_mpcmdrun.json | 7 ++++--- .../command_and_control_remote_file_copy_powershell.json | 7 ++++--- .../command_and_control_remote_file_copy_scripts.json | 7 ++++--- ...command_and_control_sunburst_c2_activity_detected.json | 5 +++-- .../command_and_control_teamviewer_remote_file_copy.json | 7 ++++--- ...credential_access_aws_iam_assume_role_brute_force.json | 5 +++-- .../credential_access_bruteforce_passowrd_guessing.json | 3 ++- .../credential_access_cmdline_dump_tool.json | 3 ++- .../credential_access_credential_dumping_msbuild.json | 7 ++++--- .../credential_access_dcsync_replication_rights.json | 5 +++-- .../credential_access_disable_kerberos_preauth.json | 5 +++-- .../credential_access_dump_registry_hives.json | 3 ++- .../credential_access_iam_user_addition_to_group.json | 5 +++-- .../credential_access_kerberoasting_unusual_process.json | 7 ++++--- .../credential_access_lsass_memdump_handle_access.json | 7 ++++--- .../credential_access_mimikatz_memssp_default_logs.json | 5 +++-- .../credential_access_mimikatz_powershell_module.json | 5 +++-- .../credential_access_mod_wdigest_security_provider.json | 7 ++++--- .../credential_access_moving_registry_hive_via_smb.json | 5 +++-- .../credential_access_posh_minidump.json | 5 +++-- .../credential_access_posh_request_ticket.json | 5 +++-- .../credential_access_potential_linux_ssh_bruteforce.json | 3 ++- ...ential_access_potential_linux_ssh_bruteforce_root.json | 1 + ...credential_access_potential_macos_ssh_bruteforce.json} | 4 ++-- .../credential_access_remote_sam_secretsdump.json | 5 +++-- .../credential_access_secretsmanager_getsecretvalue.json | 5 +++-- ...cess_seenabledelegationprivilege_assigned_to_user.json | 5 +++-- .../credential_access_spn_attribute_modified.json | 5 +++-- ...access_suspicious_winreg_access_via_sebackup_priv.json | 5 +++-- ...ntial_access_symbolic_link_to_shadow_copy_created.json | 5 +++-- .../defense_evasion_amsienable_key_mod.json | 7 ++++--- .../defense_evasion_azure_service_principal_addition.json | 5 +++-- .../defense_evasion_clearing_windows_console_history.json | 5 +++-- .../defense_evasion_clearing_windows_event_logs.json | 3 ++- .../defense_evasion_clearing_windows_security_logs.json | 5 +++-- .../defense_evasion_cloudtrail_logging_deleted.json | 5 +++-- .../defense_evasion_cloudtrail_logging_suspended.json | 5 +++-- .../defense_evasion_cloudwatch_alarm_deletion.json | 5 +++-- .../defense_evasion_config_service_rule_deletion.json | 5 +++-- .../defense_evasion_create_mod_root_certificate.json | 7 ++++--- .../defense_evasion_defender_disabled_via_registry.json | 5 +++-- ...defense_evasion_defender_exclusion_via_powershell.json | 7 ++++--- .../defense_evasion_disable_posh_scriptblocklogging.json | 5 +++-- ...evasion_disable_windows_firewall_rules_with_netsh.json | 3 ++- ...nse_evasion_disabling_windows_defender_powershell.json | 5 +++-- .../defense_evasion_disabling_windows_logs.json | 3 ++- .../defense_evasion_ec2_flow_log_deletion.json | 5 +++-- .../defense_evasion_enable_inbound_rdp_with_netsh.json | 3 ++- ...fense_evasion_enable_network_discovery_with_netsh.json | 7 ++++--- ...e_evasion_execution_msbuild_started_by_office_app.json | 5 +++-- .../defense_evasion_microsoft_defender_tampering.json | 5 +++-- .../defense_evasion_ms_office_suspicious_regmod.json | 7 ++++--- .../defense_evasion_posh_assembly_load.json | 7 ++++--- .../defense_evasion_posh_compressed.json | 7 ++++--- .../defense_evasion_posh_process_injection.json | 5 +++-- ...ense_evasion_powershell_windows_firewall_disabled.json | 7 ++++--- ..._evasion_suspicious_process_access_direct_syscall.json | 7 ++++--- ...nse_evasion_suspicious_process_creation_calltrace.json | 5 +++-- ...asion_system_critical_proc_abnormal_file_activity.json | 7 ++++--- .../defense_evasion_unusual_ads_file_creation.json | 4 ++-- ...e_evasion_unusual_network_connection_via_rundll32.json | 3 ++- ...efense_evasion_unusual_process_network_connection.json | 5 +++-- .../defense_evasion_workfolders_control_execution.json | 3 ++- .../discovery_adfind_command_activity.json | 3 ++- .../rules/prepackaged_rules/discovery_admin_recon.json | 5 +++-- .../discovery_command_system_account.json | 3 ++- .../rules/prepackaged_rules/discovery_net_view.json | 5 +++-- .../prepackaged_rules/discovery_peripheral_device.json | 5 +++-- .../discovery_posh_invoke_sharefinder.json | 2 +- .../discovery_posh_suspicious_api_functions.json | 5 +++-- .../discovery_post_exploitation_external_ip_lookup.json | 5 +++-- .../discovery_privileged_localgroup_membership.json | 7 ++++--- ...iscovery_remote_system_discovery_commands_windows.json | 5 +++-- .../discovery_security_software_grep.json | 4 ++-- .../discovery_security_software_wmic.json | 5 +++-- .../discovery_whoami_command_activity.json | 5 +++-- .../execution_abnormal_process_id_file_created.json | 4 ++-- .../execution_command_shell_started_by_svchost.json | 7 ++++--- .../execution_from_unusual_path_cmdline.json | 2 +- .../execution_linux_netcat_network_connection.json | 3 ++- .../execution_ms_office_written_file.json | 5 +++-- .../prepackaged_rules/execution_pdf_written_file.json | 5 +++-- .../execution_posh_portable_executable.json | 7 ++++--- .../rules/prepackaged_rules/execution_posh_psreflect.json | 7 ++++--- .../execution_psexec_lateral_movement_command.json | 5 +++-- .../execution_revershell_via_shell_cmd.json | 4 ++-- .../execution_suspicious_jar_child_process.json | 4 ++-- .../execution_suspicious_pdf_reader.json | 5 +++-- .../execution_suspicious_powershell_imgload.json | 7 ++++--- .../execution_via_hidden_shell_conhost.json | 5 +++-- .../execution_via_xp_cmdshell_mssql_stored_procedure.json | 5 ++++- .../exfiltration_ec2_snapshot_change_activity.json | 5 +++-- .../prepackaged_rules/impact_backup_file_deletion.json | 5 +++-- .../impact_cloudtrail_logging_updated.json | 5 +++-- .../impact_cloudwatch_log_group_deletion.json | 5 +++-- .../impact_cloudwatch_log_stream_deletion.json | 5 +++-- .../impact_deleting_backup_catalogs_with_wbadmin.json | 3 ++- .../impact_google_workspace_mfa_enforcement_disabled.json | 5 +++-- .../prepackaged_rules/impact_hosts_file_modified.json | 4 ++-- .../impact_iam_deactivate_mfa_device.json | 5 +++-- .../impact_modification_of_boot_config.json | 3 ++- .../prepackaged_rules/impact_process_kill_threshold.json | 5 +++-- .../impact_stop_process_service_threshold.json | 5 +++-- ...lume_shadow_copy_deletion_or_resized_via_vssadmin.json | 5 +++-- ...impact_volume_shadow_copy_deletion_via_powershell.json | 5 +++-- .../impact_volume_shadow_copy_deletion_via_wmic.json | 5 +++-- .../lib/detection_engine/rules/prepackaged_rules/index.ts | 2 +- ...al_access_azure_active_directory_high_risk_signin.json | 5 +++-- ...ve_directory_high_risk_signin_atrisk_or_confirmed.json | 5 +++-- ...l_access_azure_active_directory_powershell_signin.json | 5 +++-- ...ent_grant_attack_via_azure_registered_application.json | 5 +++-- .../initial_access_console_login_root.json | 5 +++-- .../initial_access_script_executing_powershell.json | 5 +++-- ...initial_access_suspicious_ms_office_child_process.json | 5 +++-- ...nitial_access_suspicious_ms_outlook_child_process.json | 5 +++-- .../initial_access_unusual_dns_service_children.json | 7 ++++--- .../initial_access_via_system_manager.json | 5 +++-- .../lateral_movement_direct_outbound_smb_connection.json | 7 ++++--- .../lateral_movement_dns_server_overflow.json | 5 +++-- .../lateral_movement_executable_tool_transfer_smb.json | 7 ++++--- ...teral_movement_execution_via_file_shares_sequence.json | 1 + .../lateral_movement_rdp_enabled_registry.json | 5 +++-- .../lateral_movement_remote_services.json | 4 ++++ .../lateral_movement_scheduled_task_target.json | 5 +++-- .../ml_cloudtrail_error_message_spike.json | 5 +++-- .../prepackaged_rules/ml_cloudtrail_rare_error_code.json | 5 +++-- .../ml_cloudtrail_rare_method_by_city.json | 5 +++-- .../ml_cloudtrail_rare_method_by_country.json | 5 +++-- .../ml_cloudtrail_rare_method_by_user.json | 5 +++-- .../persistence_adobe_hijack_persistence.json | 7 ++++--- ...zure_privileged_identity_management_role_modified.json | 5 +++-- .../persistence_dontexpirepasswd_account.json | 5 +++-- ...persistence_evasion_hidden_local_account_creation.json | 5 +++-- ...ce_evasion_registry_startup_shell_folder_modified.json | 7 ++++--- .../persistence_gpo_schtask_service_creation.json | 4 ++-- .../persistence_mfa_disabled_for_azure_user.json | 5 +++-- .../persistence_ml_rare_process_by_host_windows.json | 5 +++-- ...stence_priv_escalation_via_accessibility_features.json | 5 +++-- .../persistence_run_key_and_startup_broad.json | 3 ++- .../persistence_sdprop_exclusion_dsheuristics.json | 5 +++-- .../persistence_shell_activity_by_web_server.json | 3 ++- ...startup_folder_file_written_by_suspicious_process.json | 7 ++++--- ...e_startup_folder_file_written_by_unsigned_process.json | 5 +++-- .../persistence_startup_folder_scripts.json | 7 ++++--- .../persistence_suspicious_com_hijack_registry.json | 7 ++++--- .../persistence_system_shells_via_services.json | 3 ++- ...istence_user_account_added_to_privileged_group_ad.json | 5 +++-- .../persistence_user_account_creation.json | 3 ++- ...ersistence_via_update_orchestrator_service_hijack.json | 4 ++-- .../prepackaged_rules/persistence_webshell_detection.json | 6 +++--- .../privilege_escalation_disable_uac_registry.json | 4 ++-- .../privilege_escalation_group_policy_iniscript.json | 5 +++-- ...ivilege_escalation_group_policy_privileged_groups.json | 5 +++-- .../privilege_escalation_group_policy_scheduled_task.json | 5 +++-- .../privilege_escalation_installertakeover.json | 7 ++++--- .../privilege_escalation_persistence_phantom_dll.json | 4 ++-- ...ilege_escalation_printspooler_suspicious_spl_file.json | 8 ++++---- .../privilege_escalation_root_login_without_mfa.json | 5 +++-- .../privilege_escalation_uac_bypass_event_viewer.json | 5 +++-- .../privilege_escalation_uac_bypass_mock_windir.json | 5 +++-- .../privilege_escalation_uac_bypass_winfw_mmc_hijack.json | 5 +++-- ...ilege_escalation_unusual_parentchild_relationship.json | 5 +++-- .../privilege_escalation_updateassumerolepolicy.json | 5 +++-- .../rules/prepackaged_rules/threat_intel_filebeat8x.json | 5 +++-- .../threat_intel_fleet_integrations.json | 5 +++-- 176 files changed, 522 insertions(+), 357 deletions(-) rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{credential_access_potential_ssh_bruteforce.json => credential_access_potential_macos_ssh_bruteforce.json} (95%) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json index 3a82c828d637c..d891ad96a57c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json @@ -47,7 +47,8 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Collection", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json index 3721078880a42..2a3519f83bd29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json @@ -37,7 +37,8 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Collection", + "has_guide" ], "threat": [ { @@ -80,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json index db9abf0b19506..a1d437332842a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Collection", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json index 1b041659b488c..cec0fd82efb05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json @@ -37,7 +37,8 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Collection", + "has_guide" ], "threat": [ { @@ -80,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json index 6aeca04a6d9d7..0c69fba974b39 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json @@ -53,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Collection", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json index c437fde5ea4bb..827659f95566f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Certutil", - "note": "## Triage and analysis\n\n### Investigating Network Connection via Certutil\n\nAttackers can abuse `certutil.exe` to download malware, offensive security tools, and certificates from external sources\nin order to take the next steps in a compromised environment.\n\nThis rule looks for network events where `certutil.exe` contacts IP ranges other than the ones specified in\n[IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate if the downloaded file was executed.\n- Determine the context in which `certutil.exe` and the file were run.\n- Retrieve the downloaded file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If trusted software uses this command and the triage has not identified\nanything suspicious, this alert can be closed as a false positive.\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Network Connection via Certutil\n\nAttackers can abuse `certutil.exe` to download malware, offensive security tools, and certificates from external sources\nin order to take the next steps in a compromised environment.\n\nThis rule looks for network events where `certutil.exe` contacts IP ranges other than the ones specified in\n[IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate if the downloaded file was executed.\n- Determine the context in which `certutil.exe` and the file were run.\n- Retrieve the downloaded file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If trusted software uses this command and the triage has not identified\nanything suspicious, this alert can be closed as a false positive.\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by process.entity_id\n [process where process.name : \"certutil.exe\" and event.type == \"start\"]\n [network where process.name : \"certutil.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", "references": [ "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml", @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -68,5 +69,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json index f528501b0da5a..49562601e3372 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Connection to Commonly Abused Web Services", - "note": "## Triage and analysis\n\n### Investigating Connection to Commonly Abused Web Services\n\nAdversaries may use an existing, legitimate external Web service as a means for relaying data to/from a compromised\nsystem. Popular websites and social media acting as a mechanism for C2 may give a significant amount of cover due to the\nlikelihood that hosts within a network are already communicating with them prior to a compromise.\n\nThis rule looks for processes outside known legitimate program locations communicating with a list of services that can\nbe abused for exfiltration or command and control.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Verify whether the digital signature exists in the executable.\n- Identify the operation type (upload, download, tunneling, etc.).\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives because it detects communication with legitimate services. Noisy\nfalse positives can be added as exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Connection to Commonly Abused Web Services\n\nAdversaries may use an existing, legitimate external Web service as a means for relaying data to/from a compromised\nsystem. Popular websites and social media acting as a mechanism for C2 may give a significant amount of cover due to the\nlikelihood that hosts within a network are already communicating with them prior to a compromise.\n\nThis rule looks for processes outside known legitimate program locations communicating with a list of services that can\nbe abused for exfiltration or command and control.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Verify whether the digital signature exists in the executable.\n- Identify the operation type (upload, download, tunneling, etc.).\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives because it detects communication with legitimate services. Noisy\nfalse positives can be added as exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"raw.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\",\n \"rawcdn.githack.*\",\n \"paste.nrecom.net\",\n \"zerobin.net\",\n \"controlc.com\",\n \"requestbin.net\",\n \"cdn.discordapp.com\",\n \"discordapp.com\",\n \"discord.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\",\n \"?:\\\\Windows\\\\system32\\\\mobsync.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\mobsync.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Discord\\\\app-*\\\\Discord.exe\"\n )\n", "required_fields": [ { @@ -47,7 +47,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -95,5 +96,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json index 55eb5c136eb56..89945ae91c6da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json @@ -47,7 +47,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -80,5 +81,5 @@ "value": 15 }, "type": "threshold", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json index 20a6904c44bc0..7898871bb9b3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json @@ -33,7 +33,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -54,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json index d7d8d9b394813..a9af94cec09d3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json index a175e4f84ead1..c49f3cd7efefb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via Desktopimgdownldr Utility", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via Desktopimgdownldr Utility\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `Desktopimgdownldr.exe` utility is used to to configure lockscreen/desktop image, and can be abused with the\n`lockscreenurl` argument to download remote files and tools, this rule looks for this behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check the reputation of the domain or IP address used to host the downloaded file or if the user downloaded the file\nfrom an internal system.\n- Retrieve the file and determine if it is malicious:\n - Identify the file type.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unusual but can be done by administrators. Benign true positives (B-TPs) can be added as exceptions\nif necessary.\n- Analysts can dismiss the alert if the downloaded file is a legitimate image.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via Desktopimgdownldr Utility\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `Desktopimgdownldr.exe` utility is used to to configure lockscreen/desktop image, and can be abused with the\n`lockscreenurl` argument to download remote files and tools, this rule looks for this behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check the reputation of the domain or IP address used to host the downloaded file or if the user downloaded the file\nfrom an internal system.\n- Retrieve the file and determine if it is malicious:\n - Identify the file type.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unusual but can be done by administrators. Benign true positives (B-TPs) can be added as exceptions\nif necessary.\n- Analysts can dismiss the alert if the downloaded file is a legitimate image.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name : \"desktopimgdownldr.exe\" or process.pe.original_file_name == \"desktopimgdownldr.exe\") and\n process.args : \"/lockscreenurl:http*\"\n", "references": [ "https://labs.sentinelone.com/living-off-windows-land-a-new-native-file-downldr/" @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json index 2ffafee4416b6..229755609cc38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via MpCmdRun", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via MpCmdRun\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `MpCmdRun.exe` is a command-line tool part of Windows Defender and is used to manage various Microsoft Windows\nDefender Antivirus settings and perform certain tasks. It can also be abused by attackers to download remote files,\nincluding malware and offensive tooling. This rule looks for the patterns used to perform downloads using the utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via MpCmdRun\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `MpCmdRun.exe` is a command-line tool part of Windows Defender and is used to manage various Microsoft Windows\nDefender Antivirus settings and perform certain tasks. It can also be abused by attackers to download remote files,\nincluding malware and offensive tooling. This rule looks for the patterns used to perform downloads using the utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name : \"MpCmdRun.exe\" or process.pe.original_file_name == \"MpCmdRun.exe\") and\n process.args : \"-DownloadFile\" and process.args : \"-url\" and process.args : \"-path\"\n", "references": [ "https://twitter.com/mohammadaskar2/status/1301263551638761477", @@ -49,7 +49,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json index 1b42352777aef..c6a7c798235d8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via PowerShell\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nPowerShell is one of system administrators' main tools for automation, report routines, and other tasks. This makes it\navailable for use in various environments and creates an attractive way for attackers to execute code and perform\nactions. This rule correlates network and file events to detect downloads of executable and script files performed using\nPowerShell.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators can use PowerShell legitimately to download executable and script files. Analysts can dismiss the alert\nif the Administrator is aware of the activity and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via PowerShell\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nPowerShell is one of system administrators' main tools for automation, report routines, and other tasks. This makes it\navailable for use in various environments and creates an attractive way for attackers to execute code and perform\nactions. This rule correlates network and file events to detect downloads of executable and script files performed using\nPowerShell.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators can use PowerShell legitimately to download executable and script files. Analysts can dismiss the alert\nif the Administrator is aware of the activity and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id, process.entity_id with maxspan=30s\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and network.protocol == \"dns\" and\n not dns.question.name : (\"localhost\", \"*.microsoft.com\", \"*.azureedge.net\", \"*.powershellgallery.com\", \"*.windowsupdate.com\", \"metadata.google.internal\") and\n not user.domain : \"NT AUTHORITY\"]\n [file where process.name : \"powershell.exe\" and event.type == \"creation\" and file.extension : (\"exe\", \"dll\", \"ps1\", \"bat\") and\n not file.name : \"__PSScriptPolicy*.ps1\"]\n", "required_fields": [ { @@ -69,7 +69,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -111,5 +112,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json index 35ac4855cdab5..3ab79126bec9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via Script Interpreter", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via Script Interpreter\n\nThe Windows Script Host (WSH) is a Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for DLLs and executables downloaded using `cscript.exe` or `wscript.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the script file and the executable involved and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the script to determine if malicious capabilities are present.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via Script Interpreter\n\nThe Windows Script Host (WSH) is a Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for DLLs and executables downloaded using `cscript.exe` or `wscript.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the script file and the executable involved and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the script to determine if malicious capabilities are present.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id, process.entity_id\n [network where process.name : (\"wscript.exe\", \"cscript.exe\") and network.protocol != \"dns\" and\n network.direction : (\"outgoing\", \"egress\") and network.type == \"ipv4\" and destination.ip != \"127.0.0.1\"\n ]\n [file where event.type == \"creation\" and file.extension : (\"exe\", \"dll\")]\n", "required_fields": [ { @@ -69,7 +69,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -89,5 +90,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json index 8a6bf64b5edb7..45c95688fc9b4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "SUNBURST Command and Control Activity", - "note": "## Triage and analysis\n\n### Investigating SUNBURST Command and Control Activity\n\nSUNBURST is a trojanized version of a digitally signed SolarWinds Orion plugin called\nSolarWinds.Orion.Core.BusinessLayer.dll. The plugin contains a backdoor that communicates via HTTP to third-party\nservers. After an initial dormant period of up to two weeks, SUNBURST may retrieve and execute commands that instruct\nthe backdoor to transfer files, execute files, profile the system, reboot the system, and disable system services.\nThe malware's network traffic attempts to blend in with legitimate SolarWinds activity by imitating the Orion\nImprovement Program (OIP) protocol, and the malware stores persistent state data within legitimate plugin configuration files. The\nbackdoor uses multiple obfuscated blocklists to identify processes, services, and drivers associated with forensic and\nanti-virus tools.\n\nMore details on SUNBURST can be found on the [Mandiant Report](https://www.mandiant.com/resources/sunburst-additional-technical-details).\n\nThis rule identifies suspicious network connections that attempt to blend in with legitimate SolarWinds activity\nby imitating the Orion Improvement Program (OIP) protocol behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the executable involved:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate the network traffic.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the environment at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Upgrade SolarWinds systems to the latest version to eradicate the chance of reinfection by abusing the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating SUNBURST Command and Control Activity\n\nSUNBURST is a trojanized version of a digitally signed SolarWinds Orion plugin called\nSolarWinds.Orion.Core.BusinessLayer.dll. The plugin contains a backdoor that communicates via HTTP to third-party\nservers. After an initial dormant period of up to two weeks, SUNBURST may retrieve and execute commands that instruct\nthe backdoor to transfer files, execute files, profile the system, reboot the system, and disable system services.\nThe malware's network traffic attempts to blend in with legitimate SolarWinds activity by imitating the Orion\nImprovement Program (OIP) protocol, and the malware stores persistent state data within legitimate plugin configuration files. The\nbackdoor uses multiple obfuscated blocklists to identify processes, services, and drivers associated with forensic and\nanti-virus tools.\n\nMore details on SUNBURST can be found on the [Mandiant Report](https://www.mandiant.com/resources/sunburst-additional-technical-details).\n\nThis rule identifies suspicious network connections that attempt to blend in with legitimate SolarWinds activity\nby imitating the Orion Improvement Program (OIP) protocol behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the executable involved:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate the network traffic.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the environment at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Upgrade SolarWinds systems to the latest version to eradicate the chance of reinfection by abusing the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "network where event.type == \"protocol\" and network.protocol == \"http\" and\n process.name : (\"ConfigurationWizard.exe\",\n \"NetFlowService.exe\",\n \"NetflowDatabaseMaintenance.exe\",\n \"SolarWinds.Administration.exe\",\n \"SolarWinds.BusinessLayerHost.exe\",\n \"SolarWinds.BusinessLayerHostx64.exe\",\n \"SolarWinds.Collector.Service.exe\",\n \"SolarwindsDiagnostics.exe\") and\n (\n (\n (http.request.body.content : \"*/swip/Upload.ashx*\" and http.request.body.content : (\"POST*\", \"PUT*\")) or\n (http.request.body.content : (\"*/swip/SystemDescription*\", \"*/swip/Events*\") and http.request.body.content : (\"GET*\", \"HEAD*\"))\n ) and\n not http.request.body.content : \"*solarwinds.com*\"\n )\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html" @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json index 0e82077dd3da8..82e7f299ea8fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Copy via TeamViewer", - "note": "## Triage and analysis\n\n### Investigating Remote File Copy via TeamViewer\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse legitimate utilities to drop these files.\n\nTeamViewer is a remote access and remote control tool used by helpdesks and system administrators to perform various\nsupport activities. It is also frequently used by attackers and scammers to deploy malware interactively and other\nmalicious activities. This rule looks for the TeamViewer process creating files with suspicious extensions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Contact the user to gather information about who and why was conducting the remote access.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether the company uses TeamViewer for the support activities and if there is a support ticket related to this\naccess.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the company relies on TeamViewer to conduct\nremote access and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Remote File Copy via TeamViewer\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse legitimate utilities to drop these files.\n\nTeamViewer is a remote access and remote control tool used by helpdesks and system administrators to perform various\nsupport activities. It is also frequently used by attackers and scammers to deploy malware interactively and other\nmalicious activities. This rule looks for the TeamViewer process creating files with suspicious extensions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Contact the user to gather information about who and why was conducting the remote access.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether the company uses TeamViewer for the support activities and if there is a support ticket related to this\naccess.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the company relies on TeamViewer to conduct\nremote access and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type == \"creation\" and process.name : \"TeamViewer.exe\" and\n file.extension : (\"exe\", \"dll\", \"scr\", \"com\", \"bat\", \"ps1\", \"vbs\", \"vbe\", \"js\", \"wsh\", \"hta\")\n", "references": [ "https://blog.menasec.net/2019/11/hunting-for-suspicious-use-of.html" @@ -43,7 +43,8 @@ "Host", "Windows", "Threat Detection", - "Command and Control" + "Command and Control", + "has_guide" ], "threat": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json index 646cc2397ac02..490b96665a83b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json @@ -61,7 +61,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -85,5 +86,5 @@ "value": 25 }, "type": "threshold", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json index a50ad42b025ad..5d6992ccec503 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json @@ -10,7 +10,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Potential SSH Password Spraying", + "name": "Potential SSH Password Guessing", + "note": "## Triage and analysis\n\n### Investigating Potential SSH Password Guessing Attack\n\nThe rule identifies consecutive SSH login failures followed by a successful login from the same source IP address to the\nsame target host indicating a successful attempt of brute force password guessing.\n\n#### Possible investigation steps\n\n- Investigate the login failure user name(s).\n- Investigate the source IP address of the failed ssh login attempt(s).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Identify the source and the target computer and their roles in the IT environment.\n\n### False positive analysis\n\n- Authentication misconfiguration or obsolete credentials.\n- Service account password expired.\n- Infrastructure or availability issue.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Ensure active session(s) on the host(s) are terminated as the attacker could have gained initial\naccess to the system(s).\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified.\n- Reset passwords for these accounts and other potentially compromised credentials.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n", "query": "sequence by host.id, source.ip, user.name with maxspan=3s\n [authentication where event.action in (\"ssh_login\", \"user_login\") and\n event.outcome == \"failure\" and source.ip != null and source.ip != \"0.0.0.0\" and source.ip != \"::\" ] with runs=2\n\n [authentication where event.action in (\"ssh_login\", \"user_login\") and\n event.outcome == \"success\" and source.ip != null and source.ip != \"0.0.0.0\" and source.ip != \"::\" ]\n", "required_fields": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json index dbc92a27f7040..30103c4bf3c5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json @@ -58,7 +58,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json index 844b09d5de305..cf962e1f5f65b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Credential Access via Trusted Developer Utility", - "note": "## Triage and analysis\n\n### Investigating Potential Credential Access via Trusted Developer Utility\n\nThe Microsoft Build Engine is a platform for building applications. This engine, also known as MSBuild, provides an XML\nschema for a project file that controls how the build platform processes and builds software.\n\nAdversaries can abuse MSBuild to proxy the execution of malicious code. The inline task capability of MSBuild that was\nintroduced in .NET version 4 allows for C# or Visual Basic code to be inserted into an XML project file. MSBuild will\ncompile and execute the inline task. `MSBuild.exe` is a signed Microsoft binary, and the execution of code using it can bypass\napplication control defenses that are configured to allow `MSBuild.exe` execution.\n\nThis rule looks for the MSBuild process loading `vaultcli.dll` or `SAMLib.DLL`, which indicates the execution of\ncredential access activities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine the command line to identify the `.csproj` file location.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Potential Credential Access via Trusted Developer Utility\n\nThe Microsoft Build Engine is a platform for building applications. This engine, also known as MSBuild, provides an XML\nschema for a project file that controls how the build platform processes and builds software.\n\nAdversaries can abuse MSBuild to proxy the execution of malicious code. The inline task capability of MSBuild that was\nintroduced in .NET version 4 allows for C# or Visual Basic code to be inserted into an XML project file. MSBuild will\ncompile and execute the inline task. `MSBuild.exe` is a signed Microsoft binary, and the execution of code using it can bypass\napplication control defenses that are configured to allow `MSBuild.exe` execution.\n\nThis rule looks for the MSBuild process loading `vaultcli.dll` or `SAMLib.DLL`, which indicates the execution of\ncredential access activities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine the command line to identify the `.csproj` file location.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by process.entity_id\n [process where event.type == \"start\" and (process.name : \"MSBuild.exe\" or process.pe.original_file_name == \"MSBuild.exe\")]\n [any where (event.category == \"library\" or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n (dll.name : (\"vaultcli.dll\", \"SAMLib.DLL\") or file.name : (\"vaultcli.dll\", \"SAMLib.DLL\"))]\n", "required_fields": [ { @@ -67,7 +67,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -87,5 +88,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json index 38a9092d1eb2f..15fcde9b83e81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json @@ -58,7 +58,8 @@ "Windows", "Threat Detection", "Credential Access", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -86,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json index 0bde382a2e55a..783852168fcb5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json @@ -39,7 +39,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -67,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json index 15e4cad7bfc40..49a04cb198d86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json @@ -43,7 +43,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json index 41e7921aab797..2140870e22d1f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json @@ -61,7 +61,8 @@ "SecOps", "Identity and Access", "Credential Access", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -91,5 +92,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index b10a2e5ea9fbc..11a8abce27853 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Kerberos Traffic from Unusual Process", - "note": "## Triage and analysis\n\n### Investigating Kerberos Traffic from Unusual Process\n\nKerberos is the default authentication protocol in Active Directory, designed to provide strong authentication for\nclient/server applications by using secret-key cryptography.\n\nDomain-joined hosts usually perform Kerberos traffic using the `lsass.exe` process. This rule detects the occurrence of\ntraffic on the Kerberos port (88) by processes other than `lsass.exe` to detect the unusual request and usage of\nKerberos tickets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if the Destination IP is related to a Domain Controller.\n- Review event ID 4769 for suspicious ticket requests.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule uses a Kerberos-related port but does not identify the protocol used on that port. HTTP traffic on a\nnon-standard port or destination IP address unrelated to Domain controllers can create false positives.\n- Exceptions can be added for noisy/frequent connections.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n - Ticket requests can be used to investigate potentially compromised accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Kerberos Traffic from Unusual Process\n\nKerberos is the default authentication protocol in Active Directory, designed to provide strong authentication for\nclient/server applications by using secret-key cryptography.\n\nDomain-joined hosts usually perform Kerberos traffic using the `lsass.exe` process. This rule detects the occurrence of\ntraffic on the Kerberos port (88) by processes other than `lsass.exe` to detect the unusual request and usage of\nKerberos tickets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if the Destination IP is related to a Domain Controller.\n- Review event ID 4769 for suspicious ticket requests.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule uses a Kerberos-related port but does not identify the protocol used on that port. HTTP traffic on a\nnon-standard port or destination IP address unrelated to Domain controllers can create false positives.\n- Exceptions can be added for noisy/frequent connections.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n - Ticket requests can be used to investigate potentially compromised accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "network where event.type == \"start\" and network.direction : (\"outgoing\", \"egress\") and\n destination.port == 88 and source.port >= 49152 and\n not process.executable :\n (\"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"System\",\n \"\\\\device\\\\harddiskvolume?\\\\windows\\\\system32\\\\lsass.exe\",\n \"?:\\\\Program Files\\\\rapid7\\\\nexpose\\\\nse\\\\.DLLCACHE\\\\nseserv.exe\",\n \"?:\\\\Program Files (x86)\\\\GFI\\\\LanGuard 12 Agent\\\\lnsscomm.exe\",\n \"?:\\\\Program Files (x86)\\\\SuperScan\\\\scanner.exe\",\n \"?:\\\\Program Files (x86)\\\\Nmap\\\\nmap.exe\",\n \"\\\\device\\\\harddiskvolume?\\\\program files (x86)\\\\nmap\\\\nmap.exe\") and\n destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert false positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", "required_fields": [ { @@ -63,7 +63,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -84,5 +85,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json index 63b939ff2e55c..eb1af7292bdfa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "LSASS Memory Dump Handle Access", - "note": "## Triage and analysis\n\n### Investigating LSASS Memory Dump Handle Access\n\nLocal Security Authority Server Service (LSASS) is a process in Microsoft Windows operating systems that is responsible\nfor enforcing security policy on the system. It verifies users logging on to a Windows computer or server, handles\npassword changes, and creates access tokens.\n\nAdversaries may attempt to access credential material stored in LSASS process memory. After a user logs on,the system\ngenerates and stores a variety of credential materials in LSASS process memory. This is meant to facilitate single\nsign-on (SSO) ensuring a user isn\u2019t prompted each time resource access is requested. These credential materials can be\nharvested by an adversary using administrative user or SYSTEM privileges to conduct lateral movement using\n[alternate authentication material](https://attack.mitre.org/techniques/T1550/).\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There should be very few or no false positives for this rule. If this activity is expected or noisy in your environment,\nconsider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n- If the process is related to antivirus or endpoint detection and response solutions, validate that it is installed on\nthe correct path and signed with the company's valid digital signature.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Scope compromised credentials and disable the accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested)\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nSystem Audit Policies >\nObject Access >\nAudit File System (Success,Failure)\nAudit Handle Manipulation (Success,Failure)\n```\n\nAlso, this event generates only if the object\u2019s [SACL](https://docs.microsoft.com/en-us/windows/win32/secauthz/access-control-lists) has the required access control entry (ACE) to handle the use of specific access rights.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", + "note": "## Triage and analysis\n\n### Investigating LSASS Memory Dump Handle Access\n\nLocal Security Authority Server Service (LSASS) is a process in Microsoft Windows operating systems that is responsible\nfor enforcing security policy on the system. It verifies users logging on to a Windows computer or server, handles\npassword changes, and creates access tokens.\n\nAdversaries may attempt to access credential material stored in LSASS process memory. After a user logs on,the system\ngenerates and stores a variety of credential materials in LSASS process memory. This is meant to facilitate single\nsign-on (SSO) ensuring a user isn\u2019t prompted each time resource access is requested. These credential materials can be\nharvested by an adversary using administrative user or SYSTEM privileges to conduct lateral movement using\n[alternate authentication material](https://attack.mitre.org/techniques/T1550/).\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There should be very few or no false positives for this rule. If this activity is expected or noisy in your environment,\nconsider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n- If the process is related to antivirus or endpoint detection and response solutions, validate that it is installed on\nthe correct path and signed with the company's valid digital signature.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Scope compromised credentials and disable the accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested)\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nSystem Audit Policies >\nObject Access >\nAudit File System (Success,Failure)\nAudit Handle Manipulation (Success,Failure)\n```\n\nAlso, this event generates only if the object\u2019s [SACL](https://docs.microsoft.com/en-us/windows/win32/secauthz/access-control-lists) has the required access control entry (ACE) to handle the use of specific access rights.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", "query": "any where event.action == \"File System\" and event.code == \"4656\" and\n\n winlog.event_data.ObjectName : (\n \"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"\\\\Device\\\\HarddiskVolume?\\\\Windows\\\\System32\\\\lsass.exe\",\n \"\\\\Device\\\\HarddiskVolume??\\\\Windows\\\\System32\\\\lsass.exe\") and\n\n /* The right to perform an operation controlled by an extended access right. */\n\n (winlog.event_data.AccessMask : (\"0x1fffff\" , \"0x1010\", \"0x120089\", \"0x1F3FFF\") or\n winlog.event_data.AccessMaskDescription : (\"READ_CONTROL\", \"Read from process memory\"))\n\n /* Common Noisy False Positives */\n\n and not winlog.event_data.ProcessName : (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\system32\\\\wbem\\\\WmiPrvSE.exe\",\n \"?:\\\\Windows\\\\System32\\\\dllhost.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\msiexec.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Windows\\\\explorer.exe\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656", @@ -61,7 +61,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -89,5 +90,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json index 05199fed31f61..5fa244d380ae1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json @@ -35,7 +35,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -56,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json index 0524d0dbb1a2e..f78eead09dea8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -66,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json index 018868f23fb20..d5a13d5f0285f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of WDigest Security Provider", - "note": "## Triage and analysis\n\n### Investigating Modification of WDigest Security Provider\n\nIn Windows XP, Microsoft added support for a protocol known as WDigest. The WDigest protocol allows clients to send\ncleartext credentials to Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) applications\nbased on RFC 2617 and 2831. Windows versions up to 8 and 2012 store logon credentials in memory in plaintext by default,\nwhich is no longer the case with newer Windows versions.\n\nStill, attackers can force WDigest to store the passwords insecurely on the memory by modifying the\n`HKLM\\SYSTEM\\*ControlSet*\\Control\\SecurityProviders\\WDigest\\UseLogonCredential` registry key. This activity is\ncommonly related to the execution of credential dumping tools.\n\n#### Possible investigation steps\n\n- It is unlikely that the monitored registry key was modified legitimately in newer versions of Windows. Analysts should\ntreat any activity triggered from this rule with high priority as it typically represents an active adversary.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if credential dumping tools were run on the host, and retrieve and analyze suspicious executables:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the entire domain to credential compromises and\nconsequently unauthorized access.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Modification of WDigest Security Provider\n\nIn Windows XP, Microsoft added support for a protocol known as WDigest. The WDigest protocol allows clients to send\ncleartext credentials to Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) applications\nbased on RFC 2617 and 2831. Windows versions up to 8 and 2012 store logon credentials in memory in plaintext by default,\nwhich is no longer the case with newer Windows versions.\n\nStill, attackers can force WDigest to store the passwords insecurely on the memory by modifying the\n`HKLM\\SYSTEM\\*ControlSet*\\Control\\SecurityProviders\\WDigest\\UseLogonCredential` registry key. This activity is\ncommonly related to the execution of credential dumping tools.\n\n#### Possible investigation steps\n\n- It is unlikely that the monitored registry key was modified legitimately in newer versions of Windows. Analysts should\ntreat any activity triggered from this rule with high priority as it typically represents an active adversary.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if credential dumping tools were run on the host, and retrieve and analyze suspicious executables:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the entire domain to credential compromises and\nconsequently unauthorized access.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where event.type : (\"creation\", \"change\") and\n registry.path :\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\"\n and registry.data.strings : (\"1\", \"0x00000001\") and\n not (process.executable : \"?:\\\\Windows\\\\System32\\\\svchost.exe\" and user.id : \"S-1-5-18\")\n", "references": [ "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", @@ -55,7 +55,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -83,5 +84,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json index ca2bae7cdc87d..9da4639332581 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json @@ -48,7 +48,8 @@ "Windows", "Threat Detection", "Lateral Movement", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -98,5 +99,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json index a8be93f64e493..aa5dc5f8288d6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json @@ -42,7 +42,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -92,5 +93,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json index 8df600b289265..0caeb8f4cd34e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -93,5 +94,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json index 0dde40f82f1ce..bd28407a2d53c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json @@ -10,7 +10,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Potential SSH Brute Force Detected", + "name": "Potential Linux SSH Brute Force Detected", + "note": "## Triage and analysis\n\n### Investigating Potential SSH Brute Force Attack\n\nThe rule identifies consecutive SSH login failures targeting a user account from the same source IP address to the\nsame target host indicating brute force login attempts.\n\n#### Possible investigation steps\n\n- Investigate the login failure user name(s).\n- Investigate the source IP address of the failed ssh login attempt(s).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Identify the source and the target computer and their roles in the IT environment.\n\n### False positive analysis\n\n- Authentication misconfiguration or obsolete credentials.\n- Service account password expired.\n- Infrastructure or availability issue.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified.\n- Reset passwords for these accounts and other potentially compromised credentials.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n", "query": "sequence by host.id, source.ip, user.name with maxspan=10s\n [authentication where event.action in (\"ssh_login\", \"user_login\") and\n event.outcome == \"failure\" and source.ip != null and source.ip != \"0.0.0.0\" and source.ip != \"::\" ] with runs=10\n", "required_fields": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json index a15fa38e979a1..f540d3ae3202c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json @@ -11,6 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential SSH Brute Force Detected on Privileged Account", + "note": "## Triage and analysis\n\n### Investigating Potential SSH Brute Force Attack on Privileged Account\n\nThe rule identifies consecutive SSH login failures targeting a privileged (root) account from the same source IP\naddress to the same target host indicating brute force login attempts.\n\n#### Possible investigation steps\n\n- Investigate the login failure on privileged account(s).\n- Investigate the source IP address of the failed ssh login attempt(s).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Identify the source and the target computer and their roles in the IT environment.\n\n### False positive analysis\n- Authentication misconfiguration or obsolete credentials.\n- Service account password expired.\n- Infrastructure or availability issue.\n\n### Response and remediation\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified.\n- Reset passwords for these accounts and other potentially compromised credentials.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n", "query": "sequence by host.id, source.ip with maxspan=10s\n [authentication where event.action in (\"ssh_login\", \"user_login\") and\n event.outcome == \"failure\" and source.ip != null and source.ip != \"0.0.0.0\" and\n source.ip != \"::\" and user.name in (\"*root*\" , \"*admin*\")] with runs=3\n", "required_fields": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json index 89519f49a1170..0f420cfb6d08f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json @@ -10,7 +10,7 @@ ], "language": "kuery", "license": "Elastic License v2", - "name": "Potential SSH Brute Force Detected", + "name": "Potential macOS SSH Brute Force Detected", "query": "event.category:process and event.type:start and process.name:\"sshd-keygen-wrapper\" and process.parent.name:launchd\n", "references": [ "https://themittenmac.com/detecting-ssh-activity-via-process-monitoring/" @@ -71,5 +71,5 @@ "value": 20 }, "type": "threshold", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json index 41d744c1ea8b1..1b6a0498a8f52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json @@ -89,7 +89,8 @@ "Windows", "Threat Detection", "Lateral Movement", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -131,5 +132,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json index df0d68a13f901..2ec5b4d9a877b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json @@ -57,7 +57,8 @@ "Continuous Monitoring", "SecOps", "Data Protection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -78,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json index 8421a442c4724..1992b3a85cf93 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json @@ -47,7 +47,8 @@ "Windows", "Threat Detection", "Credential Access", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -71,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json index 2b6866599d3da..8fb978965654c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json @@ -53,7 +53,8 @@ "Windows", "Threat Detection", "Credential Access", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json index 2d2c2fadf1518..d8e9a61e71b4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json @@ -54,7 +54,8 @@ "Windows", "Threat Detection", "Lateral Movement", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -96,5 +97,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json index 6ae0580651a8c..0286e35f7d049 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json @@ -55,7 +55,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "has_guide" ], "threat": [ { @@ -76,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json index 3e6986451938e..52adb1756ad50 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of AmsiEnable Registry Key", - "note": "## Triage and analysis\n\n### Investigating Modification of AmsiEnable Registry Key\n\nThe Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and\nservices to integrate with any antimalware product that's present on a machine. AMSI provides integration with multiple\nWindows components, ranging from User Account Control (UAC) to VBA Macros.\n\nSince AMSI is widely used across security products for increased visibility, attackers can disable it to evade\ndetections that rely on it.\n\nThis rule monitors the modifications to the Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable registry key.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate the execution of scripts and macros after the registry modification.\n- Retrieve scripts or Microsoft Office files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the host to malware infections.\n\n### Related rules\n\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Delete or set the key to its default value.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Modification of AmsiEnable Registry Key\n\nThe Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and\nservices to integrate with any antimalware product that's present on a machine. AMSI provides integration with multiple\nWindows components, ranging from User Account Control (UAC) to VBA Macros.\n\nSince AMSI is widely used across security products for increased visibility, attackers can disable it to evade\ndetections that rely on it.\n\nThis rule monitors the modifications to the Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable registry key.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate the execution of scripts and macros after the registry modification.\n- Retrieve scripts or Microsoft Office files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the host to malware infections.\n\n### Related rules\n\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Delete or set the key to its default value.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\"\n ) and\n registry.data.strings: (\"0\", \"0x00000000\")\n", "references": [ "https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf", @@ -44,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -72,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json index 73223f74a8c37..8b409bdb63681 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json @@ -53,7 +53,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json index 191502d44c71b..972c69b5052da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json @@ -50,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -78,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json index fec3c5dd2f0e2..ff068224efa5a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json index a79fba0714765..eed3ed289c9c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json @@ -29,7 +29,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json index fa2c001a1e37a..cb0f9d549a04e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Log Auditing" + "Log Auditing", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json index c0f990f8e496e..97867f74d0557 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Log Auditing" + "Log Auditing", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json index 3f0d5960e41f6..6fc0a85d8e9cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Monitoring" + "Monitoring", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json index 15b0818b4d3d6..9113f5907dd3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json @@ -56,7 +56,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Monitoring" + "Monitoring", + "has_guide" ], "threat": [ { @@ -84,5 +85,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json index 6221668580ee1..2264c363bb04c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Creation or Modification of Root Certificate", - "note": "## Triage and analysis\n\n### Investigating Creation or Modification of Root Certificate\n\nRoot certificates are the primary level of certifications that tell a browser that the communication is trusted and\nlegitimate. This verification is based upon the identification of a certification authority. Windows\nadds several trusted root certificates so browsers can use them to communicate with websites.\n\n[Check out this post](https://www.thewindowsclub.com/what-are-root-certificates-windows) for more details on root certificates and the involved cryptography.\n\nThis rule identifies the creation or modification of a root certificate by monitoring registry modifications. The\ninstallation of a malicious root certificate would allow an attacker the ability to masquerade malicious files as valid\nsigned components from any entity (for example, Microsoft). It could also allow an attacker to decrypt SSL traffic.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, other registry or file\nmodifications, and any spawned child processes.\n- If one of the processes is suspicious, retrieve it and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This detection may be triggered by certain applications that install root certificates for the purpose of inspecting\nSSL traffic. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove the malicious certificate from the root certificate store.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Creation or Modification of Root Certificate\n\nRoot certificates are the primary level of certifications that tell a browser that the communication is trusted and\nlegitimate. This verification is based upon the identification of a certification authority. Windows\nadds several trusted root certificates so browsers can use them to communicate with websites.\n\n[Check out this post](https://www.thewindowsclub.com/what-are-root-certificates-windows) for more details on root certificates and the involved cryptography.\n\nThis rule identifies the creation or modification of a root certificate by monitoring registry modifications. The\ninstallation of a malicious root certificate would allow an attacker the ability to masquerade malicious files as valid\nsigned components from any entity (for example, Microsoft). It could also allow an attacker to decrypt SSL traffic.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, other registry or file\nmodifications, and any spawned child processes.\n- If one of the processes is suspicious, retrieve it and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This detection may be triggered by certain applications that install root certificates for the purpose of inspecting\nSSL traffic. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove the malicious certificate from the root certificate store.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path :\n (\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\"\n ) and\n not process.executable :\n (\"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\*.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\*.exe\",\n \"?:\\\\Windows\\\\Sysmon64.exe\",\n \"?:\\\\Windows\\\\Sysmon.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Windows\\\\WinSxS\\\\*.exe\",\n \"?:\\\\Windows\\\\UUS\\\\amd64\\\\MoUsoCoreWorker.exe\")\n", "references": [ "https://posts.specterops.io/code-signing-certificate-cloning-attacks-and-defenses-6f98657fc6ec", @@ -47,7 +47,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -75,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json index 4bf9babea192f..5b39ffc36ed8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json index 90650abf7b9bb..5a74bfc9b1664 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Defender Exclusions Added via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions Added via PowerShell\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows. Since this software product is\nused to prevent and stop malware, it's important to monitor what specific exclusions are made to the product's configuration\nsettings. These can often be signs of an adversary or malware trying to bypass Windows Defender's capabilities. One of\nthe more notable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/)\nwas observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defender to avoid detection.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Examine the exclusion in order to determine the intent behind it.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- If the exclusion specifies a suspicious file or path, retrieve the file(s) and determine if malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives due to how often network administrators legitimately configure\nexclusions. In order to validate the activity further, review the specific exclusion and its intent. There are many\nlegitimate reasons for exclusions, so it's important to gain context.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions Added via PowerShell\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows. Since this software product is\nused to prevent and stop malware, it's important to monitor what specific exclusions are made to the product's configuration\nsettings. These can often be signs of an adversary or malware trying to bypass Windows Defender's capabilities. One of\nthe more notable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/)\nwas observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defender to avoid detection.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Examine the exclusion in order to determine the intent behind it.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- If the exclusion specifies a suspicious file or path, retrieve the file(s) and determine if malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives due to how often network administrators legitimately configure\nexclusions. In order to validate the activity further, review the specific exclusion and its intent. There are many\nlegitimate reasons for exclusions, so it's important to gain context.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name in (\"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\")) and\n process.args : (\"*Add-MpPreference*\", \"*Set-MpPreference*\") and\n process.args : (\"*-Exclusion*\")\n", "references": [ "https://www.bitdefender.com/files/News/CaseStudies/study/400/Bitdefender-PR-Whitepaper-MosaicLoader-creat5540-en-EN.pdf" @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -103,5 +104,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json index 3132045aeb9f1..f4c3273953dca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json @@ -43,7 +43,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -71,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json index 912596ec4188f..a07759a72e73f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json index 9629c53ead56c..21c0bd54b831c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json @@ -51,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -79,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json index 24dde4c55f75b..b6c0b9ceceb53 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json @@ -51,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json index 6cd510f9c638c..9104509c8576e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Log Auditing" + "Log Auditing", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json index c90adc84eb4b7..5ddaa9cdac60a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json index b23b4a422fe4c..5013d8a5d731a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enable Host Network Discovery via Netsh", - "note": "## Triage and analysis\n\n### Investigating Enable Host Network Discovery via Netsh\n\nThe Windows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can enable Network Discovery on the Windows firewall to find other systems present in the same network. Systems\nwith this setting enabled will communicate with other systems using broadcast messages, which can be used to identify\ntargets for lateral movement. This rule looks for the setup of this setting using the netsh utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand there are justifications for this configuration.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Disable Network Discovery:\n - Using netsh: `netsh advfirewall firewall set rule group=\"Network Discovery\" new enable=No`\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Enable Host Network Discovery via Netsh\n\nThe Windows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can enable Network Discovery on the Windows firewall to find other systems present in the same network. Systems\nwith this setting enabled will communicate with other systems using broadcast messages, which can be used to identify\ntargets for lateral movement. This rule looks for the setup of this setting using the netsh utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand there are justifications for this configuration.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Disable Network Discovery:\n - Using netsh: `netsh advfirewall firewall set rule group=\"Network Discovery\" new enable=No`\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\nprocess.name : \"netsh.exe\" and\nprocess.args : (\"firewall\", \"advfirewall\") and process.args : \"group=Network Discovery\" and process.args : \"enable=Yes\"\n", "required_fields": [ { @@ -43,7 +43,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -71,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json index faa6600e315bd..a3a651443340c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Microsoft Build Engine Started by an Office Application", - "note": "## Triage and analysis\n\n### Investigating Microsoft Build Engine Started by an Office Application\n\nMicrosoft Office (MS Office) is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently targeted\nfor initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThe Microsoft Build Engine is a platform for building applications. This engine, also known as MSBuild, provides an XML\nschema for a project file that controls how the build platform processes and builds software, and can be abused to proxy\nexecution of code.\n\nThis rule looks for the `Msbuild.exe` utility spawned by MS Office programs. This is generally the result of the\nexecution of malicious documents.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Microsoft Build Engine Started by an Office Application\n\nMicrosoft Office (MS Office) is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently targeted\nfor initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThe Microsoft Build Engine is a platform for building applications. This engine, also known as MSBuild, provides an XML\nschema for a project file that controls how the build platform processes and builds software, and can be abused to proxy\nexecution of code.\n\nThis rule looks for the `Msbuild.exe` utility spawned by MS Office programs. This is generally the result of the\nexecution of malicious documents.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.name : \"MSBuild.exe\" and\n process.parent.name : (\"eqnedt32.exe\",\n \"excel.exe\",\n \"fltldr.exe\",\n \"msaccess.exe\",\n \"mspub.exe\",\n \"outlook.exe\",\n \"powerpnt.exe\",\n \"winword.exe\" )\n", "references": [ "https://blog.talosintelligence.com/2020/02/building-bypass-with-msbuild.html" @@ -46,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json index 24a214f445d77..89a34647ca08c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json @@ -53,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -74,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json index ccb198f287caa..1d50e5192b479 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "MS Office Macro Security Registry Modifications", - "note": "## Triage and analysis\n\n### Investigating MS Office Macro Security Registry Modifications\n\nMacros are small programs that are used to automate repetitive tasks in Microsoft Office applications.\nHistorically, macros have been used for a variety of reasons -- from automating part of a job, to\nbuilding entire processes and data flows. Macros are written in Visual Basic for Applications (VBA) and are saved as\npart of Microsoft Office files.\n\nMacros are often created for legitimate reasons, but they can also be written by attackers to gain access, harm a\nsystem, or bypass other security controls such as application allow listing. In fact, exploitation from malicious macros\nis one of the top ways that organizations are compromised today. These attacks are often conducted through phishing or\nspear phishing campaigns.\n\nAttackers can convince victims to modify Microsoft Office security settings, so their macros are trusted by default and\nno warnings are displayed when they are executed. These settings include:\n\n* *Trust access to the VBA project object model* - When enabled, Microsoft Office will trust all macros and run any code\nwithout showing a security warning or requiring user permission.\n* *VbaWarnings* - When set to 1, Microsoft Office will trust all macros and run any code without showing a security\nwarning or requiring user permission.\n\nThis rule looks for registry changes affecting the conditions above.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user and check if the change was done manually.\n- Verify whether malicious macros were executed after the registry change.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently executed Office documents and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true\npositives (B-TPs), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the registry key value.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Explore using GPOs to manage security settings for Microsoft Office macros.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating MS Office Macro Security Registry Modifications\n\nMacros are small programs that are used to automate repetitive tasks in Microsoft Office applications.\nHistorically, macros have been used for a variety of reasons -- from automating part of a job, to\nbuilding entire processes and data flows. Macros are written in Visual Basic for Applications (VBA) and are saved as\npart of Microsoft Office files.\n\nMacros are often created for legitimate reasons, but they can also be written by attackers to gain access, harm a\nsystem, or bypass other security controls such as application allow listing. In fact, exploitation from malicious macros\nis one of the top ways that organizations are compromised today. These attacks are often conducted through phishing or\nspear phishing campaigns.\n\nAttackers can convince victims to modify Microsoft Office security settings, so their macros are trusted by default and\nno warnings are displayed when they are executed. These settings include:\n\n* *Trust access to the VBA project object model* - When enabled, Microsoft Office will trust all macros and run any code\nwithout showing a security warning or requiring user permission.\n* *VbaWarnings* - When set to 1, Microsoft Office will trust all macros and run any code without showing a security\nwarning or requiring user permission.\n\nThis rule looks for registry changes affecting the conditions above.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user and check if the change was done manually.\n- Verify whether malicious macros were executed after the registry change.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently executed Office documents and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true\npositives (B-TPs), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the registry key value.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Explore using GPOs to manage security settings for Microsoft Office macros.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\"\n ) and\n registry.data.strings == \"0x00000001\" and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"mshta.exe\", \"winword.exe\", \"excel.exe\")\n", "required_fields": [ { @@ -44,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -87,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json index f2c188f102fa3..56df918e940ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious .NET Reflection via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Suspicious .NET Reflection via PowerShell\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use .NET reflection to load PEs and DLLs in memory. These payloads are commonly embedded in the script,\nwhich can circumvent file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious .NET Reflection via PowerShell\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use .NET reflection to load PEs and DLLs in memory. These payloads are commonly embedded in the script,\nwhich can circumvent file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "event.category:process and\n powershell.file.script_block_text : (\n \"[System.Reflection.Assembly]::Load\" or\n \"[Reflection.Assembly]::Load\"\n )\n", "references": [ "https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load" @@ -37,7 +37,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -92,5 +93,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json index 0918910210f5e..dec606a855dcb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Payload Encoded and Compressed", - "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Payload Encoded and Compressed\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can embed compressed and encoded payloads in scripts to load directly into the memory without touching the\ndisk. This strategy can circumvent string and file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Payload Encoded and Compressed\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can embed compressed and encoded payloads in scripts to load directly into the memory without touching the\ndisk. This strategy can circumvent string and file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "event.category:process and\n powershell.file.script_block_text : (\n (\n \"System.IO.Compression.DeflateStream\" or\n \"System.IO.Compression.GzipStream\" or\n \"IO.Compression.DeflateStream\" or\n \"IO.Compression.GzipStream\"\n ) and\n FromBase64String\n )\n", "required_fields": [ { @@ -37,7 +37,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -85,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json index 53aa8459e996d..686591b1738ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json @@ -42,7 +42,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -75,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json index 5cc10bf316cdb..9f870734840dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Firewall Disabled via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Windows Firewall Disabled via PowerShell\n\nWindows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can disable the Windows firewall or its rules to enable lateral movement and command and control activity.\n\nThis rule identifies patterns related to disabling the Windows firewall or its rules using the `Set-NetFirewallProfile`\nPowerShell cmdlet.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user is an administrator and is legitimately performing\ntroubleshooting.\n- In case of an allowed benign true positive (B-TP), assess adding rules to allow needed traffic and re-enable the firewall.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Re-enable the firewall with its desired configurations.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Windows Firewall Disabled via PowerShell\n\nWindows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can disable the Windows firewall or its rules to enable lateral movement and command and control activity.\n\nThis rule identifies patterns related to disabling the Windows firewall or its rules using the `Set-NetFirewallProfile`\nPowerShell cmdlet.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user is an administrator and is legitimately performing\ntroubleshooting.\n- In case of an allowed benign true positive (B-TP), assess adding rules to allow needed traffic and re-enable the firewall.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Re-enable the firewall with its desired configurations.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.action == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name == \"PowerShell.EXE\") and\n process.args : \"*Set-NetFirewallProfile*\" and\n (process.args : \"*-Enabled*\" and process.args : \"*False*\") and\n (process.args : \"*-All*\" or process.args : (\"*Public*\", \"*Domain*\", \"*Private*\"))\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2019-ps", @@ -54,7 +54,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -82,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json index ee61888d446e1..d7f255f981c3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Process Access via Direct System Call", - "note": "## Triage and analysis\n\n### Investigating Suspicious Process Access via Direct System Call\n\nEndpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is\nmalicious or not. It's possible to bypass hooked functions by writing malicious functions that call syscalls directly.\n\nMore context and technical details can be found in this [research blog](https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/).\n\nThis rule identifies suspicious process access events from an unknown memory region. Attackers can use direct system\ncalls to bypass security solutions that rely on hooks.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This detection may be triggered by certain applications that install root certificates for the purpose of inspecting\nSSL traffic. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove the malicious certificate from the root certificate store.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious Process Access via Direct System Call\n\nEndpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is\nmalicious or not. It's possible to bypass hooked functions by writing malicious functions that call syscalls directly.\n\nMore context and technical details can be found in this [research blog](https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/).\n\nThis rule identifies suspicious process access events from an unknown memory region. Attackers can use direct system\ncalls to bypass security solutions that rely on hooks.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This detection may be triggered by certain applications that install root certificates for the purpose of inspecting\nSSL traffic. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove the malicious certificate from the root certificate store.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.code == \"10\" and\n length(winlog.event_data.CallTrace) > 0 and\n\n /* Sysmon CallTrace starting with unknown memory module instead of ntdll which host Windows NT Syscalls */\n not winlog.event_data.CallTrace :\n (\"?:\\\\WINDOWS\\\\SYSTEM32\\\\ntdll.dll*\",\n \"?:\\\\WINDOWS\\\\SysWOW64\\\\ntdll.dll*\",\n \"?:\\\\Windows\\\\System32\\\\wow64cpu.dll*\",\n \"?:\\\\WINDOWS\\\\System32\\\\wow64win.dll*\",\n \"?:\\\\Windows\\\\System32\\\\win32u.dll*\") and\n\n not winlog.event_data.TargetImage :\n (\"?:\\\\Program Files (x86)\\\\Malwarebytes Anti-Exploit\\\\mbae-svc.exe\",\n \"?:\\\\Program Files\\\\Cisco\\\\AMP\\\\*\\\\sfc.exe\",\n \"?:\\\\Program Files (x86)\\\\Microsoft\\\\EdgeWebView\\\\Application\\\\*\\\\msedgewebview2.exe\",\n \"?:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\*\\\\AcroCEF.exe\") and\n\n not (process.executable : (\"?:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\Acrobat.exe\",\n \"?:\\\\Program Files (x86)\\\\World of Warcraft\\\\_classic_\\\\WowClassic.exe\") and\n not winlog.event_data.TargetImage : \"?:\\\\WINDOWS\\\\system32\\\\lsass.exe\")\n", "references": [ "https://twitter.com/SBousseaden/status/1278013896440324096", @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json index 76e62528a181c..71090cbe65b64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies when a process is created and immediately accessed from an unknown memory code region and by the same parent process. This may indicate a code injection or hollowing attempt.", + "description": "Identifies when a process is created and immediately accessed from an unknown memory code region and by the same parent process. This may indicate a code injection attempt.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,6 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Process Creation CallTrace", + "note": "## Triage and analysis\n\n### Investigating Suspicious Process Creation CallTrace\n\nAttackers may inject code into child processes' memory to hide their actual activity, evade detection mechanisms, and\ndecrease discoverability during forensics. This rule looks for a spawned process by Microsoft Office, scripting, and\ncommand line applications, followed by a process access event for an unknown memory region by the parent process, which\ncan indicate a code injection attempt.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behavior observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Create a memory dump of the child process for analysis.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id with maxspan=1m\n [process where event.code == \"1\" and\n /* sysmon process creation */\n process.parent.name : (\"winword.exe\", \"excel.exe\", \"outlook.exe\", \"powerpnt.exe\", \"eqnedt32.exe\", \"fltldr.exe\",\n \"mspub.exe\", \"msaccess.exe\",\"cscript.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\",\n \"mshta.exe\", \"wmic.exe\", \"cmstp.exe\", \"msxsl.exe\") and\n\n /* noisy FP patterns */\n not (process.parent.name : \"EXCEL.EXE\" and process.executable : \"?:\\\\Program Files\\\\Microsoft Office\\\\root\\\\Office*\\\\ADDINS\\\\*.exe\") and\n not (process.executable : \"?:\\\\Windows\\\\splwow64.exe\" and process.args in (\"8192\", \"12288\") and process.parent.name : (\"winword.exe\", \"excel.exe\", \"outlook.exe\", \"powerpnt.exe\")) and\n not (process.parent.name : \"rundll32.exe\" and process.parent.args : (\"?:\\\\WINDOWS\\\\Installer\\\\MSI*.tmp,zzzzInvokeManagedCustomActionOutOfProc\", \"--no-sandbox\")) and\n not (process.executable :\n (\"?:\\\\Program Files (x86)\\\\Microsoft\\\\EdgeWebView\\\\Application\\\\*\\\\msedgewebview2.exe\",\n \"?:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\Acrobat.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\DWWIN.EXE\") and\n process.parent.name : (\"winword.exe\", \"excel.exe\", \"outlook.exe\", \"powerpnt.exe\")) and\n not (process.parent.name : \"regsvr32.exe\" and process.parent.args : (\"?:\\\\Program Files\\\\*\", \"?:\\\\Program Files (x86)\\\\*\"))\n ] by process.parent.entity_id, process.entity_id\n [process where event.code == \"10\" and\n /* Sysmon process access event from unknown module */\n winlog.event_data.CallTrace : \"*UNKNOWN*\"] by process.entity_id, winlog.event_data.TargetProcessGUID\n", "required_fields": [ { @@ -92,5 +93,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json index 2a39c7a7961f0..2e44936da71b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual Executable File Creation by a System Critical Process", - "note": "## Triage and analysis\n\n### Investigating Unusual Executable File Creation by a System Critical Process\n\nWindows internal/system processes have some characteristics that can be used to spot suspicious activities. One of these\ncharacteristics is file operations.\n\nThis rule looks for the creation of executable files done by system-critical processes. This can indicate the exploitation\nof a vulnerability or a malicious process masquerading as a system-critical process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Unusual Executable File Creation by a System Critical Process\n\nWindows internal/system processes have some characteristics that can be used to spot suspicious activities. One of these\ncharacteristics is file operations.\n\nThis rule looks for the creation of executable files done by system-critical processes. This can indicate the exploitation\nof a vulnerability or a malicious process masquerading as a system-critical process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type != \"deletion\" and\n file.extension : (\"exe\", \"dll\") and\n process.name : (\"smss.exe\",\n \"autochk.exe\",\n \"csrss.exe\",\n \"wininit.exe\",\n \"services.exe\",\n \"lsass.exe\",\n \"winlogon.exe\",\n \"userinit.exe\",\n \"LogonUI.exe\")\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -61,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json index 5892ec0a1e44b..e9d8c440038b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual File Creation - Alternate Data Stream", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Unusual File Creation - Alternate Data Stream\n\nAlternate Data Streams (ADS) are file attributes only found on the NTFS file system. In this file system, files are\nbuilt up from a couple of attributes; one of them is $Data, also known as the data attribute.\n\nThe regular data stream, also referred to as the unnamed data stream since the name string of this attribute is empty,\ncontains the data inside the file. So any data stream that has a name is considered an alternate data stream.\n\nAttackers can abuse these alternate data streams to hide malicious files, string payloads, etc. This rule detects the\ncreation of alternate data streams on highly targeted file types.\n\n#### Possible investigation steps\n\n- Retrieve the contents of the alternate data stream, and analyze it for potential maliciousness. Analysts can use the\nfollowing PowerShell cmdlet to accomplish this:\n - `Get-Content -file C:\\Path\\To\\file.exe -stream ADSname`\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof process executable and file conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type == \"creation\" and\n\n file.path : \"C:\\\\*:*\" and\n not file.path : \"C:\\\\*:zone.identifier*\" and\n\n not process.executable :\n (\"?:\\\\windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\inetsrv\\\\w3wp.exe\",\n \"?:\\\\Windows\\\\explorer.exe\",\n \"?:\\\\Windows\\\\System32\\\\sihost.exe\",\n \"?:\\\\Windows\\\\System32\\\\PickerHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\SearchProtocolHost.exe\",\n \"?:\\\\Program Files (x86)\\\\Dropbox\\\\Client\\\\Dropbox.exe\",\n \"?:\\\\Program Files\\\\Rivet Networks\\\\SmartByte\\\\SmartByteNetworkService.exe\",\n \"?:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe\",\n \"?:\\\\Program Files\\\\ExpressConnect\\\\ExpressConnectNetworkService.exe\",\n \"?:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Program Files\\\\Mozilla Firefox\\\\firefox.exe\") and\n\n file.extension :\n (\n \"pdf\",\n \"dll\",\n \"png\",\n \"exe\",\n \"dat\",\n \"com\",\n \"bat\",\n \"cmd\",\n \"sys\",\n \"vbs\",\n \"ps1\",\n \"hta\",\n \"txt\",\n \"vbe\",\n \"js\",\n \"wsh\",\n \"docx\",\n \"doc\",\n \"xlsx\",\n \"xls\",\n \"pptx\",\n \"ppt\",\n \"rtf\",\n \"gif\",\n \"jpg\",\n \"png\",\n \"bmp\",\n \"img\",\n \"iso\"\n )\n", "required_fields": [ { @@ -73,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json index 9ea550212840e..7afc341466d9e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json @@ -58,7 +58,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json index 6b0299f2e5cd5..c93d6b9eb7045 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json @@ -39,7 +39,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json index 614bfead11cd3..aeaba22f4b281 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json @@ -50,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json index 00229a55fadc8..fa27159b49fd1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json @@ -53,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json index 13d3d85f47363..76d95e51f4692 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enumeration of Administrator Accounts", - "note": "## Triage and analysis\n\n### Investigating Enumeration of Administrator Accounts\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` and `wmic` utilities to enumerate administrator-related users or groups\nin the domain and local machine scope. Attackers can use this information to plan their next steps of the attack, such\nas mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- AdFind Command Activity - eda499b8-a073-4e35-9733-22ec71f57f3a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Enumeration of Administrator Accounts\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` and `wmic` utilities to enumerate administrator-related users or groups\nin the domain and local machine scope. Attackers can use this information to plan their next steps of the attack, such\nas mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- AdFind Command Activity - eda499b8-a073-4e35-9733-22ec71f57f3a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (((process.name : \"net.exe\" or process.pe.original_file_name == \"net.exe\") or\n ((process.name : \"net1.exe\" or process.pe.original_file_name == \"net1.exe\") and\n not process.parent.name : \"net.exe\")) and\n process.args : (\"group\", \"user\", \"localgroup\") and\n process.args : (\"admin\", \"Domain Admins\", \"Remote Desktop Users\", \"Enterprise Admins\", \"Organization Management\") and\n not process.args : \"/add\")\n\n or\n\n ((process.name : \"wmic.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n process.args : (\"group\", \"useraccount\"))\n", "required_fields": [ { @@ -50,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json index 938ed63bb184f..e0f2daaefa5ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json @@ -50,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json index a8d08a029cd2f..38779d72a19d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Network Enumeration", - "note": "## Triage and analysis\n\n### Investigating Windows Network Enumeration\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` utility to enumerate servers in the environment that hosts shared drives\nor printers. This information is useful to attackers as they can identify targets for lateral movements and search for\nvaluable shared data.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Windows Network Enumeration\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` utility to enumerate servers in the environment that hosts shared drives\nor printers. This information is useful to attackers as they can identify targets for lateral movements and search for\nvaluable shared data.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n ((process.name : \"net.exe\" or process.pe.original_file_name == \"net.exe\") or\n ((process.name : \"net1.exe\" or process.pe.original_file_name == \"net1.exe\") and\n not process.parent.name : \"net.exe\")) and\n (process.args : \"view\" or (process.args : \"time\" and process.args : \"\\\\\\\\*\"))\n\n\n /* expand when ancestry is available\n and not descendant of [process where event.type == \"start\" and process.name : \"cmd.exe\" and\n ((process.parent.name : \"userinit.exe\") or\n (process.parent.name : \"gpscript.exe\") or\n (process.parent.name : \"explorer.exe\" and\n process.args : \"C:\\\\*\\\\Start Menu\\\\Programs\\\\Startup\\\\*.bat*\"))]\n */\n", "required_fields": [ { @@ -50,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json index 9a8fd5df2687a..08d44e6911cae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Peripheral Device Discovery", - "note": "## Triage and analysis\n\n### Investigating Peripheral Device Discovery\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `fsutil` utility with the `fsinfo` subcommand to enumerate drives attached to\nthe computer, which can be used to identify secondary drives used for backups, mapped network drives, and removable\nmedia. These devices can contain valuable information for attackers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n- Determine whether this activity was followed by suspicious file access/copy operations or uploads to file storage\nservices.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Peripheral Device Discovery\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `fsutil` utility with the `fsinfo` subcommand to enumerate drives attached to\nthe computer, which can be used to identify secondary drives used for backups, mapped network drives, and removable\nmedia. These devices can contain valuable information for attackers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Determine whether this activity was followed by suspicious file access/copy operations or uploads to file storage\nservices.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name : \"fsutil.exe\" or process.pe.original_file_name == \"fsutil.exe\") and\n process.args : \"fsinfo\" and process.args : \"drives\"\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json index c4c07e84c0e20..cd5b4ebbdf9f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Share Enumeration Script", - "note": "", + "note": "## Triage and analysis\n\n### Investigating PowerShell Share Enumeration Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell to enumerate shares to search for sensitive data like documents, scripts, and other kinds\nof valuable data for encryption, exfiltration, and lateral movement.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Check for additional PowerShell and command line logs that indicate that imported functions were run.\n - Evaluate which information was potentially mapped and accessed by the attacker.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "event.category:process and\n powershell.file.script_block_text:(\n \"Invoke-ShareFinder\" or\n \"Invoke-ShareFinderThreaded\" or\n (\n \"shi1_netname\" and\n \"shi1_remark\"\n ) or\n (\n \"NetShareEnum\" and\n \"NetApiBufferFree\"\n )\n )\n", "references": [ "https://www.advintel.io/post/hunting-for-corporate-insurance-policies-indicators-of-ransom-exfiltrations", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json index 5d584a1eed8e1..cc587ddbf8315 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json @@ -41,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { @@ -101,5 +102,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json index 6724b509809c2..271d3736b125c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json @@ -59,7 +59,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { @@ -92,5 +93,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json index ca21e6cf9077c..5c81974ec6ce3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enumeration of Privileged Local Groups Membership", - "note": "## Triage and analysis\n\n### Investigating Enumeration of Privileged Local Groups Membership\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the enumeration of privileged local groups' membership by suspicious processes, and excludes known\nlegitimate utilities and programs installed. Attackers can use this information to decide the next steps of the attack,\nsuch as mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Identify the process, host and user involved on the event.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n- Retrieve the process executable and determine if it is malicious:\n - Check if the file belongs to the operating system or has a valid digital signature.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\nThe 'Audit Security Group Management' audit policy must be configured (Success).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nAccount Management >\nAudit Security Group Management (Success)\n```\n\nMicrosoft introduced the [event used](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4799) in this detection rule on Windows 10 and Windows Server 2016 or later operating systems.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", + "note": "## Triage and analysis\n\n### Investigating Enumeration of Privileged Local Groups Membership\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the enumeration of privileged local groups' membership by suspicious processes, and excludes known\nlegitimate utilities and programs installed. Attackers can use this information to decide the next steps of the attack,\nsuch as mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Identify the process, host and user involved on the event.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Retrieve the process executable and determine if it is malicious:\n - Check if the file belongs to the operating system or has a valid digital signature.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\nThe 'Audit Security Group Management' audit policy must be configured (Success).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nAccount Management >\nAudit Security Group Management (Success)\n```\n\nMicrosoft introduced the [event used](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4799) in this detection rule on Windows 10 and Windows Server 2016 or later operating systems.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", "query": "iam where event.action == \"user-member-enumerated\" and\n\n /* excluding machine account */\n not winlog.event_data.SubjectUserName: (\"*$\", \"LOCAL SERVICE\", \"NETWORK SERVICE\") and\n\n /* noisy and usual legit processes excluded */\n not winlog.event_data.CallerProcessName:\n (\"-\",\n \"?:\\\\Windows\\\\System32\\\\VSSVC.exe\",\n \"?:\\\\Windows\\\\System32\\\\SearchIndexer.exe\",\n \"?:\\\\Windows\\\\System32\\\\CompatTelRunner.exe\",\n \"?:\\\\Windows\\\\System32\\\\oobe\\\\msoobe.exe\",\n \"?:\\\\Windows\\\\System32\\\\net1.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\Netplwiz.exe\",\n \"?:\\\\Windows\\\\System32\\\\msiexec.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\msiexec.exe\",\n \"?:\\\\Windows\\\\System32\\\\CloudExperienceHostBroker.exe\",\n \"?:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe\",\n \"?:\\\\Windows\\\\System32\\\\SrTasks.exe\",\n \"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"?:\\\\Windows\\\\System32\\\\diskshadow.exe\",\n \"?:\\\\Windows\\\\System32\\\\dfsrs.exe\",\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\WindowsAzure\\\\*\\\\WaAppAgent.exe\",\n \"?:\\\\Windows\\\\System32\\\\vssadmin.exe\",\n \"?:\\\\Windows\\\\VeeamVssSupport\\\\VeeamGuestHelper.exe\",\n \"?:\\\\Windows\\\\System32\\\\dllhost.exe\",\n \"?:\\\\Windows\\\\System32\\\\mmc.exe\",\n \"?:\\\\Windows\\\\System32\\\\SettingSyncHost.exe\",\n \"?:\\\\Windows\\\\ImmersiveControlPanel\\\\SystemSettings.exe\",\n \"?:\\\\Windows\\\\System32\\\\SystemSettingsAdminFlows.exe\",\n \"?:\\\\Windows\\\\Temp\\\\rubrik_vmware???\\\\snaptool.exe\",\n \"?:\\\\Windows\\\\System32\\\\inetsrv\\\\w3wp.exe\",\n \"?:\\\\$WINDOWS.~BT\\\\Sources\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\wsmprovhost.exe\",\n \"?:\\\\Windows\\\\System32\\\\spool\\\\drivers\\\\x64\\\\3\\\\x3jobt3?.exe\",\n \"?:\\\\Windows\\\\System32\\\\mstsc.exe\",\n \"?:\\\\Windows\\\\System32\\\\esentutl.exe\",\n \"?:\\\\Windows\\\\System32\\\\RecoveryDrive.exe\",\n \"?:\\\\Windows\\\\System32\\\\SystemPropertiesComputerName.exe\") and\n\n /* privileged local groups */\n (group.name:(\"admin*\",\"RemoteDesktopUsers\") or\n winlog.event_data.TargetSid:(\"S-1-5-32-544\",\"S-1-5-32-555\"))\n", "required_fields": [ { @@ -49,7 +49,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json index 2378e0c21b4e6..e0bf3a485e835 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote System Discovery Commands", - "note": "## Triage and analysis\n\n### Investigating Remote System Discovery Commands\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `arp` or `nbstat` utilities to enumerate remote systems in the environment,\nwhich is useful for attackers to identify lateral movement targets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Remote System Discovery Commands\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `arp` or `nbstat` utilities to enumerate remote systems in the environment,\nwhich is useful for attackers to identify lateral movement targets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n ((process.name : \"nbtstat.exe\" and process.args : (\"-n\", \"-s\")) or\n (process.name : \"arp.exe\" and process.args : \"-a\"))\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json index 1d81253496323..dbed80d94bef8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json @@ -14,7 +14,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Security Software Discovery via Grep", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Security Software Discovery via Grep\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `grep` utility with arguments compatible to the enumeration of the security\nsoftware installed on the host. Attackers can use this information to decide whether or not to infect a system, disable\nprotections, use bypasses, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence and whether they are located in expected locations.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\nprocess.name : \"grep\" and user.id != \"0\" and\n not process.parent.executable : \"/Library/Application Support/*\" and\n process.args :\n (\"Little Snitch*\",\n \"Avast*\",\n \"Avira*\",\n \"ESET*\",\n \"BlockBlock*\",\n \"360Sec*\",\n \"LuLu*\",\n \"KnockKnock*\",\n \"kav\",\n \"KIS\",\n \"RTProtectionDaemon*\",\n \"Malware*\",\n \"VShieldScanner*\",\n \"WebProtection*\",\n \"webinspectord*\",\n \"McAfee*\",\n \"isecespd*\",\n \"macmnsvc*\",\n \"masvc*\",\n \"kesl*\",\n \"avscan*\",\n \"guard*\",\n \"rtvscand*\",\n \"symcfgd*\",\n \"scmdaemon*\",\n \"symantec*\",\n \"sophos*\",\n \"osquery*\",\n \"elastic-endpoint*\"\n ) and\n not (process.args : \"Avast\" and process.args : \"Passwords\")\n", "required_fields": [ { @@ -81,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json index 4c4b1e6127340..5e0d757a0c232 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Security Software Discovery using WMIC", - "note": "## Triage and analysis\n\n### Investigating Security Software Discovery using WMIC\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `wmic` utility with arguments compatible to the enumeration of the security\nsoftware installed on the host. Attackers can use this information to decide whether or not to infect a system, disable\nprotections, use bypasses, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Security Software Discovery using WMIC\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `wmic` utility with arguments compatible to the enumeration of the security\nsoftware installed on the host. Attackers can use this information to decide whether or not to infect a system, disable\nprotections, use bypasses, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name:\"wmic.exe\" or process.pe.original_file_name:\"wmic.exe\") and\n process.args:\"/namespace:\\\\\\\\root\\\\SecurityCenter2\" and process.args:\"Get\"\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json index 801024ec2f04a..f27272de5cee0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json @@ -16,7 +16,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Whoami Process Activity", - "note": "## Triage and analysis\n\n### Investigating Whoami Process Activity\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `whoami` utility. Attackers commonly use this utility to measure their current\nprivileges, discover the current user, determine if a privilege escalation was successful, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- Account Discovery Command via SYSTEM Account - 2856446a-34e6-435b-9fb5-f8f040bfa7ed\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Whoami Process Activity\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `whoami` utility. Attackers commonly use this utility to measure their current\nprivileges, discover the current user, determine if a privilege escalation was successful, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- Account Discovery Command via SYSTEM Account - 2856446a-34e6-435b-9fb5-f8f040bfa7ed\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and process.name : \"whoami.exe\" and\n(\n\n (/* scoped for whoami execution under system privileges */\n (user.domain : (\"NT AUTHORITY\", \"NT-AUTORIT\u00c4T\", \"AUTORITE NT\", \"IIS APPPOOL\") or user.id : (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\")) and\n\n not (process.parent.name : \"cmd.exe\" and\n process.parent.args : (\"chcp 437>nul 2>&1 & C:\\\\WINDOWS\\\\System32\\\\whoami.exe /groups\",\n \"chcp 437>nul 2>&1 & %systemroot%\\\\system32\\\\whoami /user\",\n \"C:\\\\WINDOWS\\\\System32\\\\whoami.exe /groups\",\n \"*WINDOWS\\\\system32\\\\config\\\\systemprofile*\")) and\n not (process.parent.executable : \"C:\\\\Windows\\\\system32\\\\inetsrv\\\\appcmd.exe\" and process.parent.args : \"LIST\") and\n not process.parent.executable : (\"C:\\\\Program Files\\\\Microsoft Monitoring Agent\\\\Agent\\\\MonitoringHost.exe\",\n \"C:\\\\Program Files\\\\Cohesity\\\\cohesity_windows_agent_service.exe\")) or\n\n process.parent.name : (\"wsmprovhost.exe\", \"w3wp.exe\", \"wmiprvse.exe\", \"rundll32.exe\", \"regsvr32.exe\")\n\n)\n", "required_fields": [ { @@ -64,7 +64,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json index 6389a8a3ab80d..785c950719b8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Abnormal Process ID or Lock File Created", - "note": "## Triage and analysis\n\n### Investigating Abnormal Process ID or Lock File Created\nDetection alerts from this rule indicate that an unusual PID file was created and could potentially have alternate purposes during an intrusion. Here are some possible avenues of investigation:\n- Run the following in Osquery to quickly identify unsual PID file size: \"SELECT f.size, f.uid, f.type, f.path from file f WHERE path like '/var/run/%pid';\"\n- Examine the history of this file creation and from which process it was created by using the \"lsof\" command.\n- Examine the contents of the PID file itself, simply by running the \"cat\" command to determine if the expected process ID integer exists and if not, the PID file is not legitimate.\n- Examine the reputation of the SHA256 hash from the PID file in a database like VirusTotal to identify additional pivots and artifacts for investigation.", + "note": "## Triage and analysis\n\n### Investigating Abnormal Process ID or Lock File Created\n\nLinux applications may need to save their process identification number (PID) for various purposes: from signaling that\na program is running to serving as a signal that a previous instance of an application didn't exit successfully. PID\nfiles contain its creator process PID in an integer value.\n\nLinux lock files are used to coordinate operations in files so that conflicts and race conditions are prevented.\n\nThis rule identifies the creation of PID, lock, or reboot files in the /var/run/ directory. Attackers can masquerade\nmalware, payloads, staged data for exfiltration, and more as legitimate PID files.\n\n#### Possible investigation steps\n\n- Retrieve the file and determine if it is malicious:\n - Check the contents of the PID files. They should only contain integer strings.\n - Check the file type of the lock and PID files to determine if they are executables. This is only observed in\n malicious files.\n - Check the size of the subject file. Legitimate PID files should be under 10 bytes.\n - Check if the lock or PID file has high entropy. This typically indicates an encrypted payload.\n - Analysts can use tools like `ent` to measure entropy.\n - Examine the reputation of the SHA-256 hash in the PID file. Use a database like VirusTotal to identify additional\n pivots and artifacts for investigation.\n- Trace the file's creation to ensure it came from a legitimate or authorized process.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n\n### False positive analysis\n\n- False positives can appear if the PID file is legitimate and holding a process ID as intended. If the PID file is\nan executable or has a file size that's larger than 10 bytes, it should be ruled suspicious.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof file name and process executable conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Block the identified indicators of compromise (IoCs).\n- Take actions to terminate processes and connections used by the attacker.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "/* add file size filters when data is available */\nfile where event.type == \"creation\" and user.id == \"0\" and\n file.path regex~ \"\"\"/var/run/\\w+\\.(pid|lock|reboot)\"\"\" and file.extension in (\"pid\",\"lock\",\"reboot\") and\n\n /* handle common legitimate files */\n\n not file.name in (\n \"auditd.pid\",\n \"python*\",\n \"apport.pid\",\n \"apport.lock\",\n \"kworker*\",\n \"gdm3.pid\",\n \"sshd.pid\",\n \"acpid.pid\",\n \"unattended-upgrades.lock\",\n \"unattended-upgrades.pid\",\n \"cmd.pid\",\n \"cron*.pid\",\n \"yum.pid\",\n \"netconfig.pid\",\n \"docker.pid\",\n \"atd.pid\",\n \"lfd.pid\",\n \"atop.pid\",\n \"nginx.pid\",\n \"dhclient.pid\",\n \"smtpd.pid\",\n \"stunnel.pid\"\n )\n", "references": [ "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", @@ -77,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json index c14551a7a751e..7cea863e990a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Svchost spawning Cmd", - "note": "## Triage and analysis\n\n### Investigating Svchost spawning Cmd\n\nThe Service Host process (SvcHost) is a system process that can host one, or multiple, Windows services in the Windows\nNT family of operating systems. Note that `Svchost.exe` is reserved for use by the operating system and should not be\nused by non-Windows services.\n\nThis rule looks for the creation of the `cmd.exe` process with `svchost.exe` as its parent process. This is an unusual\nbehavior that can indicate the masquerading of a malicious process as `svchost.exe` or exploitation for privilege\nescalation.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Svchost spawning Cmd\n\nThe Service Host process (SvcHost) is a system process that can host one, or multiple, Windows services in the Windows\nNT family of operating systems. Note that `Svchost.exe` is reserved for use by the operating system and should not be\nused by non-Windows services.\n\nThis rule looks for the creation of the `cmd.exe` process with `svchost.exe` as its parent process. This is an unusual\nbehavior that can indicate the masquerading of a malicious process as `svchost.exe` or exploitation for privilege\nescalation.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n\n process.parent.name : \"svchost.exe\" and process.name : \"cmd.exe\" and\n\n not process.args :\n (\"??:\\\\Program Files\\\\Npcap\\\\CheckStatus.bat?\",\n \"?:\\\\Program Files\\\\Npcap\\\\CheckStatus.bat\",\n \"\\\\system32\\\\cleanmgr.exe\",\n \"?:\\\\Windows\\\\system32\\\\silcollector.cmd\",\n \"\\\\system32\\\\AppHostRegistrationVerifier.exe\",\n \"\\\\system32\\\\ServerManagerLauncher.exe\",\n \"dir\",\n \"?:\\\\Program Files\\\\*\",\n \"?:\\\\Program Files (x86)\\\\*\",\n \"?:\\\\Windows\\\\LSDeployment\\\\Lspush.exe\",\n \"(x86)\\\\FMAuditOnsite\\\\watchdog.bat\",\n \"?:\\\\ProgramData\\\\chocolatey\\\\bin\\\\choco-upgrade-all.bat\",\n \"Files\\\\Npcap\\\\CheckStatus.bat\") and\n\n /* very noisy pattern - bat or cmd script executed via scheduled tasks */\n not (process.parent.args : \"netsvcs\" and process.args : (\"?:\\\\*.bat\", \"?:\\\\*.cmd\"))\n", "references": [ "https://nasbench.medium.com/demystifying-the-svchost-exe-process-and-its-command-line-options-508e9114e747" @@ -53,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -76,5 +77,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json index a63258dc96af6..2221085a9b518 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution from Unusual Directory - Command Line", - "note": "## Triage and analysis\n\nThis is related to the `Process Execution from an Unusual Directory rule`.", + "note": "## Triage and analysis\n\n### Investigating Execution from Unusual Directory - Command Line\n\nThis rule looks for the execution of scripts from unusual directories. Attackers can use system or application paths to\nhide malware and make the execution less suspicious.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine the command line to determine which commands or scripts were executed.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof parent process executable and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\nThis is related to the `Process Execution from an Unusual Directory rule`.", "query": "process where event.type == \"start\" and\n process.name : (\"wscript.exe\",\n \"cscript.exe\",\n \"rundll32.exe\",\n \"regsvr32.exe\",\n \"cmstp.exe\",\n \"RegAsm.exe\",\n \"installutil.exe\",\n \"mshta.exe\",\n \"RegSvcs.exe\",\n \"powershell.exe\",\n \"pwsh.exe\",\n \"cmd.exe\") and\n\n /* add suspicious execution paths here */\n process.args : (\"C:\\\\PerfLogs\\\\*\",\n \"C:\\\\Users\\\\Public\\\\*\",\n \"C:\\\\Windows\\\\Tasks\\\\*\",\n \"C:\\\\Intel\\\\*\",\n \"C:\\\\AMD\\\\Temp\\\\*\",\n \"C:\\\\Windows\\\\AppReadiness\\\\*\",\n \"C:\\\\Windows\\\\ServiceState\\\\*\",\n \"C:\\\\Windows\\\\security\\\\*\",\n \"C:\\\\Windows\\\\IdentityCRL\\\\*\",\n \"C:\\\\Windows\\\\Branding\\\\*\",\n \"C:\\\\Windows\\\\csc\\\\*\",\n \"C:\\\\Windows\\\\DigitalLocker\\\\*\",\n \"C:\\\\Windows\\\\en-US\\\\*\",\n \"C:\\\\Windows\\\\wlansvc\\\\*\",\n \"C:\\\\Windows\\\\Prefetch\\\\*\",\n \"C:\\\\Windows\\\\Fonts\\\\*\",\n \"C:\\\\Windows\\\\diagnostics\\\\*\",\n \"C:\\\\Windows\\\\TAPI\\\\*\",\n \"C:\\\\Windows\\\\INF\\\\*\",\n \"C:\\\\Windows\\\\System32\\\\Speech\\\\*\",\n \"C:\\\\windows\\\\tracing\\\\*\",\n \"c:\\\\windows\\\\IME\\\\*\",\n \"c:\\\\Windows\\\\Performance\\\\*\",\n \"c:\\\\windows\\\\intel\\\\*\",\n \"c:\\\\windows\\\\ms\\\\*\",\n \"C:\\\\Windows\\\\dot3svc\\\\*\",\n \"C:\\\\Windows\\\\panther\\\\*\",\n \"C:\\\\Windows\\\\RemotePackages\\\\*\",\n \"C:\\\\Windows\\\\OCR\\\\*\",\n \"C:\\\\Windows\\\\appcompat\\\\*\",\n \"C:\\\\Windows\\\\apppatch\\\\*\",\n \"C:\\\\Windows\\\\addins\\\\*\",\n \"C:\\\\Windows\\\\Setup\\\\*\",\n \"C:\\\\Windows\\\\Help\\\\*\",\n \"C:\\\\Windows\\\\SKB\\\\*\",\n \"C:\\\\Windows\\\\Vss\\\\*\",\n \"C:\\\\Windows\\\\servicing\\\\*\",\n \"C:\\\\Windows\\\\CbsTemp\\\\*\",\n \"C:\\\\Windows\\\\Logs\\\\*\",\n \"C:\\\\Windows\\\\WaaS\\\\*\",\n \"C:\\\\Windows\\\\twain_32\\\\*\",\n \"C:\\\\Windows\\\\ShellExperiences\\\\*\",\n \"C:\\\\Windows\\\\ShellComponents\\\\*\",\n \"C:\\\\Windows\\\\PLA\\\\*\",\n \"C:\\\\Windows\\\\Migration\\\\*\",\n \"C:\\\\Windows\\\\debug\\\\*\",\n \"C:\\\\Windows\\\\Cursors\\\\*\",\n \"C:\\\\Windows\\\\Containers\\\\*\",\n \"C:\\\\Windows\\\\Boot\\\\*\",\n \"C:\\\\Windows\\\\bcastdvr\\\\*\",\n \"C:\\\\Windows\\\\TextInput\\\\*\",\n \"C:\\\\Windows\\\\security\\\\*\",\n \"C:\\\\Windows\\\\schemas\\\\*\",\n \"C:\\\\Windows\\\\SchCache\\\\*\",\n \"C:\\\\Windows\\\\Resources\\\\*\",\n \"C:\\\\Windows\\\\rescache\\\\*\",\n \"C:\\\\Windows\\\\Provisioning\\\\*\",\n \"C:\\\\Windows\\\\PrintDialog\\\\*\",\n \"C:\\\\Windows\\\\PolicyDefinitions\\\\*\",\n \"C:\\\\Windows\\\\media\\\\*\",\n \"C:\\\\Windows\\\\Globalization\\\\*\",\n \"C:\\\\Windows\\\\L2Schemas\\\\*\",\n \"C:\\\\Windows\\\\LiveKernelReports\\\\*\",\n \"C:\\\\Windows\\\\ModemLogs\\\\*\",\n \"C:\\\\Windows\\\\ImmersiveControlPanel\\\\*\",\n \"C:\\\\$Recycle.Bin\\\\*\") and\n\n /* noisy FP patterns */\n\n not process.parent.executable : (\"C:\\\\WINDOWS\\\\System32\\\\DriverStore\\\\FileRepository\\\\*\\\\igfxCUIService*.exe\",\n \"C:\\\\Windows\\\\System32\\\\spacedeskService.exe\",\n \"C:\\\\Program Files\\\\Dell\\\\SupportAssistAgent\\\\SRE\\\\SRE.exe\") and\n not (process.name : \"rundll32.exe\" and\n process.args : (\"uxtheme.dll,#64\",\n \"PRINTUI.DLL,PrintUIEntry\",\n \"?:\\\\Windows\\\\System32\\\\FirewallControlPanel.dll,ShowNotificationDialog\",\n \"?:\\\\WINDOWS\\\\system32\\\\Speech\\\\SpeechUX\\\\sapi.cpl\",\n \"?:\\\\Windows\\\\system32\\\\shell32.dll,OpenAs_RunDLL\")) and\n\n not (process.name : \"cscript.exe\" and process.args : \"?:\\\\WINDOWS\\\\system32\\\\calluxxprovider.vbs\") and\n\n not (process.name : \"cmd.exe\" and process.args : \"?:\\\\WINDOWS\\\\system32\\\\powercfg.exe\" and process.args : \"?:\\\\WINDOWS\\\\inf\\\\PowerPlan.log\") and\n\n not (process.name : \"regsvr32.exe\" and process.args : \"?:\\\\Windows\\\\Help\\\\OEM\\\\scripts\\\\checkmui.dll\") and\n\n not (process.name : \"cmd.exe\" and\n process.parent.executable : (\"?:\\\\Windows\\\\System32\\\\oobe\\\\windeploy.exe\",\n \"?:\\\\Program Files (x86)\\\\ossec-agent\\\\wazuh-agent.exe\",\n \"?:\\\\Windows\\\\System32\\\\igfxCUIService.exe\",\n \"?:\\\\Windows\\\\Temp\\\\IE*.tmp\\\\IE*-support\\\\ienrcore.exe\"))\n", "required_fields": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json index bd0d4ee978b74..ce07c4a04bfca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json @@ -14,6 +14,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Netcat Network Activity", + "note": "## Triage and analysis\n\n### Investigating Netcat Network Activity\n\nNetcat is a dual-use command line tool that can be used for various purposes, such as port scanning, file transfers, and\nconnection tests. Attackers can abuse its functionality for malicious purposes such creating bind shells or reverse\nshells to gain access to the target system.\n\nA reverse shell is a mechanism that's abused to connect back to an attacker-controlled system. It effectively redirects\nthe system's input and output and delivers a fully functional remote shell to the attacker. Even private systems are\nvulnerable since the connection is outgoing.\n\nA bind shell is a type of backdoor that attackers set up on the target host and binds to a specific port to listen for\nan incoming connection from the attacker.\n\nThis rule identifies potential reverse shell or bind shell activity using Netcat by checking for the execution of Netcat\nfollowed by a network connection.\n\n#### Possible investigation steps\n\n- Examine the command line to identify if the command is suspicious.\n- Extract and examine the target domain or IP address.\n - Check if the domain is newly registered or unexpected.\n - Check the reputation of the domain or IP address.\n - Scope other potentially compromised hosts in your environment by mapping hosts that also communicated with the\n domain or IP address.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n\n### False positive analysis\n\n- Netcat is a dual-use tool that can be used for benign or malicious activity. It is included in some Linux\ndistributions, so its presence is not necessarily suspicious. Some normal use of this program, while uncommon, may\noriginate from scripts, automation tools, and frameworks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Block the identified indicators of compromise (IoCs).\n- Take actions to terminate processes and connections used by the attacker.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by process.entity_id\n [process where (process.name == \"nc\" or process.name == \"ncat\" or process.name == \"netcat\" or\n process.name == \"netcat.openbsd\" or process.name == \"netcat.traditional\") and\n event.type == \"start\"]\n [network where (process.name == \"nc\" or process.name == \"ncat\" or process.name == \"netcat\" or\n process.name == \"netcat.openbsd\" or process.name == \"netcat.traditional\")]\n", "references": [ "http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet", @@ -65,5 +66,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json index eaad2e49cd4f7..89f6364c7571c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution of File Written or Modified by Microsoft Office", - "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by Microsoft Office\n\nMicrosoft Office is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently\ntargeted for initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule searches for executable files written by MS Office applications executed in sequence. This is most likely the result\nof the execution of malicious documents or exploitation for initial access or privilege escalation. This rule can also detect\nsuspicious processes masquerading as the MS Office applications.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by Microsoft Office\n\nMicrosoft Office is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently\ntargeted for initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule searches for executable files written by MS Office applications executed in sequence. This is most likely the result\nof the execution of malicious documents or exploitation for initial access or privilege escalation. This rule can also detect\nsuspicious processes masquerading as the MS Office applications.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=2h\n [file where event.type != \"deletion\" and file.extension : \"exe\" and\n (process.name : \"WINWORD.EXE\" or\n process.name : \"EXCEL.EXE\" or\n process.name : \"OUTLOOK.EXE\" or\n process.name : \"POWERPNT.EXE\" or\n process.name : \"eqnedt32.exe\" or\n process.name : \"fltldr.exe\" or\n process.name : \"MSPUB.EXE\" or\n process.name : \"MSACCESS.EXE\")\n ] by host.id, file.path\n [process where event.type == \"start\"] by host.id, process.executable\n", "required_fields": [ { @@ -55,7 +55,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json index 84871a5b9d608..e525b02a6efd5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution of File Written or Modified by PDF Reader", - "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by PDF Reader\n\nPDF is a common file type used in corporate environments and most machines have software to\nhandle these files. This creates a vector where attackers can exploit the engines and technology behind this class of\nsoftware for initial access or privilege escalation.\n\nThis rule searches for executable files written by PDF reader software and executed in sequence. This is most likely the\nresult of exploitation for privilege escalation or initial access. This rule can also detect suspicious processes masquerading as\nPDF readers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the PDF documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by PDF Reader\n\nPDF is a common file type used in corporate environments and most machines have software to\nhandle these files. This creates a vector where attackers can exploit the engines and technology behind this class of\nsoftware for initial access or privilege escalation.\n\nThis rule searches for executable files written by PDF reader software and executed in sequence. This is most likely the\nresult of exploitation for privilege escalation or initial access. This rule can also detect suspicious processes masquerading as\nPDF readers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the PDF documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=2h\n [file where event.type != \"deletion\" and file.extension : \"exe\" and\n (process.name : \"AcroRd32.exe\" or\n process.name : \"rdrcef.exe\" or\n process.name : \"FoxitPhantomPDF.exe\" or\n process.name : \"FoxitReader.exe\") and\n not (file.name : \"FoxitPhantomPDF.exe\" or\n file.name : \"FoxitPhantomPDFUpdater.exe\" or\n file.name : \"FoxitReader.exe\" or\n file.name : \"FoxitReaderUpdater.exe\" or\n file.name : \"AcroRd32.exe\" or\n file.name : \"rdrcef.exe\")\n ] by host.id, file.path\n [process where event.type == \"start\"] by host.id, process.executable\n", "required_fields": [ { @@ -60,7 +60,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json index 538dc16e045d3..50a63ceed93a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Portable Executable Encoded in Powershell Script", - "note": "## Triage and analysis\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell in-memory capabilities to inject executables into memory without touching the disk,\nbypassing file-based security protections. These executables are generally base64 encoded.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell in-memory capabilities to inject executables into memory without touching the disk,\nbypassing file-based security protections. These executables are generally base64 encoded.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "event.category:process and\n powershell.file.script_block_text : (\n TVqQAAMAAAAEAAAA\n )\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0109_windows_powershell_script_block_log.md" @@ -37,7 +37,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -65,5 +66,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json index e775d8bf73942..ac618f2a58cd4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell PSReflect Script", - "note": "## Triage and analysis\n\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses Windows API through\nPowerShell, enabling defenders to discover tools being dropped in the environment.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics. The\nscript content that may be split into multiple script blocks (you can use the field `powershell.file.script_block_id`\nfor filtering).\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Check for additional PowerShell and command-line logs that indicate that imported functions were run.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses Windows API through\nPowerShell, enabling defenders to discover tools being dropped in the environment.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics. The\nscript content that may be split into multiple script blocks (you can use the field `powershell.file.script_block_id`\nfor filtering).\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Check for additional PowerShell and command-line logs that indicate that imported functions were run.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "event.category:process and\n powershell.file.script_block_text:(\n \"New-InMemoryModule\" or\n \"Add-Win32Type\" or\n psenum or\n DefineDynamicAssembly or\n DefineDynamicModule or\n \"Reflection.TypeAttributes\" or\n \"Reflection.Emit.OpCodes\" or\n \"Reflection.Emit.CustomAttributeBuilder\" or\n \"Runtime.InteropServices.DllImportAttribute\"\n )\n", "references": [ "https://github.com/mattifestation/PSReflect/blob/master/PSReflect.psm1", @@ -41,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -74,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json index 72d8ed1fdaffc..a293878687a6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json @@ -57,7 +57,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -93,5 +94,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json index 85ed673ec33e6..fbefe7152d9c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Reverse Shell Activity via Terminal", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Potential Reverse Shell Activity via Terminal\n\nA reverse shell is a mechanism that's abused to connect back to an attacker-controlled system. It effectively redirects\nthe system's input and output and delivers a fully functional remote shell to the attacker. Even private systems are\nvulnerable since the connection is outgoing. This activity is typically the result of vulnerability exploitation,\nmalware infection, or penetration testing.\n\nThis rule identifies commands that are potentially related to reverse shell activities using shell applications.\n\n#### Possible investigation steps\n\n- Examine the command line and extract the target domain or IP address information.\n - Check if the domain is newly registered or unexpected.\n - Check the reputation of the domain or IP address.\n - Scope other potentially compromised hosts in your environment by mapping hosts that also communicated with the\n domain or IP address.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Take actions to terminate processes and connections used by the attacker.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name in (\"sh\", \"bash\", \"zsh\", \"dash\", \"zmodload\") and\n process.args : (\"*/dev/tcp/*\", \"*/dev/udp/*\", \"*zsh/net/tcp*\", \"*zsh/net/udp*\") and\n\n /* noisy FPs */\n not (process.parent.name : \"timeout\" and process.executable : \"/var/lib/docker/overlay*\") and\n not process.command_line : (\"*/dev/tcp/sirh_db/*\", \"*/dev/tcp/remoteiot.com/*\", \"*dev/tcp/elk.stag.one/*\", \"*dev/tcp/kafka/*\", \"*/dev/tcp/$0/$1*\", \"*/dev/tcp/127.*\", \"*/dev/udp/127.*\", \"*/dev/tcp/localhost/*\") and\n not process.parent.command_line : \"runc init\"\n", "references": [ "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md", @@ -86,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json index 629a5b1f21401..5e742ae4ed4bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious JAVA Child Process", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Suspicious Java Child Process\n\nThis rule identifies a suspicious child process of the Java interpreter process. It may indicate an attempt to execute\na malicious JAR file or an exploitation attempt via a Java specific vulnerability.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence and whether they are located in expected locations.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n- Examine the command line to determine if the command executed is potentially harmful or malicious.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n\n### False positive analysis\n\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof process and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"java\" and\n process.name : (\"sh\", \"bash\", \"dash\", \"ksh\", \"tcsh\", \"zsh\", \"curl\", \"wget\")\n", "references": [ "https://www.lunasec.io/docs/blog/log4j-zero-day/", @@ -73,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json index 3bf8084fe3659..3eab94d7a63e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious PDF Reader Child Process", - "note": "## Triage and analysis\n\n### Investigating Suspicious PDF Reader Child Process\n\nPDF is a common file type used in corporate environments and most machines have software to handle these files. This\ncreates a vector where attackers can exploit the engines and technology behind this class of software for initial access\nor privilege escalation.\n\nThis rule looks for commonly abused built-in utilities spawned by a PDF reader process, which is likely a malicious behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve PDF documents received and opened by the user that could cause this behavior. Common locations include, but\nare not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious PDF Reader Child Process\n\nPDF is a common file type used in corporate environments and most machines have software to handle these files. This\ncreates a vector where attackers can exploit the engines and technology behind this class of software for initial access\nor privilege escalation.\n\nThis rule looks for commonly abused built-in utilities spawned by a PDF reader process, which is likely a malicious behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve PDF documents received and opened by the user that could cause this behavior. Common locations include, but\nare not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"AcroRd32.exe\",\n \"Acrobat.exe\",\n \"FoxitPhantomPDF.exe\",\n \"FoxitReader.exe\") and\n process.name : (\"arp.exe\", \"dsquery.exe\", \"dsget.exe\", \"gpresult.exe\", \"hostname.exe\", \"ipconfig.exe\", \"nbtstat.exe\",\n \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"ping.exe\", \"qprocess.exe\",\n \"quser.exe\", \"qwinsta.exe\", \"reg.exe\", \"sc.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\",\n \"whoami.exe\", \"bginfo.exe\", \"cdb.exe\", \"cmstp.exe\", \"csi.exe\", \"dnx.exe\", \"fsi.exe\", \"ieexec.exe\",\n \"iexpress.exe\", \"installutil.exe\", \"Microsoft.Workflow.Compiler.exe\", \"msbuild.exe\", \"mshta.exe\",\n \"msxsl.exe\", \"odbcconf.exe\", \"rcsi.exe\", \"regsvr32.exe\", \"xwizard.exe\", \"atbroker.exe\",\n \"forfiles.exe\", \"schtasks.exe\", \"regasm.exe\", \"regsvcs.exe\", \"cmd.exe\", \"cscript.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"wmic.exe\", \"wscript.exe\", \"bitsadmin.exe\", \"certutil.exe\", \"ftp.exe\")\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json index 652689a233566..b1b2b87bc5d87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious PowerShell Engine ImageLoad", - "note": "## Triage and analysis\n\n### Investigating Suspicious PowerShell Engine ImageLoad\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell without having to execute `PowerShell.exe` directly. This technique, often called\n\"PowerShell without PowerShell,\" works by using the underlying System.Management.Automation namespace and can bypass\napplication allowlisting and PowerShell security features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Retrieve the implementation (DLL, executable, etc.) and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Some vendors have their own PowerShell implementations that are shipped with\nsome products. These benign true positives (B-TPs) can be added as exceptions if necessary after analysis.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", + "note": "## Triage and analysis\n\n### Investigating Suspicious PowerShell Engine ImageLoad\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell without having to execute `PowerShell.exe` directly. This technique, often called\n\"PowerShell without PowerShell,\" works by using the underlying System.Management.Automation namespace and can bypass\napplication allowlisting and PowerShell security features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Retrieve the implementation (DLL, executable, etc.) and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Some vendors have their own PowerShell implementations that are shipped with\nsome products. These benign true positives (B-TPs) can be added as exceptions if necessary after analysis.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", "query": "any where (event.category == \"library\" or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n (dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") or\n file.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\")) and\n\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\") and\nnot process.executable regex~ \"\"\"C:\\\\Program Files( \\(x86\\))?\\\\*\\.exe\"\"\" and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", "required_fields": [ { @@ -55,7 +55,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -83,5 +84,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json index 4f6e8c63b7be8..873ed4a120f19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Conhost Spawned By Suspicious Parent Process", - "note": "## Triage and analysis\n\n### Investigating Conhost Spawned By Suspicious Parent Process\n\nThe Windows Console Host, or `conhost.exe`, is both the server application for all of the Windows Console APIs as well as\nthe classic Windows user interface for working with command-line applications.\n\nAttackers often rely on custom shell implementations to avoid using built-in command interpreters like `cmd.exe` and\n`PowerShell.exe` and bypass application allowlisting and security features. Attackers commonly inject these implementations into\nlegitimate system processes.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Retrieve the parent process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Process from Conhost - 28896382-7d4f-4d50-9b72-67091901fd26\n- Suspicious PowerShell Engine ImageLoad - 852c1f19-68e8-43a6-9dce-340771fe1be3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Conhost Spawned By Suspicious Parent Process\n\nThe Windows Console Host, or `conhost.exe`, is both the server application for all of the Windows Console APIs as well as\nthe classic Windows user interface for working with command-line applications.\n\nAttackers often rely on custom shell implementations to avoid using built-in command interpreters like `cmd.exe` and\n`PowerShell.exe` and bypass application allowlisting and security features. Attackers commonly inject these implementations into\nlegitimate system processes.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Retrieve the parent process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Process from Conhost - 28896382-7d4f-4d50-9b72-67091901fd26\n- Suspicious PowerShell Engine ImageLoad - 852c1f19-68e8-43a6-9dce-340771fe1be3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.name : \"conhost.exe\" and\n process.parent.name : (\"lsass.exe\", \"services.exe\", \"smss.exe\", \"winlogon.exe\", \"explorer.exe\", \"dllhost.exe\", \"rundll32.exe\",\n \"regsvr32.exe\", \"userinit.exe\", \"wininit.exe\", \"spoolsv.exe\", \"ctfmon.exe\") and\n not (process.parent.name : \"rundll32.exe\" and\n process.parent.args : (\"?:\\\\Windows\\\\Installer\\\\MSI*.tmp,zzzzInvokeManagedCustomActionOutOfProc\",\n \"?:\\\\WINDOWS\\\\system32\\\\PcaSvc.dll,PcaPatchSdbTask\",\n \"?:\\\\WINDOWS\\\\system32\\\\davclnt.dll,DavSetCookie\"))\n", "references": [ "https://www.fireeye.com/blog/threat-research/2017/08/monitoring-windows-console-activity-part-one.html" @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json index fdc2114f9c36d..2b55041c64fe7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json @@ -12,8 +12,11 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution via MSSQL xp_cmdshell Stored Procedure", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Execution via MSSQL xp_cmdshell Stored Procedure\n\nMicrosoft SQL Server (MSSQL) has procedures meant to extend its functionality, the Extended Stored Procedures. These\nprocedures are external functions written in C/C++; some provide interfaces for external programs. This is the case for\nxp_cmdshell, which spawns a Windows command shell and passes in a string for execution. Attackers can use this to\nexecute commands on the system running the SQL server, commonly to escalate their privileges and establish persistence.\n\nThe xp_cmdshell procedure is disabled by default, but when used, it has the same security context as the MSSQL Server\nservice account, which is often privileged.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Examine the command line to determine if the command executed is potentially harmful or malicious.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately, but it brings inherent risk. The security team must monitor any activity of\nit. If recurrent tasks are being executed using this mechanism, consider adding exceptions \u2014 preferably with a full\ncommand line.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Ensure that SQL servers are not directly exposed to the internet. If there is a business justification for such, use\nan allowlist to allow only connections from known legitimate sources.\n- Disable the xp_cmdshell stored procedure.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.name : \"cmd.exe\" and process.parent.name : \"sqlservr.exe\" and\n not process.args : (\"\\\\\\\\*\", \"diskfree\", \"rmdir\", \"mkdir\", \"dir\", \"del\", \"rename\", \"bcp\", \"*XMLNAMESPACES*\",\n \"?:\\\\MSSQL\\\\Backup\\\\Jobs\\\\sql_agent_backup_job.ps1\", \"K:\\\\MSSQL\\\\Backup\\\\msdb\", \"K:\\\\MSSQL\\\\Backup\\\\Logins\")\n", + "references": [ + "https://thedfirreport.com/2022/07/11/select-xmrig-from-sqlserver/" + ], "required_fields": [ { "ecs": true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json index c39d0637ab983..d8de82c5251a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json @@ -56,7 +56,8 @@ "Continuous Monitoring", "SecOps", "Asset Visibility", - "Exfiltration" + "Exfiltration", + "has_guide" ], "threat": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json index 5c0cae49bde07..73d7ed7c943bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json @@ -46,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -67,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json index 84403d4b48ded..80ad1350df659 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Log Auditing" + "Log Auditing", + "has_guide" ], "threat": [ { @@ -103,5 +104,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json index 0e28f8255ffb2..5734ffe4c312a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json @@ -60,7 +60,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Log Auditing" + "Log Auditing", + "has_guide" ], "threat": [ { @@ -103,5 +104,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json index fa184b0ea2c37..04870a18138e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json @@ -61,7 +61,8 @@ "Continuous Monitoring", "SecOps", "Log Auditing", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -104,5 +105,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json index 9b83fccb1fb17..0136fbd4adc2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json index bbaed9c02d794..6095c3c3a3923 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json @@ -64,7 +64,8 @@ "Continuous Monitoring", "SecOps", "Configuration Audit", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -85,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json index a5970f7a09fbd..a7ce614def97f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Hosts File Modified", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Hosts File Modified\n\nOperating systems use the hosts file to map a connection between an IP address and domain names before going to domain\nname servers. Attackers can abuse this mechanism to route traffic to malicious infrastructure or disrupt security that\ndepends on server communications. For example, Russian threat actors modified this file on a domain controller to\nredirect Duo MFA calls to localhost instead of the Duo server, which prevented the MFA service from contacting its\nserver to validate MFA login. This effectively disabled MFA for active domain accounts because the default policy of Duo\nfor Windows is to \"Fail open\" if the MFA server is unreachable. This can happen in any MFA implementation and is not\nexclusive to Duo. Find more details in this [CISA Alert](https://www.cisa.gov/uscert/ncas/alerts/aa22-074a).\n\nThis rule identifies modifications in the hosts file across multiple operating systems using process creation events for\nLinux and file events in Windows and macOS.\n\n#### Possible investigation steps\n\n- Identify the specifics of the involved assets, such as role, criticality, and associated users.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine the changes to the hosts file by comparing it against file backups, volume shadow copies, and other restoration\nmechanisms.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand the configuration was justified.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges of the administrator account that performed the action.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "any where\n\n /* file events for creation; file change events are not captured by some of the included sources for linux and so may\n miss this, which is the purpose of the process + command line args logic below */\n (\n event.category == \"file\" and event.type in (\"change\", \"creation\") and\n file.path : (\"/private/etc/hosts\", \"/etc/hosts\", \"?:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts\")\n )\n or\n\n /* process events for change targeting linux only */\n (\n event.category == \"process\" and event.type in (\"start\") and\n process.name in (\"nano\", \"vim\", \"vi\", \"emacs\", \"echo\", \"sed\") and\n process.args : (\"/etc/hosts\")\n )\n", "references": [ "https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-reference-yml.html" @@ -86,5 +86,5 @@ "timeline_title": "Comprehensive File Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json index 864a9c9e814f6..49c4f32551554 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json @@ -61,7 +61,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Monitoring" + "Monitoring", + "has_guide" ], "threat": [ { @@ -82,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json index 2952696f91d6b..256e8a869d076 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json index 0a86a88da1140..18700e2d4bbac 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json @@ -42,7 +42,8 @@ "Host", "Linux", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -68,5 +69,5 @@ "value": 10 }, "type": "threshold", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json index cb9344a9276ee..8204204bb34c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json @@ -44,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -70,5 +71,5 @@ "value": 10 }, "type": "threshold", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json index e640e7778107d..a4dbdf6df2385 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deleted or Resized via VssAdmin", - "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deleted or Resized via VssAdmin\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of Vssadmin.exe to either delete or resize shadow copies.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule may produce benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deleted or Resized via VssAdmin\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of Vssadmin.exe to either delete or resize shadow copies.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule may produce benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\"\n and (process.name : \"vssadmin.exe\" or process.pe.original_file_name == \"VSSADMIN.EXE\") and\n process.args in (\"delete\", \"resize\") and process.args : \"shadows*\"\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json index fefcf9f192ded..04563fc72f525 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deletion via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via PowerShell\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of PowerShell cmdlets to interact with the Win32_ShadowCopy WMI class, retrieve shadow\ncopy objects, and delete them.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via PowerShell\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of PowerShell cmdlets to interact with the Win32_ShadowCopy WMI class, retrieve shadow\ncopy objects, and delete them.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and\n process.args : (\"*Get-WmiObject*\", \"*gwmi*\", \"*Get-CimInstance*\", \"*gcim*\") and\n process.args : (\"*Win32_ShadowCopy*\") and\n process.args : (\"*.Delete()*\", \"*Remove-WmiObject*\", \"*rwmi*\", \"*Remove-CimInstance*\", \"*rcim*\")\n", "references": [ "https://docs.microsoft.com/en-us/previous-versions/windows/desktop/vsswmi/win32-shadowcopy", @@ -46,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json index 0844dc6d0148d..eac82475417b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deletion via WMIC", - "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via WMIC\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of `wmic.exe` to interact with VSS via the `shadowcopy` alias and delete parameter.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via WMIC\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of `wmic.exe` to interact with VSS via the `shadowcopy` alias and delete parameter.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n process.args : \"delete\" and process.args : \"shadowcopy\"\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 7d30ff7502a90..97878f9eb2e0b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -368,7 +368,7 @@ import rule355 from './persistence_google_workspace_api_access_granted_via_domai import rule356 from './defense_evasion_suspicious_short_program_name.json'; import rule357 from './lateral_movement_incoming_wmi.json'; import rule358 from './persistence_via_hidden_run_key_valuename.json'; -import rule359 from './credential_access_potential_ssh_bruteforce.json'; +import rule359 from './credential_access_potential_macos_ssh_bruteforce.json'; import rule360 from './credential_access_promt_for_pwd_via_osascript.json'; import rule361 from './lateral_movement_remote_services.json'; import rule362 from './defense_evasion_domain_added_to_google_workspace_trusted_domains.json'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json index 3dec3ef0087ae..d06a073fd504c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json @@ -57,7 +57,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -78,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json index c1aa77df2db99..c2d57924a39e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json @@ -52,7 +52,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json index e6660c2dc1251..d46d34a762cce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json @@ -58,7 +58,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -86,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json index 9ce6e6ddaee9d..01a68b944d294 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json @@ -70,7 +70,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -113,5 +114,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json index 48b42a0351b9e..7612d79572c17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json @@ -64,7 +64,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -100,5 +101,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json index 8c707115c0bb6..564667506aac3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Script Executing PowerShell", - "note": "## Triage and analysis\n\n### Investigating Windows Script Executing PowerShell\n\nThe Windows Script Host (WSH) is an Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for the spawn of the `powershell.exe` process with `cscript.exe` or `wscript.exe` as its parent process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate commands executed by the spawned PowerShell process.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Determine how the script file was delivered (email attachment, dropped by other processes, etc.).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Windows Script Executing PowerShell\n\nThe Windows Script Host (WSH) is an Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for the spawn of the `powershell.exe` process with `cscript.exe` or `wscript.exe` as its parent process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate commands executed by the spawned PowerShell process.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Determine how the script file was delivered (email attachment, dropped by other processes, etc.).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"cscript.exe\", \"wscript.exe\") and process.name : \"powershell.exe\"\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Initial Access" + "Initial Access", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json index 91f65ff6fbd18..9bc62b2b0ec53 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious MS Office Child Process", - "note": "## Triage and analysis\n\n### Investigating Suspicious MS Office Child Process\n\nMicrosoft Office (MS Office) is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently targeted\nfor initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule looks for suspicious processes spawned by MS Office programs. This is generally the result of the execution of\nmalicious documents.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious MS Office Child Process\n\nMicrosoft Office (MS Office) is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently targeted\nfor initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule looks for suspicious processes spawned by MS Office programs. This is generally the result of the execution of\nmalicious documents.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"eqnedt32.exe\", \"excel.exe\", \"fltldr.exe\", \"msaccess.exe\", \"mspub.exe\", \"powerpnt.exe\", \"winword.exe\", \"outlook.exe\") and\n process.name : (\"Microsoft.Workflow.Compiler.exe\", \"arp.exe\", \"atbroker.exe\", \"bginfo.exe\", \"bitsadmin.exe\", \"cdb.exe\", \"certutil.exe\",\n \"cmd.exe\", \"cmstp.exe\", \"control.exe\", \"cscript.exe\", \"csi.exe\", \"dnx.exe\", \"dsget.exe\", \"dsquery.exe\", \"forfiles.exe\",\n \"fsi.exe\", \"ftp.exe\", \"gpresult.exe\", \"hostname.exe\", \"ieexec.exe\", \"iexpress.exe\", \"installutil.exe\", \"ipconfig.exe\",\n \"mshta.exe\", \"msxsl.exe\", \"nbtstat.exe\", \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"odbcconf.exe\",\n \"ping.exe\", \"powershell.exe\", \"pwsh.exe\", \"qprocess.exe\", \"quser.exe\", \"qwinsta.exe\", \"rcsi.exe\", \"reg.exe\", \"regasm.exe\",\n \"regsvcs.exe\", \"regsvr32.exe\", \"sc.exe\", \"schtasks.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\", \"whoami.exe\",\n \"wmic.exe\", \"wscript.exe\", \"xwizard.exe\", \"explorer.exe\", \"rundll32.exe\", \"hh.exe\", \"msdt.exe\")\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Initial Access" + "Initial Access", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json index 2b21f26bd82e9..0b21f7c5f2e75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious MS Outlook Child Process", - "note": "## Triage and analysis\n\n### Investigating Suspicious MS Outlook Child Process\n\nMicrosoft Outlook is an email client that provides contact, email calendar, and task management features. Outlook is\nwidely used, either standalone or as part of the Office suite.\n\nThis rule looks for suspicious processes spawned by MS Outlook, which can be the result of the execution of malicious\ndocuments and/or exploitation for initial access.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently opened files received via email and opened by the user that could cause this behavior. Common\nlocations include but are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Suspicious MS Outlook Child Process\n\nMicrosoft Outlook is an email client that provides contact, email calendar, and task management features. Outlook is\nwidely used, either standalone or as part of the Office suite.\n\nThis rule looks for suspicious processes spawned by MS Outlook, which can be the result of the execution of malicious\ndocuments and/or exploitation for initial access.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently opened files received via email and opened by the user that could cause this behavior. Common\nlocations include but are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : \"outlook.exe\" and\n process.name : (\"Microsoft.Workflow.Compiler.exe\", \"arp.exe\", \"atbroker.exe\", \"bginfo.exe\", \"bitsadmin.exe\",\n \"cdb.exe\", \"certutil.exe\", \"cmd.exe\", \"cmstp.exe\", \"cscript.exe\", \"csi.exe\", \"dnx.exe\", \"dsget.exe\",\n \"dsquery.exe\", \"forfiles.exe\", \"fsi.exe\", \"ftp.exe\", \"gpresult.exe\", \"hostname.exe\", \"ieexec.exe\",\n \"iexpress.exe\", \"installutil.exe\", \"ipconfig.exe\", \"mshta.exe\", \"msxsl.exe\", \"nbtstat.exe\", \"net.exe\",\n \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"odbcconf.exe\", \"ping.exe\", \"powershell.exe\",\n \"pwsh.exe\", \"qprocess.exe\", \"quser.exe\", \"qwinsta.exe\", \"rcsi.exe\", \"reg.exe\", \"regasm.exe\",\n \"regsvcs.exe\", \"regsvr32.exe\", \"sc.exe\", \"schtasks.exe\", \"systeminfo.exe\", \"tasklist.exe\",\n \"tracert.exe\", \"whoami.exe\", \"wmic.exe\", \"wscript.exe\", \"xwizard.exe\")\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Initial Access" + "Initial Access", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json index cab15fd6b1ed6..a91bdd8faea3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual Child Process of dns.exe", - "note": "## Triage and analysis\n\n### Investigating Unusual Child Process of dns.exe\n\nSIGRed (CVE-2020-1350) is a wormable, critical vulnerability in the Windows DNS server that affects Windows Server\nversions 2003 to 2019 and can be triggered by a malicious DNS response. Because the service is running in elevated\nprivileges (SYSTEM), an attacker that successfully exploits it is granted Domain Administrator rights. This can\neffectively compromise the entire corporate infrastructure.\n\nThis rule looks for unusual children of the `dns.exe` process, which can indicate the exploitation of the SIGRed or a\nsimilar remote code execution vulnerability in the DNS server.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes.\n - Any suspicious or abnormal child process spawned from dns.exe should be carefully reviewed and investigated. It's\n impossible to predict what an adversary may deploy as the follow-on process after the exploit, but built-in\n discovery/enumeration utilities should be top of mind (`whoami.exe`, `netstat.exe`, `systeminfo.exe`, `tasklist.exe`).\n - Built-in Windows programs that contain capabilities used to download and execute additional payloads should also be\n considered. This is not an exhaustive list, but ideal candidates to start out would be: `mshta.exe`, `powershell.exe`,\n `regsvr32.exe`, `rundll32.exe`, `wscript.exe`, `wmic.exe`.\n - If a denial-of-service (DoS) exploit is successful and DNS Server service crashes, be mindful of potential child processes related to\n `werfault.exe` occurring.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the host during the past 48 hours.\n- Check whether the server is vulnerable to CVE-2020-1350.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system or restore the compromised server to a clean state.\n- Install the latest patches on systems that run Microsoft DNS Server.\n- Consider the implementation of a patch management system, such as the Windows Server Update Services (WSUS).\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Unusual Child Process of dns.exe\n\nSIGRed (CVE-2020-1350) is a wormable, critical vulnerability in the Windows DNS server that affects Windows Server\nversions 2003 to 2019 and can be triggered by a malicious DNS response. Because the service is running in elevated\nprivileges (SYSTEM), an attacker that successfully exploits it is granted Domain Administrator rights. This can\neffectively compromise the entire corporate infrastructure.\n\nThis rule looks for unusual children of the `dns.exe` process, which can indicate the exploitation of the SIGRed or a\nsimilar remote code execution vulnerability in the DNS server.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes.\n - Any suspicious or abnormal child process spawned from dns.exe should be carefully reviewed and investigated. It's\n impossible to predict what an adversary may deploy as the follow-on process after the exploit, but built-in\n discovery/enumeration utilities should be top of mind (`whoami.exe`, `netstat.exe`, `systeminfo.exe`, `tasklist.exe`).\n - Built-in Windows programs that contain capabilities used to download and execute additional payloads should also be\n considered. This is not an exhaustive list, but ideal candidates to start out would be: `mshta.exe`, `powershell.exe`,\n `regsvr32.exe`, `rundll32.exe`, `wscript.exe`, `wmic.exe`.\n - If a denial-of-service (DoS) exploit is successful and DNS Server service crashes, be mindful of potential child processes related to\n `werfault.exe` occurring.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Investigate other alerts associated with the host during the past 48 hours.\n- Check whether the server is vulnerable to CVE-2020-1350.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system or restore the compromised server to a clean state.\n- Install the latest patches on systems that run Microsoft DNS Server.\n- Consider the implementation of a patch management system, such as the Windows Server Update Services (WSUS).\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and process.parent.name : \"dns.exe\" and\n not process.name : \"conhost.exe\"\n", "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Initial Access" + "Initial Access", + "has_guide" ], "threat": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json index 1af2502b0034a..78fea7b5dafe2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json @@ -60,7 +60,8 @@ "Continuous Monitoring", "SecOps", "Log Auditing", - "Initial Access" + "Initial Access", + "has_guide" ], "threat": [ { @@ -88,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json index 62d4c160e806f..000e631380307 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Direct Outbound SMB Connection", - "note": "## Triage and analysis\n\n### Investigating Direct Outbound SMB Connection\n\nThis rule looks for unexpected processes making network connections over port 445. Windows file sharing is typically\nimplemented over Server Message Block (SMB), which communicates between hosts using port 445. When legitimate, these\nnetwork connections are established by the kernel (PID 4). Occurrences of non-system processes using this port can indicate\nport scanners, exploits, and tools used to move laterally on the environment.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Direct Outbound SMB Connection\n\nThis rule looks for unexpected processes making network connections over port 445. Windows file sharing is typically\nimplemented over Server Message Block (SMB), which communicates between hosts using port 445. When legitimate, these\nnetwork connections are established by the kernel (PID 4). Occurrences of non-system processes using this port can indicate\nport scanners, exploits, and tools used to move laterally on the environment.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by process.entity_id\n [process where event.type == \"start\" and host.os.name == \"Windows\" and process.pid != 4 and\n not (process.executable : \"D:\\\\EnterpriseCare\\\\tools\\\\jre.1\\\\bin\\\\java.exe\" and process.args : \"com.emeraldcube.prism.launcher.Invoker\") and\n not (process.executable : \"C:\\\\Docusnap 11\\\\Tools\\\\nmap\\\\nmap.exe\" and process.args : \"smb-os-discovery.nse\") and\n not process.executable :\n (\"?:\\\\Program Files\\\\SentinelOne\\\\Sentinel Agent *\\\\Ranger\\\\SentinelRanger.exe\",\n \"?:\\\\Program Files\\\\Ivanti\\\\Security Controls\\\\ST.EngineHost.exe\",\n \"?:\\\\Program Files (x86)\\\\Fortinet\\\\FSAE\\\\collectoragent.exe\",\n \"?:\\\\Program Files (x86)\\\\Nmap\\\\nmap.exe\",\n \"?:\\\\Program Files\\\\Azure Advanced Threat Protection Sensor\\\\*\\\\Microsoft.Tri.Sensor.exe\",\n \"?:\\\\Program Files\\\\CloudMatters\\\\auvik\\\\AuvikService-release-*\\\\AuvikService.exe\",\n \"?:\\\\Program Files\\\\uptime software\\\\uptime\\\\UptimeDataCollector.exe\",\n \"?:\\\\Program Files\\\\CloudMatters\\\\auvik\\\\AuvikAgentService.exe\",\n \"?:\\\\Program Files\\\\Rumble\\\\rumble-agent-*.exe\")]\n [network where destination.port == 445 and process.pid != 4 and\n not cidrmatch(destination.ip, \"127.0.0.1\", \"::1\")]\n", "required_fields": [ { @@ -64,7 +64,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -91,5 +92,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json index be4535a908006..1d985fb800a68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json @@ -59,7 +59,8 @@ "Elastic", "Network", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -80,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json index 5c463878f146e..cd1de0b09dc00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Lateral Tool Transfer via SMB Share", - "note": "## Triage and analysis\n\n### Investigating Potential Lateral Tool Transfer via SMB Share\n\nAdversaries can use network shares to host tooling to support the compromise of other hosts in the environment. These tools\ncan include discovery utilities, credential dumpers, malware, etc. Attackers can also leverage file shares that employees\nfrequently access to host malicious files to gain a foothold in other machines.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the created file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Consider adding exceptions if it is expected and noisy in your environment.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Review the privileges needed to write to the network share and restrict write access as needed.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Potential Lateral Tool Transfer via SMB Share\n\nAdversaries can use network shares to host tooling to support the compromise of other hosts in the environment. These tools\ncan include discovery utilities, credential dumpers, malware, etc. Attackers can also leverage file shares that employees\nfrequently access to host malicious files to gain a foothold in other machines.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the created file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Consider adding exceptions if it is expected and noisy in your environment.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Review the privileges needed to write to the network share and restrict write access as needed.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id with maxspan=30s\n [network where event.type == \"start\" and process.pid == 4 and destination.port == 445 and\n network.direction : (\"incoming\", \"ingress\") and\n network.transport == \"tcp\" and source.ip != \"127.0.0.1\" and source.ip != \"::1\"\n ] by process.entity_id\n /* add more executable extensions here if they are not noisy in your environment */\n [file where event.type in (\"creation\", \"change\") and process.pid == 4 and file.extension : (\"exe\", \"dll\", \"bat\", \"cmd\")] by process.entity_id\n", "required_fields": [ { @@ -69,7 +69,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -101,5 +102,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json index 0e6aab67cf050..301ce56a51f5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json @@ -12,6 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote Execution via File Shares", + "note": "## Triage and analysis\n\n### Investigating Remote Execution via File Shares\n\nAdversaries can use network shares to host tooling to support the compromise of other hosts in the environment. These\ntools can include discovery utilities, credential dumpers, malware, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Review adjacent login events (e.g., 4624) in the alert timeframe to identify the account used to perform this action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Consider adding exceptions if it is expected and noisy in your environment.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Review the privileges needed to write to the network share and restrict write access as needed.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=1m\n [file where event.type in (\"creation\", \"change\") and process.pid == 4 and file.extension : \"exe\"] by host.id, file.path\n [process where event.type == \"start\"] by host.id, process.executable\n", "references": [ "https://blog.menasec.net/2020/08/new-trick-to-detect-lateral-movement.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json index bc3a4e3b0ac95..12f9216c7b6ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json @@ -55,7 +55,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -83,5 +84,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json index d0504020fcad3..45144cc486895 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json @@ -12,7 +12,11 @@ "language": "eql", "license": "Elastic License v2", "name": "Remotely Started Services via RPC", + "note": "## Triage and analysis\n\n### Investigating Remotely Started Services via RPC\n\nThe Service Control Manager Remote Protocol is a client/server protocol used for configuring and controlling service\nprograms running on a remote computer. A remote service management session begins with the client initiating the\nconnection request to the server. If the server grants the request, the connection is established. The client can then\nmake multiple requests to modify, query the configuration, or start and stop services on the server by using the same\nsession until the session is terminated.\n\nThis rule detects the remote creation or start of a service by correlating a `services.exe` network connection and the\nspawn of a child process.\n\n#### Possible investigation steps\n\n- Review login events (e.g., 4624) in the alert timeframe to identify the account used to perform this action. Use the\n`source.address` field to help identify the source system.\n- Review network events from the source system using the source port identified on the alert and try to identify the\nprogram used to initiate the action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate if the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Remote management software like SCCM may trigger this rule. If noisy on your environment, consider adding exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=1s\n [network where process.name : \"services.exe\" and\n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and\n source.port >= 49152 and destination.port >= 49152 and source.ip != \"127.0.0.1\" and source.ip != \"::1\"\n ] by host.id, process.entity_id\n\n [process where event.type == \"start\" and process.parent.name : \"services.exe\" and \n not (process.name : \"svchost.exe\" and process.args : \"tiledatamodelsvc\") and\n not (process.name : \"msiexec.exe\" and process.args : \"/V\") and\n not process.executable :\n (\"?:\\\\Windows\\\\ADCR_Agent\\\\adcrsvc.exe\",\n \"?:\\\\Windows\\\\System32\\\\VSSVC.exe\",\n \"?:\\\\Windows\\\\servicing\\\\TrustedInstaller.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Windows\\\\PSEXESVC.EXE\",\n \"?:\\\\Windows\\\\System32\\\\sppsvc.exe\",\n \"?:\\\\Windows\\\\System32\\\\wbem\\\\WmiApSrv.exe\",\n \"?:\\\\WINDOWS\\\\RemoteAuditService.exe\",\n \"?:\\\\Windows\\\\VeeamVssSupport\\\\VeeamGuestHelper.exe\",\n \"?:\\\\Windows\\\\VeeamLogShipper\\\\VeeamLogShipper.exe\",\n \"?:\\\\Windows\\\\CAInvokerService.exe\",\n \"?:\\\\Windows\\\\System32\\\\upfc.exe\",\n \"?:\\\\Windows\\\\AdminArsenal\\\\PDQ*.exe\",\n \"?:\\\\Windows\\\\System32\\\\vds.exe\",\n \"?:\\\\Windows\\\\Veeam\\\\Backup\\\\VeeamDeploymentSvc.exe\",\n \"?:\\\\Windows\\\\ProPatches\\\\Scheduler\\\\STSchedEx.exe\",\n \"?:\\\\Windows\\\\System32\\\\certsrv.exe\",\n \"?:\\\\Windows\\\\eset-remote-install-service.exe\",\n \"?:\\\\Pella Corporation\\\\Pella Order Management\\\\GPAutoSvc.exe\",\n \"?:\\\\Pella Corporation\\\\OSCToGPAutoService\\\\OSCToGPAutoSvc.exe\",\n \"?:\\\\Pella Corporation\\\\Pella Order Management\\\\GPAutoSvc.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\NwxExeSvc\\\\NwxExeSvc.exe\",\n \"?:\\\\Windows\\\\System32\\\\taskhostex.exe\")\n ] by host.id, process.parent.entity_id\n", + "references": [ + "https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/705b624a-13de-43cc-b8a2-99573da3635f" + ], "required_fields": [ { "ecs": true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json index 1896f18299853..06dd1913a6f1b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json @@ -64,7 +64,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -106,5 +107,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json index 8ccc5adc9dce2..112767349fb88 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json @@ -25,8 +25,9 @@ "Elastic", "Cloud", "AWS", - "ML" + "ML", + "has_guide" ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json index eba9e157895ee..649df5bcfb66e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json @@ -25,8 +25,9 @@ "Elastic", "Cloud", "AWS", - "ML" + "ML", + "has_guide" ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json index d17e1053e2ccc..1368f04639b5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json @@ -25,8 +25,9 @@ "Elastic", "Cloud", "AWS", - "ML" + "ML", + "has_guide" ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json index c170c6a338b30..4719a47dc5d9d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json @@ -25,8 +25,9 @@ "Elastic", "Cloud", "AWS", - "ML" + "ML", + "has_guide" ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json index 22ef1411a537c..74f190786b086 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json @@ -25,8 +25,9 @@ "Elastic", "Cloud", "AWS", - "ML" + "ML", + "has_guide" ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json index 02bcc8eff6864..9624d0b995c11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Adobe Hijack Persistence", - "note": "## Triage and analysis\n\n### Investigating Adobe Hijack Persistence\n\nAttackers can replace the `RdrCEF.exe` executable with their own to maintain their access, which will be launched\nwhenever Adobe Acrobat Reader is executed.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Adobe Hijack Persistence\n\nAttackers can replace the `RdrCEF.exe` executable with their own to maintain their access, which will be launched\nwhenever Adobe Acrobat Reader is executed.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type == \"creation\" and\n file.path : (\"?:\\\\Program Files (x86)\\\\Adobe\\\\Acrobat Reader DC\\\\Reader\\\\AcroCEF\\\\RdrCEF.exe\",\n \"?:\\\\Program Files\\\\Adobe\\\\Acrobat Reader DC\\\\Reader\\\\AcroCEF\\\\RdrCEF.exe\") and\n not process.name : \"msiexec.exe\"\n", "references": [ "https://twitter.com/pabraeken/status/997997818362155008" @@ -43,7 +43,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -71,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json index ca2be5273f8f2..80720e7aff6ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json @@ -50,7 +50,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -86,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json index e335f4cea7319..6b20c9653773b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json @@ -51,7 +51,8 @@ "Windows", "Threat Detection", "Persistence", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -72,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json index 6f3aa83b004b6..ab09c9a6cf354 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json @@ -34,7 +34,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -62,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json index c1547b38e67b6..e1b8862c4424a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Startup Shell Folder Modification", - "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Folder Modification\n\nTechniques used within malware and by adversaries often leverage the Windows registry to store malicious programs for\npersistence. Startup shell folders are often targeted as they are not as prevalent as normal Startup folder paths so this\nbehavior may evade existing AV/EDR solutions. These programs may also run with higher privileges which can be ideal for\nan attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Review the source process and related file tied to the Windows Registry entry.\n- Validate the activity is not related to planned patches, updates, network administrator activity or legitimate software\ninstallations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to shell folders. This activity could be based\non new software installations, patches, or other network administrator activity. Before entering further investigation,\nit should be verified that this activity is not benign.\n\n### Related rules\n\n- Startup or Run Key Registry Modification - 97fc44d3-8dae-4019-ae83-298c3015600f\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Folder Modification\n\nTechniques used within malware and by adversaries often leverage the Windows registry to store malicious programs for\npersistence. Startup shell folders are often targeted as they are not as prevalent as normal Startup folder paths so this\nbehavior may evade existing AV/EDR solutions. These programs may also run with higher privileges which can be ideal for\nan attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Review the source process and related file tied to the Windows Registry entry.\n- Validate the activity is not related to planned patches, updates, network administrator activity or legitimate software\ninstallations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to shell folders. This activity could be based\non new software installations, patches, or other network administrator activity. Before undertaking further investigation,\nit should be verified that this activity is not benign.\n\n### Related rules\n\n- Startup or Run Key Registry Modification - 97fc44d3-8dae-4019-ae83-298c3015600f\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "registry where\n registry.path : (\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Common Startup\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Common Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Startup\"\n ) and\n registry.data.strings != null and\n /* Normal Startup Folder Paths */\n not registry.data.strings : (\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%ProgramData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%USERPROFILE%\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\"\n )\n", "required_fields": [ { @@ -32,7 +32,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -60,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json index 6d014886b1fa4..36fdb7f9316dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Creation or Modification of a new GPO Scheduled Task or Service", "note": "", - "query": "file where event.type != \"deletion\" and\n file.path : (\"?:\\\\Windows\\\\SYSVOL\\\\domain\\\\Policies\\\\*\\\\MACHINE\\\\Preferences\\\\ScheduledTasks\\\\ScheduledTasks.xml\",\n \"?:\\\\Windows\\\\SYSVOL\\\\domain\\\\Policies\\\\*\\\\MACHINE\\\\Preferences\\\\Preferences\\\\Services\\\\Services.xml\") and\n not process.name : \"dfsrs.exe\"\n", + "query": "file where event.type != \"deletion\" and\n file.path : (\"?:\\\\Windows\\\\SYSVOL\\\\domain\\\\Policies\\\\*\\\\MACHINE\\\\Preferences\\\\ScheduledTasks\\\\ScheduledTasks.xml\",\n \"?:\\\\Windows\\\\SYSVOL\\\\domain\\\\Policies\\\\*\\\\MACHINE\\\\Preferences\\\\Services\\\\Services.xml\") and\n not process.name : \"dfsrs.exe\"\n", "required_fields": [ { "ecs": true, @@ -68,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json index 67c7c9ad84ddc..8b21e5358f71b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json @@ -46,7 +46,8 @@ "Azure", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -67,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json index 7ae8401cf782d..8c79a775a7779 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json @@ -27,7 +27,8 @@ "Windows", "Threat Detection", "ML", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -54,5 +55,5 @@ } ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json index 30786d630001c..385f0aee4aab5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Modification of Accessibility Binaries", - "note": "## Triage and analysis\n\n### Investigating Potential Modification of Accessibility Binaries\n\nAdversaries may establish persistence and/or elevate privileges by executing malicious content triggered by\naccessibility features. Windows contains accessibility features that may be launched with a key combination before a\nuser has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs\nare launched to get a command prompt or backdoor without logging in to the system.\n\nMore details can be found [here](https://attack.mitre.org/techniques/T1546/008/).\n\nThis rule looks for the execution of supposed accessibility binaries that don't match any of the accessibility features\nbinaries' original file names, which is likely a custom binary deployed by the attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Potential Modification of Accessibility Binaries\n\nAdversaries may establish persistence and/or elevate privileges by executing malicious content triggered by\naccessibility features. Windows contains accessibility features that may be launched with a key combination before a\nuser has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs\nare launched to get a command prompt or backdoor without logging in to the system.\n\nMore details can be found [here](https://attack.mitre.org/techniques/T1546/008/).\n\nThis rule looks for the execution of supposed accessibility binaries that don't match any of the accessibility features\nbinaries' original file names, which is likely a custom binary deployed by the attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"Utilman.exe\", \"winlogon.exe\") and user.name == \"SYSTEM\" and\n process.args :\n (\n \"C:\\\\Windows\\\\System32\\\\osk.exe\",\n \"C:\\\\Windows\\\\System32\\\\Magnify.exe\",\n \"C:\\\\Windows\\\\System32\\\\Narrator.exe\",\n \"C:\\\\Windows\\\\System32\\\\Sethc.exe\",\n \"utilman.exe\",\n \"ATBroker.exe\",\n \"DisplaySwitch.exe\",\n \"sethc.exe\"\n )\n and not process.pe.original_file_name in\n (\n \"osk.exe\",\n \"sethc.exe\",\n \"utilman2.exe\",\n \"DisplaySwitch.exe\",\n \"ATBroker.exe\",\n \"ScreenMagnifier.exe\",\n \"SR.exe\",\n \"Narrator.exe\",\n \"magnify.exe\",\n \"MAGNIFY.EXE\"\n )\n\n/* uncomment once in winlogbeat to avoid bypass with rogue process with matching pe original file name */\n/* and process.code_signature.subject_name == \"Microsoft Windows\" and process.code_signature.status == \"trusted\" */\n", "references": [ "https://www.elastic.co/blog/practical-security-engineering-stateful-detection" @@ -53,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json index 7541049e4fd3e..04da402d1caf9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json @@ -10,6 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Startup or Run Key Registry Modification", + "note": "## Triage and analysis\n\n### Investigating Startup or Run Key Registry Modification\n\nAdversaries may achieve persistence by referencing a program with a registry run key. Adding an entry to the run keys\nin the registry will cause the program referenced to be executed when a user logs in. These programs will executed\nunder the context of the user and will have the account's permissions. This rule looks for this behavior by monitoring\na range of registry run keys.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to registry run keys. This activity could be\nbased on new software installations, patches, or any kind of network administrator related activity. Before undertaking\nfurther investigation, verify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n- Startup Folder Persistence via Unsigned Process - 2fba96c0-ade5-4bce-b92f-a5df2509da3f\n- Startup Persistence by a Suspicious Process - 440e2db4-bc7f-4c96-a068-65b78da59bde\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "registry where registry.data.strings != null and\n registry.path : (\n /* Machine Hive */\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Winlogon\\\\Shell\\\\*\",\n /* Users Hive */\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Winlogon\\\\Shell\\\\*\"\n ) and\n /* add common legitimate changes without being too restrictive as this is one of the most abused AESPs */\n not registry.data.strings : \"ctfmon.exe /n\" and\n not (registry.value : \"Application Restart #*\" and process.name : \"csrss.exe\") and\n user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n not registry.data.strings : (\"?:\\\\Program Files\\\\*.exe\", \"?:\\\\Program Files (x86)\\\\*.exe\") and\n not process.executable : (\"?:\\\\Windows\\\\System32\\\\msiexec.exe\", \"?:\\\\Windows\\\\SysWOW64\\\\msiexec.exe\") and\n not (process.name : \"OneDriveSetup.exe\" and\n registry.value : (\"Delete Cached Standalone Update Binary\", \"Delete Cached Update Binary\", \"amd64\", \"Uninstall *\") and\n registry.data.strings : \"?:\\\\Windows\\\\system32\\\\cmd.exe /q /c * \\\"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\*\\\"\")\n", "required_fields": [ { @@ -81,5 +82,5 @@ "timeline_title": "Comprehensive Registry Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json index 9d524bd69b5a8..13da283b0097d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json @@ -49,7 +49,8 @@ "Windows", "Threat Detection", "Persistence", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -64,5 +65,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json index db214d1b94467..a9375cd4f50ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json @@ -14,6 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Shell via Web Server", + "note": "## Triage and analysis\n\n### Investigating Potential Shell via Web Server\n\nAdversaries may backdoor web servers with web shells to establish persistent access to systems. A web shell is a web\nscript that is placed on an openly accessible web server to allow an adversary to use the web server as a gateway into a\nnetwork. A web shell may provide a set of functions to execute or a command line interface on the system that hosts the\nweb server.\n\nThis rule detects a web server process spawning script and command line interface programs, potentially indicating\nattackers executing commands using the web shell.\n\n#### Possible investigation steps\n\n- Investigate abnormal behaviors observed by the subject process such as network connections, file modifications, and\nany other spawned child processes.\n- Examine the command line to determine which commands or scripts were executed.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If scripts or executables were dropped, retrieve the files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - Check if the domain is newly registered or unexpected.\n - Check the reputation of the domain or IP address.\n - File access, modification, and creation activities.\n - Cron jobs, services and other persistence mechanisms.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.category:process and event.type:(start or process_started) and\nprocess.name:(bash or dash or ash or zsh or \"python*\" or \"perl*\" or \"php*\") and\nprocess.parent.name:(\"apache\" or \"nginx\" or \"www\" or \"apache2\" or \"httpd\" or \"www-data\")\n", "references": [ "https://pentestlab.blog/tag/web-shell/" @@ -76,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json index d3e19871a157d..b477cd0148352 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Startup Persistence by a Suspicious Process", - "note": "## Triage and analysis\n\n### Investigating Startup Persistence by a Suspicious Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule monitors for commonly abused processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators may add programs to this mechanism via command-line shells. Before the further investigation,\nverify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Startup Persistence by a Suspicious Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule monitors for commonly abused processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators may add programs to this mechanism via command-line shells. Before the further investigation,\nverify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type != \"deletion\" and\n user.domain != \"NT AUTHORITY\" and\n file.path : (\"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\*\",\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\StartUp\\\\*\") and\n process.name : (\"cmd.exe\",\n \"powershell.exe\",\n \"wmic.exe\",\n \"mshta.exe\",\n \"pwsh.exe\",\n \"cscript.exe\",\n \"wscript.exe\",\n \"regsvr32.exe\",\n \"RegAsm.exe\",\n \"rundll32.exe\",\n \"EQNEDT32.EXE\",\n \"WINWORD.EXE\",\n \"EXCEL.EXE\",\n \"POWERPNT.EXE\",\n \"MSPUB.EXE\",\n \"MSACCESS.EXE\",\n \"iexplore.exe\",\n \"InstallUtil.exe\")\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json index 8d8a6b31fd0f9..82aa2eba13fe1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Startup Folder Persistence via Unsigned Process", - "note": "## Triage and analysis\n\n### Investigating Startup Folder Persistence via Unsigned Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for unsigned processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to Startup folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, verify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "note": "## Triage and analysis\n\n### Investigating Startup Folder Persistence via Unsigned Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for unsigned processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to Startup folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before undertaking further\ninvestigation, verify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id, process.entity_id with maxspan=5s\n [process where event.type == \"start\" and process.code_signature.trusted == false and\n /* suspicious paths can be added here */\n process.executable : (\"C:\\\\Users\\\\*.exe\",\n \"C:\\\\ProgramData\\\\*.exe\",\n \"C:\\\\Windows\\\\Temp\\\\*.exe\",\n \"C:\\\\Windows\\\\Tasks\\\\*.exe\",\n \"C:\\\\Intel\\\\*.exe\",\n \"C:\\\\PerfLogs\\\\*.exe\")\n ]\n [file where event.type != \"deletion\" and user.domain != \"NT AUTHORITY\" and\n file.path : (\"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\*\",\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\StartUp\\\\*\")\n ]\n", "required_fields": [ { @@ -57,7 +57,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json index 5fce4a0cacfb0..0ba6a1a05c026 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Persistent Scripts in the Startup Directory", - "note": "## Triage and analysis\n\n### Investigating Persistent Scripts in the Startup Directory\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for shortcuts created by wscript.exe or cscript.exe, or js/vbs scripts created by any process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Startup Folder Persistence via Unsigned Process - 2fba96c0-ade5-4bce-b92f-a5df2509da3f\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Persistent Scripts in the Startup Directory\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for shortcuts created by wscript.exe or cscript.exe, or js/vbs scripts created by any process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Startup Folder Persistence via Unsigned Process - 2fba96c0-ade5-4bce-b92f-a5df2509da3f\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type != \"deletion\" and user.domain != \"NT AUTHORITY\" and\n\n /* detect shortcuts created by wscript.exe or cscript.exe */\n (file.path : \"C:\\\\*\\\\Programs\\\\Startup\\\\*.lnk\" and\n process.name : (\"wscript.exe\", \"cscript.exe\")) or\n\n /* detect vbs or js files created by any process */\n file.path : (\"C:\\\\*\\\\Programs\\\\Startup\\\\*.vbs\",\n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.vbe\",\n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.wsh\",\n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.wsf\",\n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.js\")\n", "required_fields": [ { @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index a742c05003a0f..35a9201a43fa5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Component Object Model Hijacking", - "note": "## Triage and analysis\n\n### Investigating Component Object Model Hijacking\n\nAdversaries can insert malicious code that can be executed in place of legitimate software through hijacking the COM references and relationships as a means of persistence.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file referenced in the registry and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Some Microsoft executables will reference the LocalServer32 registry key value for the location of external COM objects.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Component Object Model Hijacking\n\nAdversaries can insert malicious code that can be executed in place of legitimate software through hijacking the COM references and relationships as a means of persistence.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file referenced in the registry and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Some Microsoft executables will reference the LocalServer32 registry key value for the location of external COM objects.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\")\n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n not (process.executable : \"?:\\\\Program Files*\\\\Veeam\\\\Backup and Replication\\\\Console\\\\veeam.backup.shell.exe\" and\n registry.path : \"HKEY_USERS\\\\S-1-5-21-*_Classes\\\\CLSID\\\\*\\\\LocalServer32\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\"\n ) and\n /* removes false-positives generated by OneDrive and Teams */\n not process.name : (\"OneDrive.exe\",\"OneDriveSetup.exe\",\"FileSyncConfig.exe\",\"Teams.exe\") and\n /* Teams DLL loaded by regsvr */\n not (process.name: \"regsvr32.exe\" and\n registry.data.strings : \"*Microsoft.Teams.*.dll\")\n", "references": [ "https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/" @@ -51,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -79,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json index 99f03033656ae..4c52791fc0a9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json index c0544624f0377..ec9c831670716 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -59,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index 0734ca15c8ba8..85997cb5399cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json index 32f295c0e81d1..4836132746057 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Persistence via Update Orchestrator Service Hijack", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Persistence via Update Orchestrator Service Hijack\n\nWindows Update Orchestrator Service is a DCOM service used by other components to install Windows updates that are\nalready downloaded. Windows Update Orchestrator Service was vulnerable to elevation of privileges (any user to local\nsystem) due to an improper authorization of the callers. The vulnerability affected the Windows 10 and Windows Server\nCore products. Fixed by Microsoft on Patch Tuesday June 2020.\n\nThis rule will detect uncommon processes spawned by `svchost.exe` with `UsoSvc` as the command line parameters.\nAttackers can leverage this technique to elevate privileges or maintain persistence.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.executable : \"C:\\\\Windows\\\\System32\\\\svchost.exe\" and\n process.parent.args : \"UsoSvc\" and\n not process.executable :\n (\"?:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\UUS\\\\Packages\\\\*\\\\amd64\\\\MoUsoCoreWorker.exe\",\n \"?:\\\\Windows\\\\System32\\\\UsoClient.exe\",\n \"?:\\\\Windows\\\\System32\\\\MusNotification.exe\",\n \"?:\\\\Windows\\\\System32\\\\MusNotificationUx.exe\",\n \"?:\\\\Windows\\\\System32\\\\MusNotifyIcon.exe\",\n \"?:\\\\Windows\\\\System32\\\\WerFault.exe\",\n \"?:\\\\Windows\\\\System32\\\\WerMgr.exe\",\n \"?:\\\\Windows\\\\UUS\\\\amd64\\\\MoUsoCoreWorker.exe\",\n \"?:\\\\Windows\\\\System32\\\\MoUsoCoreWorker.exe\",\n \"?:\\\\Windows\\\\UUS\\\\amd64\\\\UsoCoreWorker.exe\",\n \"?:\\\\Windows\\\\System32\\\\UsoCoreWorker.exe\",\n \"?:\\\\Program Files\\\\Common Files\\\\microsoft shared\\\\ClickToRun\\\\OfficeC2RClient.exe\") and\n not process.name : (\"MoUsoCoreWorker.exe\", \"OfficeC2RClient.exe\")\n", "references": [ "https://github.com/irsl/CVE-2020-1313" @@ -82,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json index 4bb454dae5cf2..69130e81a668a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json @@ -14,8 +14,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Webshell Detection: Script Process Child of Common Web Processes", - "note": "## Triage and analysis\n\nDetections should be investigated to identify if the activity corresponds to legitimate activity. As this rule detects post-exploitation process activity, investigations into this should be prioritized.", + "name": "Web Shell Detection: Script Process Child of Common Web Processes", + "note": "## Triage and analysis\n\n### Investigating Web Shell Detection: Script Process Child of Common Web Processes\n\nAdversaries may backdoor web servers with web shells to establish persistent access to systems. A web shell is a web\nscript that is placed on an openly accessible web server to allow an adversary to use the web server as a gateway into a\nnetwork. A web shell may provide a set of functions to execute or a command-line interface on the system that hosts the\nweb server.\n\nThis rule detects a web server process spawning script and command-line interface programs, potentially indicating\nattackers executing commands using the web shell.\n\n#### Possible investigation steps\n\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any other spawned child processes.\n- Examine the command line to determine which commands or scripts were executed.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- If scripts or executables were dropped, retrieve the files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"w3wp.exe\", \"httpd.exe\", \"nginx.exe\", \"php.exe\", \"php-cgi.exe\", \"tomcat.exe\") and\n process.name : (\"cmd.exe\", \"cscript.exe\", \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\", \"wmic.exe\", \"wscript.exe\")\n", "references": [ "https://www.microsoft.com/security/blog/2020/02/04/ghost-in-the-shell-investigating-web-shell-attacks/" @@ -89,5 +89,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json index e54cad33c0830..04291353bfe13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Disabling User Account Control via Registry Modification", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Disabling User Account Control via Registry Modification\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nAttackers may disable UAC to execute code directly in high integrity. This rule identifies registry value changes to\nbypass the UAC protection.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Analyze non-system processes executed with high integrity after UAC was disabled for unknown or suspicious processes.\n- Retrieve the suspicious processes' executables and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Restore UAC settings to the desired state.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "registry where event.type == \"change\" and\n registry.path :\n (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\EnableLUA\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\ConsentPromptBehaviorAdmin\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\PromptOnSecureDesktop\"\n ) and\n registry.data.strings : (\"0\", \"0x00000000\")\n", "references": [ "https://www.greyhathacker.net/?p=796", @@ -95,5 +95,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json index e7901f8073390..b13f11a0a0994 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json @@ -67,7 +67,8 @@ "Windows", "Threat Detection", "Privilege Escalation", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -100,5 +101,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json index f5b8c4604f6ac..aa888ff8e3f5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json @@ -43,7 +43,8 @@ "Windows", "Threat Detection", "Privilege Escalation", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -71,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json index 6569ffb3bf6bc..f43123163d210 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json @@ -66,7 +66,8 @@ "Windows", "Threat Detection", "Privilege Escalation", - "Active Directory" + "Active Directory", + "has_guide" ], "threat": [ { @@ -106,5 +107,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json index 87b5e7f040d5e..66325135325b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Privilege Escalation via InstallerFileTakeOver", - "note": "## Triage and analysis\n\n### Investigating Potential Privilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized escalation of privilege proof of concept (EoP PoC) to the CVE-2021-41379 vulnerability. Upon successful exploitation, an\nunprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copies itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked),\nwhich is outside the scope of this rule.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Look for additional processes spawned by the process, command lines, and network communications.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Verify whether a digital signature exists in the executable, and if it is valid.\n\n### Related rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Potential Privilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized escalation of privilege proof of concept (EoP PoC) to the CVE-2021-41379 vulnerability. Upon successful exploitation, an\nunprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copies itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked),\nwhich is outside the scope of this rule.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Look for additional processes spawned by the process, command lines, and network communications.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Verify whether a digital signature exists in the executable, and if it is valid.\n\n### Related rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "/* This rule is compatible with both Sysmon and Elastic Endpoint */\n\nprocess where event.type == \"start\" and\n (?process.Ext.token.integrity_level_name : \"System\" or\n ?winlog.event_data.IntegrityLevel : \"System\") and\n (\n (process.name : \"elevation_service.exe\" and\n not process.pe.original_file_name == \"elevation_service.exe\") or\n\n (process.parent.name : \"elevation_service.exe\" and\n process.name : (\"rundll32.exe\", \"cmd.exe\", \"powershell.exe\"))\n )\n", "references": [ "https://github.com/klinix5/InstallerFileTakeOver" @@ -58,7 +58,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { @@ -79,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json index 196676058b1fa..b116edce296b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious DLL Loaded for Persistence or Privilege Escalation", - "note": "", + "note": "## Triage and analysis\n\n### Investigating Suspicious DLL Loaded for Persistence or Privilege Escalation\n\nAttackers can execute malicious code by abusing missing modules that processes try to load, enabling them to escalate\nprivileges or gain persistence. This rule identifies the loading of a non-Microsoft-signed DLL that is missing on a\ndefault Windows installation or one that can be loaded from a different location by a native Windows process.\n\n#### Possible investigation steps\n\n- Examine the DLL signature and identify the process that created it.\n - Investigate any abnormal behaviors by the process such as network connections, registry or file modifications, and\n any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the DLL and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "any where\n (event.category == \"library\" or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n (\n /* compatible with Elastic Endpoint Library Events */\n (dll.name : (\"wlbsctrl.dll\", \"wbemcomn.dll\", \"WptsExtensions.dll\", \"Tsmsisrv.dll\", \"TSVIPSrv.dll\", \"Msfte.dll\",\n \"wow64log.dll\", \"WindowsCoreDeviceInfo.dll\", \"Ualapi.dll\", \"wlanhlp.dll\", \"phoneinfo.dll\", \"EdgeGdi.dll\",\n \"cdpsgshims.dll\", \"windowsperformancerecordercontrol.dll\", \"diagtrack_win.dll\")\n and (dll.code_signature.trusted == false or dll.code_signature.exists == false)) or\n\n /* compatible with Sysmon EventID 7 - Image Load */\n (file.name : (\"wlbsctrl.dll\", \"wbemcomn.dll\", \"WptsExtensions.dll\", \"Tsmsisrv.dll\", \"TSVIPSrv.dll\", \"Msfte.dll\",\n \"wow64log.dll\", \"WindowsCoreDeviceInfo.dll\", \"Ualapi.dll\", \"wlanhlp.dll\", \"phoneinfo.dll\", \"EdgeGdi.dll\",\n \"cdpsgshims.dll\", \"windowsperformancerecordercontrol.dll\", \"diagtrack_win.dll\")\n and not file.code_signature.status == \"Valid\")\n )\n", "references": [ "https://itm4n.github.io/windows-dll-hijacking-clarified/", @@ -119,5 +119,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json index 03828b39ddf49..365caf6dd1426 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service including CVE-2020-1048 and CVE-2020-1337. .", + "description": "Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service including CVE-2020-1048 and CVE-2020-1337.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,8 +11,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Suspicious PrintSpooler SPL File Created", - "note": "## Threat intel\n\nRefer to CVEs, CVE-2020-1048 and CVE-2020-1337 for further information on the vulnerability and exploit. Verify that the relevant system is patched.", + "name": "Suspicious Print Spooler SPL File Created", + "note": "## Triage and analysis\n\n### Investigating Suspicious Print Spooler SPL File Created\n\nPrint Spooler is a Windows service enabled by default in all Windows clients and servers. The service manages print jobs\nby loading printer drivers, receiving files to be printed, queuing them, scheduling, etc.\n\nThe Print Spooler service has some known vulnerabilities that attackers can abuse to escalate privileges to SYSTEM, like\nCVE-2020-1048 and CVE-2020-1337. This rule looks for unusual processes writing SPL files to the location\n`?:\\Windows\\System32\\spool\\PRINTERS\\`, which is an essential step in exploiting these vulnerabilities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof process executable and file conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Ensure that the machine has the latest security updates and is not running legacy Windows versions.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type != \"deletion\" and\n file.extension : \"spl\" and\n file.path : \"?:\\\\Windows\\\\System32\\\\spool\\\\PRINTERS\\\\*\" and\n not process.name : (\"spoolsv.exe\",\n \"printfilterpipelinesvc.exe\",\n \"PrintIsolationHost.exe\",\n \"splwow64.exe\",\n \"msiexec.exe\",\n \"poqexec.exe\")\n", "references": [ "https://safebreach.com/Post/How-we-bypassed-CVE-2020-1048-Patch-and-got-CVE-2020-1337" @@ -69,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json index 8dc5e6ec6f221..01b6ebd880189 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json @@ -69,7 +69,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -90,5 +91,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json index 217b774ef77c1..6aeccd8896875 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Bypass UAC via Event Viewer", - "note": "## Triage and analysis\n\n### Investigating Bypass UAC via Event Viewer\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nDuring startup, `eventvwr.exe` checks the registry value of the `HKCU\\Software\\Classes\\mscfile\\shell\\open\\command`\nregistry key for the location of `mmc.exe`, which is used to open the `eventvwr.msc` saved console file. If the location\nof another binary or script is added to this registry value, it will be executed as a high-integrity process without a\nUAC prompt being displayed to the user. This rule detects this UAC bypass by monitoring processes spawned by\n`eventvwr.exe` other than `mmc.exe` and `werfault.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Bypass UAC via Event Viewer\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nDuring startup, `eventvwr.exe` checks the registry value of the `HKCU\\Software\\Classes\\mscfile\\shell\\open\\command`\nregistry key for the location of `mmc.exe`, which is used to open the `eventvwr.msc` saved console file. If the location\nof another binary or script is added to this registry value, it will be executed as a high-integrity process without a\nUAC prompt being displayed to the user. This rule detects this UAC bypass by monitoring processes spawned by\n`eventvwr.exe` other than `mmc.exe` and `werfault.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : \"eventvwr.exe\" and\n not process.executable :\n (\"?:\\\\Windows\\\\SysWOW64\\\\mmc.exe\",\n \"?:\\\\Windows\\\\System32\\\\mmc.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\WerFault.exe\",\n \"?:\\\\Windows\\\\System32\\\\WerFault.exe\")\n", "required_fields": [ { @@ -40,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json index a4621ff4b883a..a1ae394cbcd9a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "UAC Bypass Attempt via Windows Directory Masquerading", - "note": "## Triage and analysis\n\n### Investigating UAC Bypass Attempt via Windows Directory Masquerading\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nThis rule identifies an attempt to bypass User Account Control (UAC) by masquerading as a Microsoft trusted Windows\ndirectory. Attackers may bypass UAC to stealthily execute code with elevated permissions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- If any of the spawned processes are suspicious, retrieve them and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating UAC Bypass Attempt via Windows Directory Masquerading\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nThis rule identifies an attempt to bypass User Account Control (UAC) by masquerading as a Microsoft trusted Windows\ndirectory. Attackers may bypass UAC to stealthily execute code with elevated permissions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- If any of the spawned processes are suspicious, retrieve them and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.args : (\"C:\\\\Windows \\\\system32\\\\*.exe\", \"C:\\\\Windows \\\\SysWOW64\\\\*.exe\")\n", "references": [ "https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e" @@ -38,7 +38,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json index 0ed27cf021601..877d0a3ca31a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "UAC Bypass via Windows Firewall Snap-In Hijack", - "note": "## Triage and analysis\n\n### Investigating UAC Bypass via Windows Firewall Snap-In Hijack\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nThis rule identifies attempts to bypass User Account Control (UAC) by hijacking the Microsoft Management Console (MMC)\nWindows Firewall snap-in. Attackers bypass UAC to stealthily execute code with elevated permissions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- If any of the spawned processes are suspicious, retrieve them and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating UAC Bypass via Windows Firewall Snap-In Hijack\n\nWindows User Account Control (UAC) allows a program to elevate its privileges (tracked as low to high integrity levels)\nto perform a task under administrator-level permissions, possibly by prompting the user for confirmation.\nUAC can deny an operation under high-integrity enforcement, or allow the user to perform the action if they are in the\nlocal administrators group and enter an administrator password when prompted.\n\nFor more information about the UAC and how it works, check the [official Microsoft docs page](https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works).\n\nThis rule identifies attempts to bypass User Account Control (UAC) by hijacking the Microsoft Management Console (MMC)\nWindows Firewall snap-in. Attackers bypass UAC to stealthily execute code with elevated permissions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- If any of the spawned processes are suspicious, retrieve them and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name == \"mmc.exe\" and\n /* process.Ext.token.integrity_level_name == \"high\" can be added in future for tuning */\n /* args of the Windows Firewall SnapIn */\n process.parent.args == \"WF.msc\" and process.name != \"WerFault.exe\"\n", "references": [ "https://github.com/AzAgarampur/byeintegrity-uac" @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json index addabf9f61bdd..253a10c4d45a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual Parent-Child Relationship", - "note": "## Triage and analysis\n\n### Investigating Unusual Parent-Child Relationship\n\nWindows internal/system processes have some characteristics that can be used to spot suspicious activities. One of these\ncharacteristics is parent-child relationships. These relationships can be used to baseline the typical behavior of the\nsystem and then alert on occurrences that don't comply with the baseline.\n\nThis rule uses this information to spot suspicious parent and child processes.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating Unusual Parent-Child Relationship\n\nWindows internal/system processes have some characteristics that can be used to spot suspicious activities. One of these\ncharacteristics is parent-child relationships. These relationships can be used to baseline the typical behavior of the\nsystem and then alert on occurrences that don't comply with the baseline.\n\nThis rule uses this information to spot suspicious parent and child processes.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal behavior by the subject process such as network connections, registry or file modifications,\nand any spawned child processes.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\nprocess.parent.name != null and\n (\n /* suspicious parent processes */\n (process.name:\"autochk.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:(\"fontdrvhost.exe\", \"dwm.exe\") and not process.parent.name:(\"wininit.exe\", \"winlogon.exe\")) or\n (process.name:(\"consent.exe\", \"RuntimeBroker.exe\", \"TiWorker.exe\") and not process.parent.name:\"svchost.exe\") or\n (process.name:\"SearchIndexer.exe\" and not process.parent.name:\"services.exe\") or\n (process.name:\"SearchProtocolHost.exe\" and not process.parent.name:(\"SearchIndexer.exe\", \"dllhost.exe\")) or\n (process.name:\"dllhost.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"smss.exe\" and not process.parent.name:(\"System\", \"smss.exe\")) or\n (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\", \"svchost.exe\")) or\n (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:(\"lsass.exe\", \"LsaIso.exe\") and not process.parent.name:\"wininit.exe\") or\n (process.name:\"LogonUI.exe\" and not process.parent.name:(\"wininit.exe\", \"winlogon.exe\")) or\n (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or\n (process.name:\"svchost.exe\" and not process.parent.name:(\"MsMpEng.exe\", \"services.exe\")) or\n (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or\n (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\", \"winlogon.exe\")) or\n (process.name:(\"wmiprvse.exe\", \"wsmprovhost.exe\", \"winrshost.exe\") and not process.parent.name:\"svchost.exe\") or\n /* suspicious child processes */\n (process.parent.name:(\"SearchProtocolHost.exe\", \"taskhost.exe\", \"csrss.exe\") and not process.name:(\"werfault.exe\", \"wermgr.exe\", \"WerFaultSecure.exe\")) or\n (process.parent.name:\"autochk.exe\" and not process.name:(\"chkdsk.exe\", \"doskey.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"smss.exe\" and not process.name:(\"autochk.exe\", \"smss.exe\", \"csrss.exe\", \"wininit.exe\", \"winlogon.exe\", \"setupcl.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"wermgr.exe\" and not process.name:(\"WerFaultSecure.exe\", \"wermgr.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"conhost.exe\" and not process.name:(\"mscorsvw.exe\", \"wermgr.exe\", \"WerFault.exe\", \"WerFaultSecure.exe\"))\n )\n", "references": [ "https://github.com/sbousseaden/Slides/blob/master/Hunting%20MindMaps/PNG/Windows%20Processes%20TH.map.png", @@ -44,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json index 9045cc9217fbb..b632697515ada 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json @@ -59,7 +59,8 @@ "AWS", "Continuous Monitoring", "SecOps", - "Identity and Access" + "Identity and Access", + "has_guide" ], "threat": [ { @@ -80,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json index 8c4ad87e57532..2e1094d9b4fca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json @@ -63,7 +63,8 @@ "Network", "Continuous Monitoring", "SecOps", - "Monitoring" + "Monitoring", + "has_guide" ], "threat_filters": [ { @@ -226,5 +227,5 @@ "timeline_id": "495ad7a7-316e-4544-8a0f-9c098daee76e", "timeline_title": "Generic Threat Match Timeline", "type": "threat_match", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json index b35e5f72b20fb..62a1da18f9e6f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json @@ -63,7 +63,8 @@ "Network", "Continuous Monitoring", "SecOps", - "Monitoring" + "Monitoring", + "has_guide" ], "threat_filters": [ { @@ -226,5 +227,5 @@ "timeline_id": "495ad7a7-316e-4544-8a0f-9c098daee76e", "timeline_title": "Generic Threat Match Timeline", "type": "threat_match", - "version": 100 + "version": 101 } From 3598af151ed2112214573f07482ac43f4f82c7eb Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:29:26 -0600 Subject: [PATCH 046/172] Fix allowing more than 5 snooze schedules on a rule (#141835) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/rules_client/rules_client.ts | 26 +++++-- .../rules_client/tests/bulk_edit.test.ts | 2 +- .../panel/base_snooze_panel.test.tsx | 64 ++++++++++++++++ .../rule_snooze/panel/base_snooze_panel.tsx | 13 +++- .../components/rule_snooze/scheduler.tsx | 2 +- .../spaces_only/tests/alerting/snooze.ts | 74 +++++++++++++++++++ 6 files changed, 170 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 79837cdfed4cc..ed330dbd22e08 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1798,11 +1798,10 @@ export class RulesClient { } if (operation.operation === 'set') { const snoozeAttributes = getSnoozeAttributes(attributes, operation.value); - const schedules = snoozeAttributes.snoozeSchedule.filter((snooze) => snooze.id); - if (schedules.length > 5) { - throw Error( - `Error updating rule: rule cannot have more than 5 snooze schedules` - ); + try { + verifySnoozeScheduleLimit(snoozeAttributes); + } catch (error) { + throw Error(`Error updating rule: could not add snooze - ${error.message}`); } attributes = { ...attributes, @@ -2433,6 +2432,12 @@ export class RulesClient { const newAttrs = getSnoozeAttributes(attributes, snoozeSchedule); + try { + verifySnoozeScheduleLimit(newAttrs); + } catch (error) { + throw Boom.badRequest(error.message); + } + const updateAttributes = this.updateMeta({ ...newAttrs, updatedBy: await this.getUserName(), @@ -3299,3 +3304,14 @@ function clearCurrentActiveSnooze(attributes: RawRule) { }); return clearedSnoozesAndSkippedRecurringSnoozes; } + +function verifySnoozeScheduleLimit(attributes: Partial) { + const schedules = attributes.snoozeSchedule?.filter((snooze) => snooze.id); + if (schedules && schedules.length > 5) { + throw Error( + i18n.translate('xpack.alerting.rulesClient.snoozeSchedule.limitReached', { + defaultMessage: 'Rule cannot have more than 5 snooze schedules', + }) + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts index 5ab3de879c3c9..1cafcd652349a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts @@ -486,7 +486,7 @@ describe('bulkEdit()', () => { }); expect(response.errors.length).toEqual(1); expect(response.errors[0].message).toEqual( - 'Error updating rule: rule cannot have more than 5 snooze schedules' + 'Error updating rule: could not add snooze - Rule cannot have more than 5 snooze schedules' ); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.test.tsx index eb432522c1188..6749a062a1a22 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.test.tsx @@ -105,6 +105,9 @@ describe('BaseSnoozePanel', () => { expect(wrapper.find('[data-test-subj="ruleAddSchedule"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="ruleRemoveAllSchedules"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="ruleSchedulesListAddButton"]').exists()).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="ruleSchedulesListAddButton"]').first().prop('isDisabled') + ).toBeFalsy(); expect( wrapper @@ -114,4 +117,65 @@ describe('BaseSnoozePanel', () => { .filter((e) => Boolean(e.key)).length ).toEqual(2); }); + test('should disable add snooze schedule button if rule has more than 5 schedules', () => { + const wrapper = mountWithIntl( + + ); + + expect(wrapper.find('[data-test-subj="ruleSchedulesListAddButton"]').exists()).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="ruleSchedulesListAddButton"]').first().prop('isDisabled') + ).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.tsx index 2ea8752341ce5..437c75aa3ab2a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/base_snooze_panel.tsx @@ -139,10 +139,14 @@ export const BaseSnoozePanel: React.FunctionComponent = ({ onRemoveAllSchedules(scheduledSnoozes!.filter((s) => s.id).map((s) => s.id as string)); }, [onRemoveAllSchedules, scheduledSnoozes]); - const hasSchedules = useMemo( - () => scheduledSnoozes && scheduledSnoozes.filter((s) => Boolean(s.id)).length > 0, - [scheduledSnoozes] - ); + const numberOfSchedules = useMemo(() => { + if (!scheduledSnoozes) { + return 0; + } + return scheduledSnoozes.filter((s) => Boolean(s.id)).length; + }, [scheduledSnoozes]); + + const hasSchedules = useMemo(() => numberOfSchedules > 0, [numberOfSchedules]); const renderSchedule = () => { if (!showAddSchedule) { @@ -236,6 +240,7 @@ export const BaseSnoozePanel: React.FunctionComponent = ({ = 5} data-test-subj="ruleSchedulesListAddButton" iconType="plusInCircleFilled" onClick={onClickAddSchedule} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx index 179a92b6c7e8e..4b559e262424d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx @@ -368,7 +368,7 @@ const RuleSnoozeSchedulerPanel: React.FunctionComponent = ({ {i18n.translate('xpack.triggersActionsUI.sections.rulesList.deleteSchedule', { - defaultMessage: 'Delete schedules', + defaultMessage: 'Delete schedule', })} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts index 02340f69718d6..a7301d1467bf7 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import uuid from 'uuid'; import { Spaces } from '../../scenarios'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -240,6 +241,79 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(actionsAfter).to.be.greaterThan(0, 'no actions triggered after snooze'); expect(actionsDuring).to.be(0); }); + + it('should prevent more than 5 schedules from being added to a rule', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + }) + ) + .expect(200); + objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); + + // Creating 5 snooze schedules, using Promise.all is very flaky, therefore + // the schedules are being created 1 at a time + await alertUtils + .getSnoozeRequest(createdRule.id) + .send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }) + .expect(204); + await alertUtils + .getSnoozeRequest(createdRule.id) + .send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }) + .expect(204); + await alertUtils + .getSnoozeRequest(createdRule.id) + .send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }) + .expect(204); + + await alertUtils + .getSnoozeRequest(createdRule.id) + .send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }) + .expect(204); + + await alertUtils + .getSnoozeRequest(createdRule.id) + .send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }) + .expect(204); + + // Adding the 6th snooze schedule, should fail + const response = await alertUtils.getSnoozeRequest(createdRule.id).send({ + snooze_schedule: { + ...SNOOZE_SCHEDULE, + id: uuid.v4(), + }, + }); + expect(response.statusCode).to.eql(400); + expect(response.body.message).to.eql('Rule cannot have more than 5 snooze schedules'); + }); }); async function getRuleEvents(id: string, minActions: number = 1) { From bded4672f9cd976e7b69593697d9219abba33bf9 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Mon, 26 Sep 2022 18:48:42 -0700 Subject: [PATCH 047/172] [Security Solution][Alerts] Remove new terms rule type tour (#141293) * Remove new terms rule type tour * Remove unused storage constant --- .../security_solution/common/constants.ts | 3 - .../pages/detection_engine/rules/index.tsx | 25 +++---- .../pages/detection_engine/rules/tour.tsx | 69 ------------------- .../detection_engine/rules/translations.ts | 14 ---- 4 files changed, 11 insertions(+), 100 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/tour.tsx diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index c56170fde9863..967e16d323874 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -450,9 +450,6 @@ export const NEW_FEATURES_TOUR_STORAGE_KEYS = { RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.4', }; -export const RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY = - 'securitySolution.rulesManagementPage.newFeaturesTour.v8.4'; - export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY = 'securitySolution.ruleDetails.ruleExecutionLog.showMetrics.v8.2'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index f771eb46814da..997f40e918cb2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -42,7 +42,6 @@ import { useInvalidateRules } from '../../../containers/detection_engine/rules/u import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; -import { RulesPageTourComponent } from './tour'; const RulesPageComponent: React.FC = () => { const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); @@ -231,19 +230,17 @@ const RulesPageComponent: React.FC = () => { {i18n.IMPORT_RULE} - - - - {i18n.ADD_NEW_RULE} - - - + + + {i18n.ADD_NEW_RULE} + + {(prePackagedRuleStatus === 'ruleNeedUpdate' || diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/tour.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/tour.tsx deleted file mode 100644 index c13f6a7f67d40..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/tour.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiText, EuiTourStep } from '@elastic/eui'; -import React, { useCallback, useEffect, useState } from 'react'; -import { RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY } from '../../../../../common/constants'; -import { useKibana } from '../../../../common/lib/kibana'; -import * as i18n from './translations'; - -export interface Props { - children: React.ReactElement; -} - -export const RulesPageTourComponent: React.FC = ({ children }) => { - const tourConfig = { - currentTourStep: 1, - isTourActive: true, - tourPopoverWidth: 300, - }; - - const { storage } = useKibana().services; - - const [tourState, setTourState] = useState(() => { - const restoredTourState = storage.get(RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY); - - if (restoredTourState != null) { - return restoredTourState; - } - return tourConfig; - }); - - const demoTourSteps = [ - { - step: 1, - title: i18n.NEW_TERMS_TOUR_TITLE, - content: {i18n.NEW_TERMS_TOUR_CONTENT}, - }, - ]; - const finishTour = useCallback(() => { - setTourState({ - ...tourState, - isTourActive: false, - }); - }, [tourState]); - - useEffect(() => { - storage.set(RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY, tourState); - }, [tourState, storage]); - - return ( - - {children} - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 1a5d16f719eba..f49e4695731bc 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -1084,20 +1084,6 @@ export const RULES_BULK_EDIT_FAILURE_DESCRIPTION = (rulesCount: number) => } ); -export const NEW_TERMS_TOUR_TITLE = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.tour.newTermsTitle', - { - defaultMessage: 'A new Security Rule type is available!', - } -); - -export const NEW_TERMS_TOUR_CONTENT = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.tour.newTermsContent', - { - defaultMessage: '"New Terms" rules alert on values that have not previously been seen', - } -); - export const RULE_PREVIEW_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.rulePreviewTitle', { From 159eb81b8d034e50d2dd16101256e83b2096563e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 26 Sep 2022 22:43:36 -0600 Subject: [PATCH 048/172] [api-docs] Daily api_docs build (#141887) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.devdocs.json | 96 + api_docs/data.mdx | 4 +- api_docs/data_query.mdx | 4 +- api_docs/data_search.devdocs.json | 6970 +++++++++++------ api_docs/data_search.mdx | 4 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.devdocs.json | 29 + api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- .../kbn_core_injected_metadata_browser.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.devdocs.json | 36 +- api_docs/kbn_journeys.mdx | 4 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ...solution_io_ts_alerting_types.devdocs.json | 8 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.devdocs.json | 68 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 12 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 4 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.devdocs.json | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.devdocs.json | 14 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 1195 ++- api_docs/visualizations.mdx | 4 +- 412 files changed, 6087 insertions(+), 3161 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index ede1ddfd1b090..c298d8a395df6 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 9dd079407ab6c..6da505acae8b7 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 5bf8f6542ccd1..4e5199b31ef9c 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index d858edbdedf9b..df1ba878d7e68 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 0afb756926159..84c92a1ce8643 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index fc7599f168896..e10dd6028fafe 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index c63dca849d910..504dc027276f0 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 88da1736d5ebd..5dc73b2b9000b 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 72b9e7b6e447e..aaa279d698443 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 3ead0fe10942f..5e129b2343ca0 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 0de2f3b987f74..5c1a4fea8ca5c 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 8c2b0d3bdda55..6314cca4f901b 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 5fa699f9f0bd2..9c7112ee6e3ad 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index f8d553ceb4bfe..a673eebc1e1c6 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 67c3f4c2c8711..849d5fc71f968 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 36b55369e5c0a..d63230e7ed586 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 8faa9b144c520..7c8ab77b2d5ac 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 1f8a54716d56a..507845391c779 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 10e75395926d1..ef9680b1325db 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index bed0278714e2a..10ca74caf4ce6 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -9057,6 +9057,102 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-public.ExpressionFunctionKql", + "type": "Type", + "tags": [], + "label": "ExpressionFunctionKql", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"kql\", null, Arguments, ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.KibanaQueryOutput", + "text": "KibanaQueryOutput" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableRecord", + ">>" + ], + "path": "src/plugins/data/common/search/expressions/kql.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-public.ExpressionFunctionLucene", + "type": "Type", + "tags": [], + "label": "ExpressionFunctionLucene", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"lucene\", null, Arguments, ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.KibanaQueryOutput", + "text": "KibanaQueryOutput" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableRecord", + ">>" + ], + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-public.ExpressionValueSearchContext", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 59dd44dc07763..f849f08374ded 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3132 | 33 | 2429 | 23 | +| 3211 | 33 | 2508 | 23 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index c8c053c7b5ca5..bb88b05d16a92 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3132 | 33 | 2429 | 23 | +| 3211 | 33 | 2508 | 23 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index c3cfd123f1166..415500b499045 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -18687,9 +18687,14 @@ "label": "customMetric", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], "path": "src/plugins/data/common/search/aggs/metrics/bucket_avg.ts", "deprecated": false, @@ -18703,9 +18708,14 @@ "label": "customBucket", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], "path": "src/plugins/data/common/search/aggs/metrics/bucket_avg.ts", "deprecated": false, @@ -18716,18 +18726,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMax", + "id": "def-common.AggParamsBucketAvgSerialized", "type": "Interface", "tags": [], - "label": "AggParamsBucketMax", + "label": "AggParamsBucketAvgSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsBucketMax", - "text": "AggParamsBucketMax" + "section": "def-common.AggParamsBucketAvgSerialized", + "text": "AggParamsBucketAvgSerialized" }, " extends ", { @@ -18738,13 +18748,13 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_avg.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMax.customMetric", + "id": "def-common.AggParamsBucketAvgSerialized.customMetric", "type": "Object", "tags": [], "label": "customMetric", @@ -18754,13 +18764,13 @@ "SerializableRecord", " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_avg.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMax.customBucket", + "id": "def-common.AggParamsBucketAvgSerialized.customBucket", "type": "Object", "tags": [], "label": "customBucket", @@ -18770,7 +18780,7 @@ "SerializableRecord", " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_avg.ts", "deprecated": false, "trackAdoption": false } @@ -18779,18 +18789,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMin", + "id": "def-common.AggParamsBucketMax", "type": "Interface", "tags": [], - "label": "AggParamsBucketMin", + "label": "AggParamsBucketMax", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsBucketMin", - "text": "AggParamsBucketMin" + "section": "def-common.AggParamsBucketMax", + "text": "AggParamsBucketMax" }, " extends ", { @@ -18801,39 +18811,49 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMin.customMetric", + "id": "def-common.AggParamsBucketMax.customMetric", "type": "Object", "tags": [], "label": "customMetric", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketMin.customBucket", + "id": "def-common.AggParamsBucketMax.customBucket", "type": "Object", "tags": [], "label": "customBucket", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false } @@ -18842,18 +18862,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketSum", + "id": "def-common.AggParamsBucketMaxSerialized", "type": "Interface", "tags": [], - "label": "AggParamsBucketSum", + "label": "AggParamsBucketMaxSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsBucketSum", - "text": "AggParamsBucketSum" + "section": "def-common.AggParamsBucketMaxSerialized", + "text": "AggParamsBucketMaxSerialized" }, " extends ", { @@ -18864,13 +18884,13 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsBucketSum.customMetric", + "id": "def-common.AggParamsBucketMaxSerialized.customMetric", "type": "Object", "tags": [], "label": "customMetric", @@ -18880,13 +18900,13 @@ "SerializableRecord", " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsBucketSum.customBucket", + "id": "def-common.AggParamsBucketMaxSerialized.customBucket", "type": "Object", "tags": [], "label": "customBucket", @@ -18896,7 +18916,7 @@ "SerializableRecord", " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_max.ts", "deprecated": false, "trackAdoption": false } @@ -18905,18 +18925,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsCardinality", + "id": "def-common.AggParamsBucketMin", "type": "Interface", "tags": [], - "label": "AggParamsCardinality", + "label": "AggParamsBucketMin", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsCardinality", - "text": "AggParamsCardinality" + "section": "def-common.AggParamsBucketMin", + "text": "AggParamsBucketMin" }, " extends ", { @@ -18927,32 +18947,49 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsCardinality.field", - "type": "string", + "id": "def-common.AggParamsBucketMin.customMetric", + "type": "Object", "tags": [], - "label": "field", + "label": "customMetric", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsCardinality.emptyAsNull", - "type": "CompoundType", + "id": "def-common.AggParamsBucketMin.customBucket", + "type": "Object", "tags": [], - "label": "emptyAsNull", + "label": "customBucket", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", "deprecated": false, "trackAdoption": false } @@ -18961,18 +18998,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsCount", + "id": "def-common.AggParamsBucketMinSerialized", "type": "Interface", "tags": [], - "label": "AggParamsCount", + "label": "AggParamsBucketMinSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsCount", - "text": "AggParamsCount" + "section": "def-common.AggParamsBucketMinSerialized", + "text": "AggParamsBucketMinSerialized" }, " extends ", { @@ -18983,21 +19020,39 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/count.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsCount.emptyAsNull", - "type": "CompoundType", + "id": "def-common.AggParamsBucketMinSerialized.customMetric", + "type": "Object", "tags": [], - "label": "emptyAsNull", + "label": "customMetric", "description": [], "signature": [ - "boolean | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/count.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsBucketMinSerialized.customBucket", + "type": "Object", + "tags": [], + "label": "customBucket", + "description": [], + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/bucket_min.ts", "deprecated": false, "trackAdoption": false } @@ -19006,18 +19061,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsCumulativeSum", + "id": "def-common.AggParamsBucketSum", "type": "Interface", "tags": [], - "label": "AggParamsCumulativeSum", + "label": "AggParamsBucketSum", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsCumulativeSum", - "text": "AggParamsCumulativeSum" + "section": "def-common.AggParamsBucketSum", + "text": "AggParamsBucketSum" }, " extends ", { @@ -19028,51 +19083,49 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsCumulativeSum.buckets_path", - "type": "string", - "tags": [], - "label": "buckets_path", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsCumulativeSum.customMetric", + "id": "def-common.AggParamsBucketSum.customMetric", "type": "Object", "tags": [], "label": "customMetric", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsCumulativeSum.metricAgg", - "type": "string", + "id": "def-common.AggParamsBucketSum.customBucket", + "type": "Object", "tags": [], - "label": "metricAgg", + "label": "customBucket", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false } @@ -19081,18 +19134,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram", + "id": "def-common.AggParamsBucketSumSerialized", "type": "Interface", "tags": [], - "label": "AggParamsDateHistogram", + "label": "AggParamsBucketSumSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsDateHistogram", - "text": "AggParamsDateHistogram" + "section": "def-common.AggParamsBucketSumSerialized", + "text": "AggParamsBucketSumSerialized" }, " extends ", { @@ -19103,199 +19156,140 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.field", - "type": "CompoundType", + "id": "def-common.AggParamsBucketSumSerialized.customMetric", + "type": "Object", "tags": [], - "label": "field", + "label": "customMetric", "description": [], "signature": [ - "string | ", - "DataViewFieldBase", - " | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.timeRange", + "id": "def-common.AggParamsBucketSumSerialized.customBucket", "type": "Object", "tags": [], - "label": "timeRange", + "label": "customBucket", "description": [], "signature": [ - "TimeRange", - " | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/bucket_sum.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.useNormalizedEsInterval", - "type": "CompoundType", - "tags": [], - "label": "useNormalizedEsInterval", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.scaleMetricValues", - "type": "CompoundType", - "tags": [], - "label": "scaleMetricValues", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.interval", - "type": "string", - "tags": [], - "label": "interval", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsCardinality", + "type": "Interface", + "tags": [], + "label": "AggParamsCardinality", + "description": [], + "signature": [ { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.used_interval", - "type": "string", - "tags": [], - "label": "used_interval", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsCardinality", + "text": "AggParamsCardinality" }, + " extends ", { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.time_zone", - "type": "string", - "tags": [], - "label": "time_zone", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.used_time_zone", + "id": "def-common.AggParamsCardinality.field", "type": "string", "tags": [], - "label": "used_time_zone", + "label": "field", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.drop_partials", + "id": "def-common.AggParamsCardinality.emptyAsNull", "type": "CompoundType", "tags": [], - "label": "drop_partials", + "label": "emptyAsNull", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.format", - "type": "string", - "tags": [], - "label": "format", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cardinality.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsCount", + "type": "Interface", + "tags": [], + "label": "AggParamsCount", + "description": [], + "signature": [ { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.min_doc_count", - "type": "number", - "tags": [], - "label": "min_doc_count", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsCount", + "text": "AggParamsCount" }, + " extends ", { - "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.extended_bounds", - "type": "Object", - "tags": [], - "label": "extended_bounds", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExtendedBounds", - "text": "ExtendedBounds" - }, - " | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - }, + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/count.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDateHistogram.extendToTimeRange", + "id": "def-common.AggParamsCount.emptyAsNull", "type": "CompoundType", "tags": [], - "label": "extendToTimeRange", + "label": "emptyAsNull", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/count.ts", "deprecated": false, "trackAdoption": false } @@ -19304,78 +19298,50 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsDateRange", + "id": "def-common.AggParamsCumulativeSum", "type": "Interface", "tags": [], - "label": "AggParamsDateRange", + "label": "AggParamsCumulativeSum", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsDateRange", - "text": "AggParamsDateRange" + "section": "def-common.AggParamsCumulativeSum", + "text": "AggParamsCumulativeSum" }, " extends ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" + "section": "def-common.CommonAggParamsCumulativeSum", + "text": "CommonAggParamsCumulativeSum" } ], - "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDateRange.field", - "type": "string", - "tags": [], - "label": "field", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateRange.ranges", - "type": "Array", + "id": "def-common.AggParamsCumulativeSum.customMetric", + "type": "Object", "tags": [], - "label": "ranges", + "label": "customMetric", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.DateRange", - "text": "DateRange" + "section": "def-common.AggConfig", + "text": "AggConfig" }, - "[] | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDateRange.time_zone", - "type": "string", - "tags": [], - "label": "time_zone", - "description": [], - "signature": [ - "string | undefined" + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, "trackAdoption": false } @@ -19384,73 +19350,45 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsDerivative", + "id": "def-common.AggParamsCumulativeSumSerialized", "type": "Interface", "tags": [], - "label": "AggParamsDerivative", + "label": "AggParamsCumulativeSumSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsDerivative", - "text": "AggParamsDerivative" + "section": "def-common.AggParamsCumulativeSumSerialized", + "text": "AggParamsCumulativeSumSerialized" }, " extends ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" + "section": "def-common.CommonAggParamsCumulativeSum", + "text": "CommonAggParamsCumulativeSum" } ], - "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDerivative.buckets_path", - "type": "string", + "id": "def-common.AggParamsCumulativeSumSerialized.customMetric", + "type": "Object", "tags": [], - "label": "buckets_path", + "label": "customMetric", "description": [], "signature": [ - "string | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDerivative.customMetric", - "type": "Object", - "tags": [], - "label": "customMetric", - "description": [], - "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsDerivative.metricAgg", - "type": "string", - "tags": [], - "label": "metricAgg", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, "trackAdoption": false } @@ -19459,18 +19397,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsDiversifiedSampler", + "id": "def-common.AggParamsDateHistogram", "type": "Interface", "tags": [], - "label": "AggParamsDiversifiedSampler", + "label": "AggParamsDateHistogram", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsDiversifiedSampler", - "text": "AggParamsDiversifiedSampler" + "section": "def-common.AggParamsDateHistogram", + "text": "AggParamsDateHistogram" }, " extends ", { @@ -19481,139 +19419,199 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsDiversifiedSampler.field", - "type": "string", + "id": "def-common.AggParamsDateHistogram.field", + "type": "CompoundType", "tags": [], "label": "field", - "description": [ - "\nIs used to provide values used for de-duplication" + "description": [], + "signature": [ + "string | ", + "DataViewFieldBase", + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsDiversifiedSampler.shard_size", - "type": "number", + "id": "def-common.AggParamsDateHistogram.timeRange", + "type": "Object", "tags": [], - "label": "shard_size", - "description": [ - "\nLimits how many top-scoring documents are collected in the sample processed on each shard." + "label": "timeRange", + "description": [], + "signature": [ + "TimeRange", + " | undefined" ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.useNormalizedEsInterval", + "type": "CompoundType", + "tags": [], + "label": "useNormalizedEsInterval", + "description": [], "signature": [ - "number | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsDiversifiedSampler.max_docs_per_value", - "type": "number", + "id": "def-common.AggParamsDateHistogram.scaleMetricValues", + "type": "CompoundType", "tags": [], - "label": "max_docs_per_value", - "description": [ - "\nLimits how many documents are permitted per choice of de-duplicating value" + "label": "scaleMetricValues", + "description": [], + "signature": [ + "boolean | undefined" ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.interval", + "type": "string", + "tags": [], + "label": "interval", + "description": [], "signature": [ - "number | undefined" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsFilter", - "type": "Interface", - "tags": [], - "label": "AggParamsFilter", - "description": [], - "signature": [ + }, { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsFilter", - "text": "AggParamsFilter" + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.used_interval", + "type": "string", + "tags": [], + "label": "used_interval", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false }, - " extends ", { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.time_zone", + "type": "string", + "tags": [], + "label": "time_zone", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilter.geo_bounding_box", + "id": "def-common.AggParamsDateHistogram.used_time_zone", + "type": "string", + "tags": [], + "label": "used_time_zone", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.drop_partials", "type": "CompoundType", "tags": [], - "label": "geo_bounding_box", + "label": "drop_partials", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.GeoBoundingBox", - "text": "GeoBoundingBox" - }, - " | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilter.filter", + "id": "def-common.AggParamsDateHistogram.format", + "type": "string", + "tags": [], + "label": "format", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.min_doc_count", + "type": "number", + "tags": [], + "label": "min_doc_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateHistogram.extended_bounds", "type": "Object", "tags": [], - "label": "filter", + "label": "extended_bounds", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.QueryFilter", - "text": "QueryFilter" + "section": "def-common.ExtendedBounds", + "text": "ExtendedBounds" }, " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilter.timeWindow", - "type": "string", + "id": "def-common.AggParamsDateHistogram.extendToTimeRange", + "type": "CompoundType", "tags": [], - "label": "timeWindow", + "label": "extendToTimeRange", "description": [], "signature": [ - "string | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, "trackAdoption": false } @@ -19622,18 +19620,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilteredMetric", + "id": "def-common.AggParamsDateRange", "type": "Interface", "tags": [], - "label": "AggParamsFilteredMetric", + "label": "AggParamsDateRange", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsFilteredMetric", - "text": "AggParamsFilteredMetric" + "section": "def-common.AggParamsDateRange", + "text": "AggParamsDateRange" }, " extends ", { @@ -19644,39 +19642,56 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsFilteredMetric.customMetric", - "type": "Object", + "id": "def-common.AggParamsDateRange.field", + "type": "string", "tags": [], - "label": "customMetric", + "label": "field", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilteredMetric.customBucket", - "type": "Object", + "id": "def-common.AggParamsDateRange.ranges", + "type": "Array", "tags": [], - "label": "customBucket", + "label": "ranges", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.DateRange", + "text": "DateRange" + }, + "[] | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDateRange.time_zone", + "type": "string", + "tags": [], + "label": "time_zone", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_range.ts", "deprecated": false, "trackAdoption": false } @@ -19685,51 +19700,50 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsFilters", + "id": "def-common.AggParamsDerivative", "type": "Interface", "tags": [], - "label": "AggParamsFilters", + "label": "AggParamsDerivative", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsFilters", - "text": "AggParamsFilters" + "section": "def-common.AggParamsDerivative", + "text": "AggParamsDerivative" }, - " extends Omit<", + " extends ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - }, - ", \"customLabel\">" + "section": "def-common.CommonAggParamsDerivative", + "text": "CommonAggParamsDerivative" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/filters.ts", + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsFilters.filters", - "type": "Array", + "id": "def-common.AggParamsDerivative.customMetric", + "type": "Object", "tags": [], - "label": "filters", + "label": "customMetric", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.QueryFilter", - "text": "QueryFilter" + "section": "def-common.AggConfig", + "text": "AggConfig" }, - "[] | undefined" + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/filters.ts", + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, "trackAdoption": false } @@ -19738,40 +19752,45 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoBounds", + "id": "def-common.AggParamsDerivativeSerialized", "type": "Interface", "tags": [], - "label": "AggParamsGeoBounds", + "label": "AggParamsDerivativeSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsGeoBounds", - "text": "AggParamsGeoBounds" + "section": "def-common.AggParamsDerivativeSerialized", + "text": "AggParamsDerivativeSerialized" }, " extends ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" + "section": "def-common.CommonAggParamsDerivative", + "text": "CommonAggParamsDerivative" } ], - "path": "src/plugins/data/common/search/aggs/metrics/geo_bounds.ts", + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsGeoBounds.field", - "type": "string", + "id": "def-common.AggParamsDerivativeSerialized.customMetric", + "type": "Object", "tags": [], - "label": "field", + "label": "customMetric", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/geo_bounds.ts", + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, "trackAdoption": false } @@ -19780,18 +19799,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoCentroid", + "id": "def-common.AggParamsDiversifiedSampler", "type": "Interface", "tags": [], - "label": "AggParamsGeoCentroid", + "label": "AggParamsDiversifiedSampler", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsGeoCentroid", - "text": "AggParamsGeoCentroid" + "section": "def-common.AggParamsDiversifiedSampler", + "text": "AggParamsDiversifiedSampler" }, " extends ", { @@ -19802,18 +19821,52 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/geo_centroid.ts", + "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsGeoCentroid.field", + "id": "def-common.AggParamsDiversifiedSampler.field", "type": "string", "tags": [], "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/geo_centroid.ts", + "description": [ + "\nIs used to provide values used for de-duplication" + ], + "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDiversifiedSampler.shard_size", + "type": "number", + "tags": [], + "label": "shard_size", + "description": [ + "\nLimits how many top-scoring documents are collected in the sample processed on each shard." + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsDiversifiedSampler.max_docs_per_value", + "type": "number", + "tags": [], + "label": "max_docs_per_value", + "description": [ + "\nLimits how many documents are permitted per choice of de-duplicating value" + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/diversified_sampler.ts", "deprecated": false, "trackAdoption": false } @@ -19822,18 +19875,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash", + "id": "def-common.AggParamsFilter", "type": "Interface", "tags": [], - "label": "AggParamsGeoHash", + "label": "AggParamsFilter", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsGeoHash", - "text": "AggParamsGeoHash" + "section": "def-common.AggParamsFilter", + "text": "AggParamsFilter" }, " extends ", { @@ -19844,95 +19897,63 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.field", - "type": "string", - "tags": [], - "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.autoPrecision", - "type": "CompoundType", - "tags": [], - "label": "autoPrecision", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.precision", - "type": "number", - "tags": [], - "label": "precision", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.useGeocentroid", + "id": "def-common.AggParamsFilter.geo_bounding_box", "type": "CompoundType", "tags": [], - "label": "useGeocentroid", + "label": "geo_bounding_box", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.GeoBoundingBox", + "text": "GeoBoundingBox" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.isFilteredByCollar", - "type": "CompoundType", + "id": "def-common.AggParamsFilter.filter", + "type": "Object", "tags": [], - "label": "isFilteredByCollar", + "label": "filter", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.QueryFilter", + "text": "QueryFilter" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoHash.boundingBox", - "type": "CompoundType", + "id": "def-common.AggParamsFilter.timeWindow", + "type": "string", "tags": [], - "label": "boundingBox", + "label": "timeWindow", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.GeoBoundingBox", - "text": "GeoBoundingBox" - }, - " | undefined" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", "deprecated": false, "trackAdoption": false } @@ -19941,18 +19962,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoTile", + "id": "def-common.AggParamsFilteredMetric", "type": "Interface", "tags": [], - "label": "AggParamsGeoTile", + "label": "AggParamsFilteredMetric", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsGeoTile", - "text": "AggParamsGeoTile" + "section": "def-common.AggParamsFilteredMetric", + "text": "AggParamsFilteredMetric" }, " extends ", { @@ -19963,46 +19984,49 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsGeoTile.field", - "type": "string", + "id": "def-common.AggParamsFilteredMetric.customMetric", + "type": "Object", "tags": [], - "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsGeoTile.useGeocentroid", - "type": "CompoundType", - "tags": [], - "label": "useGeocentroid", + "label": "customMetric", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsGeoTile.precision", - "type": "number", + "id": "def-common.AggParamsFilteredMetric.customBucket", + "type": "Object", "tags": [], - "label": "precision", + "label": "customBucket", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false } @@ -20011,18 +20035,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsHistogram", + "id": "def-common.AggParamsFilteredMetricSerialized", "type": "Interface", "tags": [], - "label": "AggParamsHistogram", + "label": "AggParamsFilteredMetricSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsHistogram", - "text": "AggParamsHistogram" + "section": "def-common.AggParamsFilteredMetricSerialized", + "text": "AggParamsFilteredMetricSerialized" }, " extends ", { @@ -20033,137 +20057,39 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.field", - "type": "string", - "tags": [], - "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.interval", - "type": "CompoundType", - "tags": [], - "label": "interval", - "description": [], - "signature": [ - "string | number" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.used_interval", - "type": "CompoundType", - "tags": [], - "label": "used_interval", - "description": [], - "signature": [ - "string | number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.maxBars", - "type": "number", - "tags": [], - "label": "maxBars", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.intervalBase", - "type": "number", - "tags": [], - "label": "intervalBase", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.min_doc_count", - "type": "CompoundType", - "tags": [], - "label": "min_doc_count", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.has_extended_bounds", - "type": "CompoundType", - "tags": [], - "label": "has_extended_bounds", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.extended_bounds", + "id": "def-common.AggParamsFilteredMetricSerialized.customMetric", "type": "Object", "tags": [], - "label": "extended_bounds", + "label": "customMetric", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExtendedBounds", - "text": "ExtendedBounds" - }, - " | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsHistogram.autoExtendBounds", - "type": "CompoundType", + "id": "def-common.AggParamsFilteredMetricSerialized.customBucket", + "type": "Object", "tags": [], - "label": "autoExtendBounds", + "label": "customBucket", "description": [], "signature": [ - "boolean | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", "deprecated": false, "trackAdoption": false } @@ -20172,91 +20098,51 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsIpRange", + "id": "def-common.AggParamsFilters", "type": "Interface", "tags": [], - "label": "AggParamsIpRange", + "label": "AggParamsFilters", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsIpRange", - "text": "AggParamsIpRange" + "section": "def-common.AggParamsFilters", + "text": "AggParamsFilters" }, - " extends ", + " extends Omit<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", "section": "def-common.BaseAggParams", "text": "BaseAggParams" - } + }, + ", \"customLabel\">" ], - "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filters.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsIpRange.field", - "type": "string", - "tags": [], - "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsIpRange.ipRangeType", - "type": "CompoundType", - "tags": [], - "label": "ipRangeType", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IP_RANGE_TYPES", - "text": "IP_RANGE_TYPES" - }, - " | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsIpRange.ranges", - "type": "Object", + "id": "def-common.AggParamsFilters.filters", + "type": "Array", "tags": [], - "label": "ranges", + "label": "filters", "description": [], "signature": [ - "Partial<{ fromTo: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.RangeIpRangeAggKey", - "text": "RangeIpRangeAggKey" - }, - "[]; mask: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.CidrMaskIpRangeAggKey", - "text": "CidrMaskIpRangeAggKey" + "section": "def-common.QueryFilter", + "text": "QueryFilter" }, - "[]; }> | undefined" + "[] | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", + "path": "src/plugins/data/common/search/aggs/buckets/filters.ts", "deprecated": false, "trackAdoption": false } @@ -20265,18 +20151,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsMax", + "id": "def-common.AggParamsGeoBounds", "type": "Interface", "tags": [], - "label": "AggParamsMax", + "label": "AggParamsGeoBounds", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsMax", - "text": "AggParamsMax" + "section": "def-common.AggParamsGeoBounds", + "text": "AggParamsGeoBounds" }, " extends ", { @@ -20287,18 +20173,18 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/max.ts", + "path": "src/plugins/data/common/search/aggs/metrics/geo_bounds.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMax.field", + "id": "def-common.AggParamsGeoBounds.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/max.ts", + "path": "src/plugins/data/common/search/aggs/metrics/geo_bounds.ts", "deprecated": false, "trackAdoption": false } @@ -20307,18 +20193,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsMedian", + "id": "def-common.AggParamsGeoCentroid", "type": "Interface", "tags": [], - "label": "AggParamsMedian", + "label": "AggParamsGeoCentroid", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsMedian", - "text": "AggParamsMedian" + "section": "def-common.AggParamsGeoCentroid", + "text": "AggParamsGeoCentroid" }, " extends ", { @@ -20329,18 +20215,18 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/median.ts", + "path": "src/plugins/data/common/search/aggs/metrics/geo_centroid.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMedian.field", + "id": "def-common.AggParamsGeoCentroid.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/median.ts", + "path": "src/plugins/data/common/search/aggs/metrics/geo_centroid.ts", "deprecated": false, "trackAdoption": false } @@ -20349,18 +20235,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsMin", + "id": "def-common.AggParamsGeoHash", "type": "Interface", "tags": [], - "label": "AggParamsMin", + "label": "AggParamsGeoHash", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsMin", - "text": "AggParamsMin" + "section": "def-common.AggParamsGeoHash", + "text": "AggParamsGeoHash" }, " extends ", { @@ -20371,121 +20257,95 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/min.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMin.field", + "id": "def-common.AggParamsGeoHash.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/min.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg", - "type": "Interface", - "tags": [], - "label": "AggParamsMovingAvg", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsMovingAvg", - "text": "AggParamsMovingAvg" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg.buckets_path", - "type": "string", + "id": "def-common.AggParamsGeoHash.autoPrecision", + "type": "CompoundType", "tags": [], - "label": "buckets_path", + "label": "autoPrecision", "description": [], "signature": [ - "string | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg.window", + "id": "def-common.AggParamsGeoHash.precision", "type": "number", "tags": [], - "label": "window", + "label": "precision", "description": [], "signature": [ "number | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg.script", - "type": "string", + "id": "def-common.AggParamsGeoHash.useGeocentroid", + "type": "CompoundType", "tags": [], - "label": "script", + "label": "useGeocentroid", "description": [], "signature": [ - "string | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg.customMetric", - "type": "Object", + "id": "def-common.AggParamsGeoHash.isFilteredByCollar", + "type": "CompoundType", "tags": [], - "label": "customMetric", + "label": "isFilteredByCollar", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMovingAvg.metricAgg", - "type": "string", + "id": "def-common.AggParamsGeoHash.boundingBox", + "type": "CompoundType", "tags": [], - "label": "metricAgg", + "label": "boundingBox", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.GeoBoundingBox", + "text": "GeoBoundingBox" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_hash.ts", "deprecated": false, "trackAdoption": false } @@ -20494,18 +20354,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms", + "id": "def-common.AggParamsGeoTile", "type": "Interface", "tags": [], - "label": "AggParamsMultiTerms", + "label": "AggParamsGeoTile", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsMultiTerms", - "text": "AggParamsMultiTerms" + "section": "def-common.AggParamsGeoTile", + "text": "AggParamsGeoTile" }, " extends ", { @@ -20516,132 +20376,207 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.fields", - "type": "Array", + "id": "def-common.AggParamsGeoTile.field", + "type": "string", "tags": [], - "label": "fields", + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsGeoTile.useGeocentroid", + "type": "CompoundType", + "tags": [], + "label": "useGeocentroid", "description": [], "signature": [ - "string[]" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsGeoTile.precision", + "type": "number", + "tags": [], + "label": "precision", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/geo_tile.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsHistogram", + "type": "Interface", + "tags": [], + "label": "AggParamsHistogram", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsHistogram", + "text": "AggParamsHistogram" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.orderBy", + "id": "def-common.AggParamsHistogram.field", "type": "string", "tags": [], - "label": "orderBy", + "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.orderAgg", - "type": "Object", + "id": "def-common.AggParamsHistogram.interval", + "type": "CompoundType", "tags": [], - "label": "orderAgg", + "label": "interval", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + "string | number" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.order", + "id": "def-common.AggParamsHistogram.used_interval", "type": "CompoundType", "tags": [], - "label": "order", + "label": "used_interval", "description": [], "signature": [ - "\"asc\" | \"desc\" | undefined" + "string | number | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.size", + "id": "def-common.AggParamsHistogram.maxBars", "type": "number", "tags": [], - "label": "size", + "label": "maxBars", "description": [], "signature": [ "number | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.shardSize", + "id": "def-common.AggParamsHistogram.intervalBase", "type": "number", "tags": [], - "label": "shardSize", + "label": "intervalBase", "description": [], "signature": [ "number | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.otherBucket", + "id": "def-common.AggParamsHistogram.min_doc_count", "type": "CompoundType", "tags": [], - "label": "otherBucket", + "label": "min_doc_count", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.otherBucketLabel", - "type": "string", + "id": "def-common.AggParamsHistogram.has_extended_bounds", + "type": "CompoundType", "tags": [], - "label": "otherBucketLabel", + "label": "has_extended_bounds", "description": [], "signature": [ - "string | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsMultiTerms.separatorLabel", - "type": "string", + "id": "def-common.AggParamsHistogram.extended_bounds", + "type": "Object", "tags": [], - "label": "separatorLabel", + "label": "extended_bounds", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExtendedBounds", + "text": "ExtendedBounds" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsHistogram.autoExtendBounds", + "type": "CompoundType", + "tags": [], + "label": "autoExtendBounds", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", "deprecated": false, "trackAdoption": false } @@ -20650,18 +20585,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsPercentileRanks", + "id": "def-common.AggParamsIpRange", "type": "Interface", "tags": [], - "label": "AggParamsPercentileRanks", + "label": "AggParamsIpRange", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsPercentileRanks", - "text": "AggParamsPercentileRanks" + "section": "def-common.AggParamsIpRange", + "text": "AggParamsIpRange" }, " extends ", { @@ -20672,32 +20607,69 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", + "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsPercentileRanks.field", + "id": "def-common.AggParamsIpRange.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", + "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsPercentileRanks.values", - "type": "Array", + "id": "def-common.AggParamsIpRange.ipRangeType", + "type": "CompoundType", "tags": [], - "label": "values", + "label": "ipRangeType", "description": [], "signature": [ - "number[] | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.IP_RANGE_TYPES", + "text": "IP_RANGE_TYPES" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", + "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsIpRange.ranges", + "type": "Object", + "tags": [], + "label": "ranges", + "description": [], + "signature": [ + "Partial<{ fromTo: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.RangeIpRangeAggKey", + "text": "RangeIpRangeAggKey" + }, + "[]; mask: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CidrMaskIpRangeAggKey", + "text": "CidrMaskIpRangeAggKey" + }, + "[]; }> | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/ip_range.ts", "deprecated": false, "trackAdoption": false } @@ -20706,932 +20678,852 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsPercentiles", + "id": "def-common.AggParamsMapping", "type": "Interface", "tags": [], - "label": "AggParamsPercentiles", + "label": "AggParamsMapping", "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsPercentiles", - "text": "AggParamsPercentiles" - }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsPercentiles.field", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.RANGE", + "type": "Object", "tags": [], - "label": "field", + "label": "[BUCKET_TYPES.RANGE]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsRange", + "text": "AggParamsRange" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsPercentiles.percents", - "type": "Array", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.IP_RANGE", + "type": "Object", "tags": [], - "label": "percents", + "label": "[BUCKET_TYPES.IP_RANGE]", "description": [], "signature": [ - "number[] | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsIpRange", + "text": "AggParamsIpRange" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsRange", - "type": "Interface", - "tags": [], - "label": "AggParamsRange", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsRange", - "text": "AggParamsRange" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/range.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsRange.field", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.DATE_RANGE", + "type": "Object", "tags": [], - "label": "field", + "label": "[BUCKET_TYPES.DATE_RANGE]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/range.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsDateRange", + "text": "AggParamsDateRange" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsRange.ranges", - "type": "Array", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.FILTER", + "type": "Object", "tags": [], - "label": "ranges", + "label": "[BUCKET_TYPES.FILTER]", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.NumericalRange", - "text": "NumericalRange" - }, - "[] | undefined" + "section": "def-common.AggParamsFilter", + "text": "AggParamsFilter" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/range.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsRareTerms", - "type": "Interface", - "tags": [], - "label": "AggParamsRareTerms", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsRareTerms", - "text": "AggParamsRareTerms" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsRareTerms.field", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.FILTERS", + "type": "Object", "tags": [], - "label": "field", + "label": "[BUCKET_TYPES.FILTERS]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsFilters", + "text": "AggParamsFilters" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsRareTerms.max_doc_count", - "type": "number", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.SIGNIFICANT_TERMS", + "type": "Object", "tags": [], - "label": "max_doc_count", + "label": "[BUCKET_TYPES.SIGNIFICANT_TERMS]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSignificantTerms", + "text": "AggParamsSignificantTerms" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSampler", - "type": "Interface", - "tags": [], - "label": "AggParamsSampler", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSampler", - "text": "AggParamsSampler" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/sampler.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSampler.shard_size", - "type": "number", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.SIGNIFICANT_TEXT", + "type": "Object", "tags": [], - "label": "shard_size", - "description": [ - "\nLimits how many top-scoring documents are collected in the sample processed on each shard." - ], + "label": "[BUCKET_TYPES.SIGNIFICANT_TEXT]", + "description": [], "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/buckets/sampler.ts", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSignificantText", + "text": "AggParamsSignificantText" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSerialDiff", - "type": "Interface", - "tags": [], - "label": "AggParamsSerialDiff", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSerialDiff", - "text": "AggParamsSerialDiff" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSerialDiff.buckets_path", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.GEOTILE_GRID", + "type": "Object", "tags": [], - "label": "buckets_path", + "label": "[BUCKET_TYPES.GEOTILE_GRID]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsGeoTile", + "text": "AggParamsGeoTile" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSerialDiff.customMetric", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.GEOHASH_GRID", "type": "Object", "tags": [], - "label": "customMetric", + "label": "[BUCKET_TYPES.GEOHASH_GRID]", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsGeoHash", + "text": "AggParamsGeoHash" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSerialDiff.metricAgg", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.HISTOGRAM", + "type": "Object", "tags": [], - "label": "metricAgg", + "label": "[BUCKET_TYPES.HISTOGRAM]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsHistogram", + "text": "AggParamsHistogram" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSignificantTerms", - "type": "Interface", - "tags": [], - "label": "AggParamsSignificantTerms", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSignificantTerms", - "text": "AggParamsSignificantTerms" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantTerms.field", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.DATE_HISTOGRAM", + "type": "Object", "tags": [], - "label": "field", + "label": "[BUCKET_TYPES.DATE_HISTOGRAM]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsDateHistogram", + "text": "AggParamsDateHistogram" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantTerms.size", - "type": "number", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.TERMS", + "type": "Object", "tags": [], - "label": "size", + "label": "[BUCKET_TYPES.TERMS]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTerms", + "text": "AggParamsTerms" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantTerms.exclude", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.MULTI_TERMS", + "type": "Object", "tags": [], - "label": "exclude", + "label": "[BUCKET_TYPES.MULTI_TERMS]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMultiTerms", + "text": "AggParamsMultiTerms" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantTerms.include", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.RARE_TERMS", + "type": "Object", "tags": [], - "label": "include", + "label": "[BUCKET_TYPES.RARE_TERMS]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsRareTerms", + "text": "AggParamsRareTerms" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText", - "type": "Interface", - "tags": [], - "label": "AggParamsSignificantText", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSignificantText", - "text": "AggParamsSignificantText" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.field", - "type": "string", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.SAMPLER", + "type": "Object", "tags": [], - "label": "field", + "label": "[BUCKET_TYPES.SAMPLER]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSampler", + "text": "AggParamsSampler" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.size", - "type": "number", + "id": "def-common.AggParamsMapping.BUCKET_TYPES.DIVERSIFIED_SAMPLER", + "type": "Object", "tags": [], - "label": "size", + "label": "[BUCKET_TYPES.DIVERSIFIED_SAMPLER]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsDiversifiedSampler", + "text": "AggParamsDiversifiedSampler" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.min_doc_count", - "type": "number", + "id": "def-common.AggParamsMapping.METRIC_TYPES.AVG", + "type": "Object", "tags": [], - "label": "min_doc_count", + "label": "[METRIC_TYPES.AVG]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsAvg", + "text": "AggParamsAvg" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.filter_duplicate_text", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.CARDINALITY", + "type": "Object", "tags": [], - "label": "filter_duplicate_text", + "label": "[METRIC_TYPES.CARDINALITY]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsCardinality", + "text": "AggParamsCardinality" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.exclude", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.COUNT", + "type": "Object", "tags": [], - "label": "exclude", + "label": "[METRIC_TYPES.COUNT]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsCount", + "text": "AggParamsCount" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSignificantText.include", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.VALUE_COUNT", + "type": "Object", "tags": [], - "label": "include", + "label": "[METRIC_TYPES.VALUE_COUNT]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsValueCount", + "text": "AggParamsValueCount" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentile", - "type": "Interface", - "tags": [], - "label": "AggParamsSinglePercentile", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSinglePercentile", - "text": "AggParamsSinglePercentile" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentile.field", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.GEO_BOUNDS", + "type": "Object", "tags": [], - "label": "field", + "label": "[METRIC_TYPES.GEO_BOUNDS]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsGeoBounds", + "text": "AggParamsGeoBounds" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentile.percentile", - "type": "number", + "id": "def-common.AggParamsMapping.METRIC_TYPES.GEO_CENTROID", + "type": "Object", "tags": [], - "label": "percentile", + "label": "[METRIC_TYPES.GEO_CENTROID]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsGeoCentroid", + "text": "AggParamsGeoCentroid" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentileRank", - "type": "Interface", - "tags": [], - "label": "AggParamsSinglePercentileRank", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSinglePercentileRank", - "text": "AggParamsSinglePercentileRank" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentileRank.field", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MAX", + "type": "Object", "tags": [], - "label": "field", + "label": "[METRIC_TYPES.MAX]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMax", + "text": "AggParamsMax" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSinglePercentileRank.value", - "type": "number", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MEDIAN", + "type": "Object", "tags": [], - "label": "value", + "label": "[METRIC_TYPES.MEDIAN]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMedian", + "text": "AggParamsMedian" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsStdDeviation", - "type": "Interface", - "tags": [], - "label": "AggParamsStdDeviation", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsStdDeviation", - "text": "AggParamsStdDeviation" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsStdDeviation.field", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.SINGLE_PERCENTILE", + "type": "Object", "tags": [], - "label": "field", + "label": "[METRIC_TYPES.SINGLE_PERCENTILE]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSinglePercentile", + "text": "AggParamsSinglePercentile" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsStdDeviation.showBounds", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.SINGLE_PERCENTILE_RANK", + "type": "Object", "tags": [], - "label": "showBounds", + "label": "[METRIC_TYPES.SINGLE_PERCENTILE_RANK]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSinglePercentileRank", + "text": "AggParamsSinglePercentileRank" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsSum", - "type": "Interface", - "tags": [], - "label": "AggParamsSum", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsSum", - "text": "AggParamsSum" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsSum.field", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MIN", + "type": "Object", "tags": [], - "label": "field", + "label": "[METRIC_TYPES.MIN]", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMin", + "text": "AggParamsMin" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsSum.emptyAsNull", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.STD_DEV", + "type": "Object", "tags": [], - "label": "emptyAsNull", + "label": "[METRIC_TYPES.STD_DEV]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsStdDeviation", + "text": "AggParamsStdDeviation" + } ], - "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsTerms", - "type": "Interface", - "tags": [], - "label": "AggParamsTerms", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsTerms", - "text": "AggParamsTerms" }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" - } - ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.field", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.SUM", + "type": "Object", "tags": [], - "label": "field", + "label": "[METRIC_TYPES.SUM]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSum", + "text": "AggParamsSum" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.orderBy", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.AVG_BUCKET", + "type": "Object", "tags": [], - "label": "orderBy", + "label": "[METRIC_TYPES.AVG_BUCKET]", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsBucketAvg", + "text": "AggParamsBucketAvg" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.orderAgg", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MAX_BUCKET", "type": "Object", "tags": [], - "label": "orderAgg", + "label": "[METRIC_TYPES.MAX_BUCKET]", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; } | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsBucketMax", + "text": "AggParamsBucketMax" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.order", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MIN_BUCKET", + "type": "Object", "tags": [], - "label": "order", + "label": "[METRIC_TYPES.MIN_BUCKET]", "description": [], "signature": [ - "\"asc\" | \"desc\" | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsBucketMin", + "text": "AggParamsBucketMin" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.size", - "type": "number", + "id": "def-common.AggParamsMapping.METRIC_TYPES.SUM_BUCKET", + "type": "Object", "tags": [], - "label": "size", + "label": "[METRIC_TYPES.SUM_BUCKET]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsBucketSum", + "text": "AggParamsBucketSum" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.shardSize", - "type": "number", + "id": "def-common.AggParamsMapping.METRIC_TYPES.FILTERED_METRIC", + "type": "Object", "tags": [], - "label": "shardSize", + "label": "[METRIC_TYPES.FILTERED_METRIC]", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsFilteredMetric", + "text": "AggParamsFilteredMetric" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.missingBucket", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.CUMULATIVE_SUM", + "type": "Object", "tags": [], - "label": "missingBucket", + "label": "[METRIC_TYPES.CUMULATIVE_SUM]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsCumulativeSum", + "text": "AggParamsCumulativeSum" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.missingBucketLabel", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.DERIVATIVE", + "type": "Object", "tags": [], - "label": "missingBucketLabel", + "label": "[METRIC_TYPES.DERIVATIVE]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsDerivative", + "text": "AggParamsDerivative" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.otherBucket", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.MOVING_FN", + "type": "Object", "tags": [], - "label": "otherBucket", + "label": "[METRIC_TYPES.MOVING_FN]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMovingAvg", + "text": "AggParamsMovingAvg" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.otherBucketLabel", - "type": "string", + "id": "def-common.AggParamsMapping.METRIC_TYPES.PERCENTILE_RANKS", + "type": "Object", "tags": [], - "label": "otherBucketLabel", + "label": "[METRIC_TYPES.PERCENTILE_RANKS]", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsPercentileRanks", + "text": "AggParamsPercentileRanks" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.exclude", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.PERCENTILES", + "type": "Object", "tags": [], - "label": "exclude", + "label": "[METRIC_TYPES.PERCENTILES]", "description": [], "signature": [ - "string[] | number[] | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsPercentiles", + "text": "AggParamsPercentiles" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.include", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.SERIAL_DIFF", + "type": "Object", "tags": [], - "label": "include", + "label": "[METRIC_TYPES.SERIAL_DIFF]", "description": [], "signature": [ - "string[] | number[] | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSerialDiff", + "text": "AggParamsSerialDiff" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.includeIsRegex", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.TOP_HITS", + "type": "Object", "tags": [], - "label": "includeIsRegex", + "label": "[METRIC_TYPES.TOP_HITS]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopHit", + "text": "AggParamsTopHit" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggParamsTerms.excludeIsRegex", - "type": "CompoundType", + "id": "def-common.AggParamsMapping.METRIC_TYPES.TOP_METRICS", + "type": "Object", "tags": [], - "label": "excludeIsRegex", + "label": "[METRIC_TYPES.TOP_METRICS]", "description": [], "signature": [ - "boolean | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopMetrics", + "text": "AggParamsTopMetrics" + } ], - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false } @@ -21640,18 +21532,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsTopHit", + "id": "def-common.AggParamsMax", "type": "Interface", "tags": [], - "label": "AggParamsTopHit", + "label": "AggParamsMax", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsTopHit", - "text": "AggParamsTopHit" + "section": "def-common.AggParamsMax", + "text": "AggParamsMax" }, " extends ", { @@ -21662,74 +21554,18 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "path": "src/plugins/data/common/search/aggs/metrics/max.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsTopHit.field", + "id": "def-common.AggParamsMax.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsTopHit.aggregate", - "type": "CompoundType", - "tags": [], - "label": "aggregate", - "description": [], - "signature": [ - "\"min\" | \"max\" | \"sum\" | \"average\" | \"concat\"" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsTopHit.sortField", - "type": "string", - "tags": [], - "label": "sortField", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsTopHit.size", - "type": "number", - "tags": [], - "label": "size", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsTopHit.sortOrder", - "type": "CompoundType", - "tags": [], - "label": "sortOrder", - "description": [], - "signature": [ - "\"asc\" | \"desc\" | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "path": "src/plugins/data/common/search/aggs/metrics/max.ts", "deprecated": false, "trackAdoption": false } @@ -21738,18 +21574,18 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsTopMetrics", + "id": "def-common.AggParamsMedian", "type": "Interface", "tags": [], - "label": "AggParamsTopMetrics", + "label": "AggParamsMedian", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsTopMetrics", - "text": "AggParamsTopMetrics" + "section": "def-common.AggParamsMedian", + "text": "AggParamsMedian" }, " extends ", { @@ -21760,60 +21596,112 @@ "text": "BaseAggParams" } ], - "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "path": "src/plugins/data/common/search/aggs/metrics/median.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsTopMetrics.field", + "id": "def-common.AggParamsMedian.field", "type": "string", "tags": [], "label": "field", "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "path": "src/plugins/data/common/search/aggs/metrics/median.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsMin", + "type": "Interface", + "tags": [], + "label": "AggParamsMin", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMin", + "text": "AggParamsMin" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/min.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsTopMetrics.sortField", + "id": "def-common.AggParamsMin.field", "type": "string", "tags": [], - "label": "sortField", + "label": "field", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "path": "src/plugins/data/common/search/aggs/metrics/min.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsMovingAvg", + "type": "Interface", + "tags": [], + "label": "AggParamsMovingAvg", + "description": [], + "signature": [ { - "parentPluginId": "data", - "id": "def-common.AggParamsTopMetrics.sortOrder", - "type": "CompoundType", - "tags": [], - "label": "sortOrder", - "description": [], - "signature": [ - "\"asc\" | \"desc\" | undefined" - ], - "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMovingAvg", + "text": "AggParamsMovingAvg" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsMovingAvg", + "text": "CommonAggParamsMovingAvg" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsTopMetrics.size", - "type": "number", + "id": "def-common.AggParamsMovingAvg.customMetric", + "type": "Object", "tags": [], - "label": "size", + "label": "customMetric", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false } @@ -21822,54 +21710,45 @@ }, { "parentPluginId": "data", - "id": "def-common.AggParamsValueCount", + "id": "def-common.AggParamsMovingAvgSerialized", "type": "Interface", "tags": [], - "label": "AggParamsValueCount", + "label": "AggParamsMovingAvgSerialized", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamsValueCount", - "text": "AggParamsValueCount" + "section": "def-common.AggParamsMovingAvgSerialized", + "text": "AggParamsMovingAvgSerialized" }, " extends ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseAggParams", - "text": "BaseAggParams" + "section": "def-common.CommonAggParamsMovingAvg", + "text": "CommonAggParamsMovingAvg" } ], - "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggParamsValueCount.field", - "type": "string", - "tags": [], - "label": "field", - "description": [], - "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggParamsValueCount.emptyAsNull", - "type": "CompoundType", + "id": "def-common.AggParamsMovingAvgSerialized.customMetric", + "type": "Object", "tags": [], - "label": "emptyAsNull", + "label": "customMetric", "description": [], "signature": [ - "boolean | undefined" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false } @@ -21878,58 +21757,43 @@ }, { "parentPluginId": "data", - "id": "def-common.AggsCommonSetup", + "id": "def-common.AggParamsMultiTerms", "type": "Interface", "tags": [], - "label": "AggsCommonSetup", + "label": "AggParamsMultiTerms", "description": [], - "path": "src/plugins/data/common/search/aggs/types.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMultiTerms", + "text": "AggParamsMultiTerms" + }, + " extends CommonAggParamsMultiTerms" + ], + "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonSetup.types", + "id": "def-common.AggParamsMultiTerms.orderAgg", "type": "Object", "tags": [], - "label": "types", + "label": "orderAgg", "description": [], "signature": [ - "{ registerBucket: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" - }, - ">(name: N, type: T) => void; registerMetric: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfig", + "text": "AggConfig" }, - ">(name: N, type: T) => void; }" + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/types.ts", + "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", "deprecated": false, "trackAdoption": false } @@ -21938,337 +21802,2275 @@ }, { "parentPluginId": "data", - "id": "def-common.AggsCommonSetupDependencies", + "id": "def-common.AggParamsMultiTermsSerialized", "type": "Interface", "tags": [], - "label": "AggsCommonSetupDependencies", + "label": "AggParamsMultiTermsSerialized", "description": [], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMultiTermsSerialized", + "text": "AggParamsMultiTermsSerialized" + }, + " extends CommonAggParamsMultiTerms" + ], + "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonSetupDependencies.registerFunction", - "type": "Function", + "id": "def-common.AggParamsMultiTermsSerialized.orderAgg", + "type": "Object", "tags": [], - "label": "registerFunction", + "label": "orderAgg", "description": [], "signature": [ - "(functionDefinition: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.AnyExpressionFunctionDefinition", - "text": "AnyExpressionFunctionDefinition" - }, - " | (() => ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.AnyExpressionFunctionDefinition", - "text": "AnyExpressionFunctionDefinition" - }, - ")) => void" + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "path": "src/plugins/data/common/search/aggs/buckets/multi_terms.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggsCommonSetupDependencies.registerFunction.$1", - "type": "CompoundType", - "tags": [], - "label": "functionDefinition", - "description": [], - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.AnyExpressionFunctionDefinition", - "text": "AnyExpressionFunctionDefinition" - }, - " | (() => ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.AnyExpressionFunctionDefinition", - "text": "AnyExpressionFunctionDefinition" - }, - ")" - ], - "path": "src/plugins/expressions/common/service/expressions_services.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false } ], "initialIsOpen": false }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStart", + "id": "def-common.AggParamsPercentileRanks", "type": "Interface", "tags": [], - "label": "AggsCommonStart", + "label": "AggParamsPercentileRanks", "description": [], - "path": "src/plugins/data/common/search/aggs/types.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsPercentileRanks", + "text": "AggParamsPercentileRanks" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonStart.calculateAutoTimeExpression", - "type": "Function", + "id": "def-common.AggParamsPercentileRanks.field", + "type": "string", "tags": [], - "label": "calculateAutoTimeExpression", + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsPercentileRanks.values", + "type": "Array", + "tags": [], + "label": "values", "description": [], "signature": [ - "(range: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" - }, - ") => string | undefined" + "number[] | undefined" ], - "path": "src/plugins/data/common/search/aggs/types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggsCommonStart.calculateAutoTimeExpression.$1", - "type": "Object", - "tags": [], - "label": "range", - "description": [], - "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" - ], - "path": "src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsPercentiles", + "type": "Interface", + "tags": [], + "label": "AggParamsPercentiles", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsPercentiles", + "text": "AggParamsPercentiles" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonStart.createAggConfigs", - "type": "Function", + "id": "def-common.AggParamsPercentiles.field", + "type": "string", "tags": [], - "label": "createAggConfigs", + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsPercentiles.percents", + "type": "Array", + "tags": [], + "label": "percents", "description": [], "signature": [ - "(indexPattern: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - ", configStates?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" - }, - "[] | undefined, options?: Partial<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" - }, - "> | undefined) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigs", - "text": "AggConfigs" - } + "number[] | undefined" ], - "path": "src/plugins/data/common/search/aggs/types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/percentiles.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggsCommonStart.createAggConfigs.$1", - "type": "Object", - "tags": [], - "label": "indexPattern", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - } - ], - "path": "src/plugins/data/common/search/aggs/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "data", - "id": "def-common.AggsCommonStart.createAggConfigs.$2", - "type": "Array", - "tags": [], - "label": "configStates", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" - }, - "[] | undefined" - ], - "path": "src/plugins/data/common/search/aggs/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggsCommonStart.createAggConfigs.$3", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "Partial<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" - }, - "> | undefined" - ], - "path": "src/plugins/data/common/search/aggs/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsRange", + "type": "Interface", + "tags": [], + "label": "AggParamsRange", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsRange", + "text": "AggParamsRange" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/range.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsRange.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/range.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStart.types", - "type": "Object", + "id": "def-common.AggParamsRange.ranges", + "type": "Array", "tags": [], - "label": "types", + "label": "ranges", "description": [], "signature": [ - "{ get: (name: string) => ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "section": "def-common.NumericalRange", + "text": "NumericalRange" }, - " | ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" - }, - "; getAll: () => { buckets: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" - }, - "[]; metrics: ", + "[] | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/range.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsRareTerms", + "type": "Interface", + "tags": [], + "label": "AggParamsRareTerms", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsRareTerms", + "text": "AggParamsRareTerms" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsRareTerms.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsRareTerms.max_doc_count", + "type": "number", + "tags": [], + "label": "max_doc_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/rare_terms.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSampler", + "type": "Interface", + "tags": [], + "label": "AggParamsSampler", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSampler", + "text": "AggParamsSampler" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/sampler.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSampler.shard_size", + "type": "number", + "tags": [], + "label": "shard_size", + "description": [ + "\nLimits how many top-scoring documents are collected in the sample processed on each shard." + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/sampler.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSerialDiff", + "type": "Interface", + "tags": [], + "label": "AggParamsSerialDiff", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSerialDiff", + "text": "AggParamsSerialDiff" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsSerialDiff", + "text": "CommonAggParamsSerialDiff" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSerialDiff.customMetric", + "type": "Object", + "tags": [], + "label": "customMetric", + "description": [], + "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfig", + "text": "AggConfig" }, - "[]; }; }" + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSerialDiffSerialized", + "type": "Interface", + "tags": [], + "label": "AggParamsSerialDiffSerialized", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSerialDiffSerialized", + "text": "AggParamsSerialDiffSerialized" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsSerialDiff", + "text": "CommonAggParamsSerialDiff" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSerialDiffSerialized.customMetric", + "type": "Object", + "tags": [], + "label": "customMetric", + "description": [], + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantTerms", + "type": "Interface", + "tags": [], + "label": "AggParamsSignificantTerms", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSignificantTerms", + "text": "AggParamsSignificantTerms" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantTerms.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantTerms.size", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantTerms.exclude", + "type": "string", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantTerms.include", + "type": "string", + "tags": [], + "label": "include", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_terms.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText", + "type": "Interface", + "tags": [], + "label": "AggParamsSignificantText", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSignificantText", + "text": "AggParamsSignificantText" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.size", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.min_doc_count", + "type": "number", + "tags": [], + "label": "min_doc_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.filter_duplicate_text", + "type": "CompoundType", + "tags": [], + "label": "filter_duplicate_text", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.exclude", + "type": "string", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSignificantText.include", + "type": "string", + "tags": [], + "label": "include", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/significant_text.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentile", + "type": "Interface", + "tags": [], + "label": "AggParamsSinglePercentile", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSinglePercentile", + "text": "AggParamsSinglePercentile" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentile.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentile.percentile", + "type": "number", + "tags": [], + "label": "percentile", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentileRank", + "type": "Interface", + "tags": [], + "label": "AggParamsSinglePercentileRank", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSinglePercentileRank", + "text": "AggParamsSinglePercentileRank" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentileRank.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSinglePercentileRank.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/single_percentile_rank.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsStdDeviation", + "type": "Interface", + "tags": [], + "label": "AggParamsStdDeviation", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsStdDeviation", + "text": "AggParamsStdDeviation" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsStdDeviation.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsStdDeviation.showBounds", + "type": "CompoundType", + "tags": [], + "label": "showBounds", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/std_deviation.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSum", + "type": "Interface", + "tags": [], + "label": "AggParamsSum", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsSum", + "text": "AggParamsSum" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsSum.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsSum.emptyAsNull", + "type": "CompoundType", + "tags": [], + "label": "emptyAsNull", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/sum.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTerms", + "type": "Interface", + "tags": [], + "label": "AggParamsTerms", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTerms", + "text": "AggParamsTerms" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsTerms", + "text": "CommonAggParamsTerms" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTerms.orderAgg", + "type": "Object", + "tags": [], + "label": "orderAgg", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTerms.order", + "type": "Object", + "tags": [], + "label": "order", + "description": [], + "signature": [ + "{ value: \"asc\" | \"desc\"; text: string; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTermsSerialized", + "type": "Interface", + "tags": [], + "label": "AggParamsTermsSerialized", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTermsSerialized", + "text": "AggParamsTermsSerialized" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsTerms", + "text": "CommonAggParamsTerms" + } + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTermsSerialized.orderAgg", + "type": "Object", + "tags": [], + "label": "orderAgg", + "description": [], + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTermsSerialized.order", + "type": "CompoundType", + "tags": [], + "label": "order", + "description": [], + "signature": [ + "\"asc\" | \"desc\" | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHit", + "type": "Interface", + "tags": [], + "label": "AggParamsTopHit", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopHit", + "text": "AggParamsTopHit" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopHit", + "text": "BaseAggParamsTopHit" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHit.sortOrder", + "type": "Object", + "tags": [], + "label": "sortOrder", + "description": [], + "signature": [ + "{ value: \"asc\" | \"desc\"; text: string; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHit.sortField", + "type": "Object", + "tags": [], + "label": "sortField", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHitSerialized", + "type": "Interface", + "tags": [], + "label": "AggParamsTopHitSerialized", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopHitSerialized", + "text": "AggParamsTopHitSerialized" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopHit", + "text": "BaseAggParamsTopHit" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHitSerialized.sortOrder", + "type": "CompoundType", + "tags": [], + "label": "sortOrder", + "description": [], + "signature": [ + "\"asc\" | \"desc\" | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopHitSerialized.sortField", + "type": "string", + "tags": [], + "label": "sortField", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetrics", + "type": "Interface", + "tags": [], + "label": "AggParamsTopMetrics", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopMetrics", + "text": "AggParamsTopMetrics" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopMetrics", + "text": "BaseAggParamsTopMetrics" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetrics.sortOrder", + "type": "Object", + "tags": [], + "label": "sortOrder", + "description": [], + "signature": [ + "{ value: \"asc\" | \"desc\"; text: string; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetrics.sortField", + "type": "Object", + "tags": [], + "label": "sortField", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetricsSerialized", + "type": "Interface", + "tags": [], + "label": "AggParamsTopMetricsSerialized", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsTopMetricsSerialized", + "text": "AggParamsTopMetricsSerialized" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopMetrics", + "text": "BaseAggParamsTopMetrics" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetricsSerialized.sortOrder", + "type": "CompoundType", + "tags": [], + "label": "sortOrder", + "description": [], + "signature": [ + "\"asc\" | \"desc\" | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsTopMetricsSerialized.sortField", + "type": "string", + "tags": [], + "label": "sortField", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsValueCount", + "type": "Interface", + "tags": [], + "label": "AggParamsValueCount", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsValueCount", + "text": "AggParamsValueCount" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggParamsValueCount.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggParamsValueCount.emptyAsNull", + "type": "CompoundType", + "tags": [], + "label": "emptyAsNull", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/value_count.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonSetup", + "type": "Interface", + "tags": [], + "label": "AggsCommonSetup", + "description": [], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonSetup.types", + "type": "Object", + "tags": [], + "label": "types", + "description": [], + "signature": [ + "{ registerBucket: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" + }, + ">(name: N, type: T) => void; registerMetric: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" + }, + ">(name: N, type: T) => void; }" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonSetupDependencies", + "type": "Interface", + "tags": [], + "label": "AggsCommonSetupDependencies", + "description": [], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonSetupDependencies.registerFunction", + "type": "Function", + "tags": [], + "label": "registerFunction", + "description": [], + "signature": [ + "(functionDefinition: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.AnyExpressionFunctionDefinition", + "text": "AnyExpressionFunctionDefinition" + }, + " | (() => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.AnyExpressionFunctionDefinition", + "text": "AnyExpressionFunctionDefinition" + }, + ")) => void" + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonSetupDependencies.registerFunction.$1", + "type": "CompoundType", + "tags": [], + "label": "functionDefinition", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.AnyExpressionFunctionDefinition", + "text": "AnyExpressionFunctionDefinition" + }, + " | (() => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.AnyExpressionFunctionDefinition", + "text": "AnyExpressionFunctionDefinition" + }, + ")" + ], + "path": "src/plugins/expressions/common/service/expressions_services.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart", + "type": "Interface", + "tags": [], + "label": "AggsCommonStart", + "description": [], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.calculateAutoTimeExpression", + "type": "Function", + "tags": [], + "label": "calculateAutoTimeExpression", + "description": [], + "signature": [ + "(range: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + ") => string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.calculateAutoTimeExpression.$1", + "type": "Object", + "tags": [], + "label": "range", + "description": [], + "signature": [ + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.createAggConfigs", + "type": "Function", + "tags": [], + "label": "createAggConfigs", + "description": [], + "signature": [ + "(indexPattern: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ", configStates?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" + }, + "[] | undefined, options?: Partial<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" + }, + "> | undefined) => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfigs", + "text": "AggConfigs" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.createAggConfigs.$1", + "type": "Object", + "tags": [], + "label": "indexPattern", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.createAggConfigs.$2", + "type": "Array", + "tags": [], + "label": "configStates", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" + }, + "[] | undefined" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.createAggConfigs.$3", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" + }, + "> | undefined" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStart.types", + "type": "Object", + "tags": [], + "label": "types", + "description": [], + "signature": [ + "{ get: (name: string) => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" + }, + "; getAll: () => { buckets: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" + }, + "[]; metrics: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" + }, + "[]; }; }" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies", + "type": "Interface", + "tags": [], + "label": "AggsCommonStartDependencies", + "description": [], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.getIndexPattern", + "type": "Function", + "tags": [], + "label": "getIndexPattern", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ">" + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.getIndexPattern.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.getConfig", + "type": "Function", + "tags": [], + "label": "getConfig", + "description": [], + "signature": [ + "(key: string, defaultOverride?: T | undefined) => T" + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.getConfig.$1", + "type": "string", + "tags": [], + "label": "key", + "description": [], + "path": "src/plugins/data/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.getConfig.$2", + "type": "Uncategorized", + "tags": [], + "label": "defaultOverride", + "description": [], + "signature": [ + "T | undefined" + ], + "path": "src/plugins/data/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.fieldFormats", + "type": "Object", + "tags": [], + "label": "fieldFormats", + "description": [], + "signature": [ + "{ deserialize: ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FormatFactory", + "text": "FormatFactory" + }, + "; getDefaultConfig: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined) => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatConfig", + "text": "FieldFormatConfig" + }, + "; getType: (formatId: string) => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatInstanceType", + "text": "FieldFormatInstanceType" + }, + " | undefined; getTypeWithoutMetaParams: (formatId: string) => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatInstanceType", + "text": "FieldFormatInstanceType" + }, + " | undefined; getDefaultType: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined) => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatInstanceType", + "text": "FieldFormatInstanceType" + }, + " | undefined; getTypeNameByEsTypes: (esTypes: ", + "ES_FIELD_TYPES", + "[] | undefined) => ", + "ES_FIELD_TYPES", + " | undefined; getDefaultTypeName: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined) => ", + "ES_FIELD_TYPES", + " | ", + "KBN_FIELD_TYPES", + "; getInstance: (formatId: string, params?: ", + "SerializableRecord", + ") => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormat", + "text": "FieldFormat" + }, + "; getDefaultInstancePlain: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined, params?: ", + "SerializableRecord", + ") => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormat", + "text": "FieldFormat" + }, + "; getDefaultInstanceCacheResolver: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined) => string; getByFieldType: (fieldType: ", + "KBN_FIELD_TYPES", + ") => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatInstanceType", + "text": "FieldFormatInstanceType" + }, + "[]; getDefaultInstance: (fieldType: ", + "KBN_FIELD_TYPES", + ", esTypes?: ", + "ES_FIELD_TYPES", + "[] | undefined, params?: ", + "SerializableRecord", + ") => ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormat", + "text": "FieldFormat" + }, + "; parseDefaultTypeMap: (value: Record) => void; has: (id: string) => boolean; }" + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.calculateBounds", + "type": "Function", + "tags": [], + "label": "calculateBounds", + "description": [], + "signature": [ + "(timeRange: ", + "TimeRange", + ") => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRangeBounds", + "text": "TimeRangeBounds" + } + ], + "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggsCommonStartDependencies.calculateBounds.$1", + "type": "Object", + "tags": [], + "label": "timeRange", + "description": [], + "signature": [ + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig", + "type": "Interface", + "tags": [], + "label": "AggTypeConfig", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggTypeConfig", + "text": "AggTypeConfig" + }, + "" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.createFilter", + "type": "Function", + "tags": [], + "label": "createFilter", + "description": [], + "signature": [ + "((aggConfig: TAggConfig, key: any, params?: any) => any) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.createFilter.$1", + "type": "Uncategorized", + "tags": [], + "label": "aggConfig", + "description": [], + "signature": [ + "TAggConfig" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.createFilter.$2", + "type": "Any", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.createFilter.$3", + "type": "Any", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.dslName", + "type": "string", + "tags": [], + "label": "dslName", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.expressionName", + "type": "string", + "tags": [], + "label": "expressionName", + "description": [], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.makeLabel", + "type": "CompoundType", + "tags": [], + "label": "makeLabel", + "description": [], + "signature": [ + "((aggConfig: TAggConfig) => string) | (() => string) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.ordered", + "type": "Any", + "tags": [], + "label": "ordered", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.hasNoDsl", + "type": "CompoundType", + "tags": [], + "label": "hasNoDsl", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.hasNoDslParams", + "type": "CompoundType", + "tags": [], + "label": "hasNoDslParams", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.params", + "type": "Array", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "Partial[] | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.valueType", + "type": "CompoundType", + "tags": [], + "label": "valueType", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.DatatableColumnType", + "text": "DatatableColumnType" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getRequestAggs", + "type": "CompoundType", + "tags": [], + "label": "getRequestAggs", + "description": [], + "signature": [ + "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getResponseAggs", + "type": "CompoundType", + "tags": [], + "label": "getResponseAggs", + "description": [], + "signature": [ + "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.customLabels", + "type": "CompoundType", + "tags": [], + "label": "customLabels", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.json", + "type": "CompoundType", + "tags": [], + "label": "json", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.decorateAggConfig", + "type": "Function", + "tags": [], + "label": "decorateAggConfig", + "description": [], + "signature": [ + "(() => any) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.postFlightRequest", + "type": "Function", + "tags": [], + "label": "postFlightRequest", + "description": [], + "signature": [ + "PostFlightRequestFn | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.hasPrecisionError", + "type": "Function", + "tags": [], + "label": "hasPrecisionError", + "description": [], + "signature": [ + "((aggBucket: Record) => boolean) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.hasPrecisionError.$1", + "type": "Object", + "tags": [], + "label": "aggBucket", + "description": [], + "signature": [ + "Record" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } ], - "path": "src/plugins/data/common/search/aggs/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies", - "type": "Interface", - "tags": [], - "label": "AggsCommonStartDependencies", - "description": [], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "returnComment": [] + }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.getIndexPattern", + "id": "def-common.AggTypeConfig.getSerializedFormat", "type": "Function", "tags": [], - "label": "getIndexPattern", + "label": "getSerializedFormat", "description": [], "signature": [ - "(id: string) => Promise<", + "((agg: TAggConfig) => ", { - "pluginId": "dataViews", + "pluginId": "fieldFormats", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.SerializedFieldFormat", + "text": "SerializedFieldFormat" }, - ">" + "<{}, ", + "SerializableRecord", + ">) | undefined" ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.getIndexPattern.$1", - "type": "string", + "id": "def-common.AggTypeConfig.getSerializedFormat.$1", + "type": "Uncategorized", "tags": [], - "label": "id", + "label": "agg", "description": [], "signature": [ - "string" + "TAggConfig" ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -22278,321 +24080,256 @@ }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.getConfig", + "id": "def-common.AggTypeConfig.getValue", "type": "Function", "tags": [], - "label": "getConfig", + "label": "getValue", "description": [], "signature": [ - "(key: string, defaultOverride?: T | undefined) => T" + "((agg: TAggConfig, bucket: any) => any) | undefined" ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.getConfig.$1", - "type": "string", + "id": "def-common.AggTypeConfig.getValue.$1", + "type": "Uncategorized", "tags": [], - "label": "key", + "label": "agg", "description": [], - "path": "src/plugins/data/common/types.ts", + "signature": [ + "TAggConfig" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.getConfig.$2", - "type": "Uncategorized", + "id": "def-common.AggTypeConfig.getValue.$2", + "type": "Any", "tags": [], - "label": "defaultOverride", + "label": "bucket", "description": [], "signature": [ - "T | undefined" + "any" ], - "path": "src/plugins/data/common/types.ts", + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } - ] + ], + "returnComment": [] }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.fieldFormats", - "type": "Object", + "id": "def-common.AggTypeConfig.getKey", + "type": "Function", "tags": [], - "label": "fieldFormats", + "label": "getKey", "description": [], "signature": [ - "{ deserialize: ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FormatFactory", - "text": "FormatFactory" - }, - "; getDefaultConfig: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined) => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatConfig", - "text": "FieldFormatConfig" - }, - "; getType: (formatId: string) => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatInstanceType", - "text": "FieldFormatInstanceType" - }, - " | undefined; getTypeWithoutMetaParams: (formatId: string) => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatInstanceType", - "text": "FieldFormatInstanceType" - }, - " | undefined; getDefaultType: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined) => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatInstanceType", - "text": "FieldFormatInstanceType" - }, - " | undefined; getTypeNameByEsTypes: (esTypes: ", - "ES_FIELD_TYPES", - "[] | undefined) => ", - "ES_FIELD_TYPES", - " | undefined; getDefaultTypeName: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined) => ", - "ES_FIELD_TYPES", - " | ", - "KBN_FIELD_TYPES", - "; getInstance: (formatId: string, params?: ", - "SerializableRecord", - ") => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormat", - "text": "FieldFormat" - }, - "; getDefaultInstancePlain: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined, params?: ", - "SerializableRecord", - ") => ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormat", - "text": "FieldFormat" - }, - "; getDefaultInstanceCacheResolver: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined) => string; getByFieldType: (fieldType: ", - "KBN_FIELD_TYPES", - ") => ", + "((bucket: any, key: any, agg: TAggConfig) => any) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatInstanceType", - "text": "FieldFormatInstanceType" + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getKey.$1", + "type": "Any", + "tags": [], + "label": "bucket", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true }, - "[]; getDefaultInstance: (fieldType: ", - "KBN_FIELD_TYPES", - ", esTypes?: ", - "ES_FIELD_TYPES", - "[] | undefined, params?: ", - "SerializableRecord", - ") => ", { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormat", - "text": "FieldFormat" + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getKey.$2", + "type": "Any", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true }, - "; parseDefaultTypeMap: (value: Record) => void; has: (id: string) => boolean; }" + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getKey.$3", + "type": "Uncategorized", + "tags": [], + "label": "agg", + "description": [], + "signature": [ + "TAggConfig" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", - "deprecated": false, - "trackAdoption": false + "returnComment": [] }, { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.calculateBounds", + "id": "def-common.AggTypeConfig.getValueBucketPath", "type": "Function", "tags": [], - "label": "calculateBounds", + "label": "getValueBucketPath", "description": [], "signature": [ - "(timeRange: ", - "TimeRange", - ") => ", + "((agg: TAggConfig) => string) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRangeBounds", - "text": "TimeRangeBounds" + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getValueBucketPath.$1", + "type": "Uncategorized", + "tags": [], + "label": "agg", + "description": [], + "signature": [ + "TAggConfig" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], - "path": "src/plugins/data/common/search/aggs/aggs_service.ts", + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypeConfig.getResponseId", + "type": "Function", + "tags": [], + "label": "getResponseId", + "description": [], + "signature": [ + "((agg: TAggConfig) => string) | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "data", - "id": "def-common.AggsCommonStartDependencies.calculateBounds.$1", - "type": "Object", + "id": "def-common.AggTypeConfig.getResponseId.$1", + "type": "Uncategorized", "tags": [], - "label": "timeRange", + "label": "agg", "description": [], "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" + "TAggConfig" ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", + "path": "src/plugins/data/common/search/aggs/agg_type.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } - ] + ], + "returnComment": [] } ], "initialIsOpen": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig", + "id": "def-common.AggTypesDependencies", "type": "Interface", "tags": [], - "label": "AggTypeConfig", + "label": "AggTypesDependencies", "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggTypeConfig", - "text": "AggTypeConfig" - }, - "" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/agg_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.name", - "type": "string", - "tags": [], - "label": "name", - "description": [], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.title", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.createFilter", + "id": "def-common.AggTypesDependencies.calculateBounds", "type": "Function", "tags": [], - "label": "createFilter", + "label": "calculateBounds", "description": [], "signature": [ - "((aggConfig: TAggConfig, key: any, params?: any) => any) | undefined" + "(timeRange: ", + "TimeRange", + ") => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRangeBounds", + "text": "TimeRangeBounds" + } ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/agg_types.ts", "deprecated": false, "trackAdoption": false, + "returnComment": [], "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.createFilter.$1", - "type": "Uncategorized", + "id": "def-common.AggTypesDependencies.calculateBounds.$1", + "type": "Object", "tags": [], - "label": "aggConfig", + "label": "timeRange", "description": [], "signature": [ - "TAggConfig" + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypesDependencies.getConfig", + "type": "Function", + "tags": [], + "label": "getConfig", + "description": [], + "signature": [ + "(key: string) => T" + ], + "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.createFilter.$2", - "type": "Any", + "id": "def-common.AggTypesDependencies.getConfig.$1", + "type": "string", "tags": [], "label": "key", "description": [], "signature": [ - "any" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.createFilter.$3", - "type": "Any", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "any" + "string" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/agg_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -22602,588 +24339,625 @@ }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.type", + "id": "def-common.AggTypesDependencies.getFieldFormatsStart", + "type": "Function", + "tags": [], + "label": "getFieldFormatsStart", + "description": [], + "signature": [ + "() => Pick<", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatsStartCommon", + "text": "FieldFormatsStartCommon" + }, + ", \"deserialize\" | \"getDefaultInstance\">" + ], + "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "data", + "id": "def-common.AggTypesDependencies.aggExecutionContext", + "type": "Object", + "tags": [], + "label": "aggExecutionContext", + "description": [], + "signature": [ + "{ shouldDetectTimeZone?: boolean | undefined; } | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.AutoBounds", + "type": "Interface", + "tags": [], + "label": "AutoBounds", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.AutoBounds.min", + "type": "number", + "tags": [], + "label": "min", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AutoBounds.max", + "type": "number", + "tags": [], + "label": "max", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.BaseAggParams", + "type": "Interface", + "tags": [], + "label": "BaseAggParams", + "description": [], + "path": "src/plugins/data/common/search/aggs/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.BaseAggParams.json", "type": "string", "tags": [], - "label": "type", + "label": "json", "description": [], "signature": [ "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.dslName", + "id": "def-common.BaseAggParams.customLabel", "type": "string", "tags": [], - "label": "dslName", + "label": "customLabel", "description": [], "signature": [ "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.expressionName", + "id": "def-common.BaseAggParams.timeShift", "type": "string", "tags": [], - "label": "expressionName", + "label": "timeShift", "description": [], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.BaseAggParamsTopHit", + "type": "Interface", + "tags": [], + "label": "BaseAggParamsTopHit", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopHit", + "text": "BaseAggParamsTopHit" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.makeLabel", - "type": "CompoundType", + "id": "def-common.BaseAggParamsTopHit.field", + "type": "string", "tags": [], - "label": "makeLabel", + "label": "field", "description": [], - "signature": [ - "((aggConfig: TAggConfig) => string) | (() => string) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.ordered", - "type": "Any", + "id": "def-common.BaseAggParamsTopHit.aggregate", + "type": "CompoundType", "tags": [], - "label": "ordered", + "label": "aggregate", "description": [], "signature": [ - "any" + "\"min\" | \"max\" | \"sum\" | \"average\" | \"concat\"" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.hasNoDsl", - "type": "CompoundType", + "id": "def-common.BaseAggParamsTopHit.size", + "type": "number", "tags": [], - "label": "hasNoDsl", + "label": "size", "description": [], "signature": [ - "boolean | undefined" + "number | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/metrics/top_hit.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.BaseAggParamsTopMetrics", + "type": "Interface", + "tags": [], + "label": "BaseAggParamsTopMetrics", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParamsTopMetrics", + "text": "BaseAggParamsTopMetrics" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.hasNoDslParams", - "type": "CompoundType", + "id": "def-common.BaseAggParamsTopMetrics.field", + "type": "string", "tags": [], - "label": "hasNoDslParams", + "label": "field", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.params", - "type": "Array", + "id": "def-common.BaseAggParamsTopMetrics.size", + "type": "number", "tags": [], - "label": "params", + "label": "size", "description": [], "signature": [ - "Partial[] | undefined" + "number | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/metrics/top_metrics.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.BaseHit", + "type": "Interface", + "tags": [], + "label": "BaseHit", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseHit", + "text": "BaseHit" }, + "" + ], + "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.valueType", - "type": "CompoundType", + "id": "def-common.BaseHit._index", + "type": "string", "tags": [], - "label": "valueType", + "label": "_index", "description": [], - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.DatatableColumnType", - "text": "DatatableColumnType" - }, - " | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getRequestAggs", - "type": "CompoundType", + "id": "def-common.BaseHit._id", + "type": "string", "tags": [], - "label": "getRequestAggs", + "label": "_id", "description": [], - "signature": [ - "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getResponseAggs", - "type": "CompoundType", + "id": "def-common.BaseHit._source", + "type": "Uncategorized", "tags": [], - "label": "getResponseAggs", + "label": "_source", "description": [], "signature": [ - "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" + "T" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.customLabels", - "type": "CompoundType", + "id": "def-common.BaseHit.fields", + "type": "Object", "tags": [], - "label": "customLabels", + "label": "fields", "description": [], "signature": [ - "boolean | undefined" + "Record | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.BucketAggParam", + "type": "Interface", + "tags": [], + "label": "BucketAggParam", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggParam", + "text": "BucketAggParam" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamType", + "text": "AggParamType" }, + "" + ], + "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.json", + "id": "def-common.BucketAggParam.scriptable", "type": "CompoundType", "tags": [], - "label": "json", + "label": "scriptable", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.decorateAggConfig", - "type": "Function", + "id": "def-common.BucketAggParam.filterFieldTypes", + "type": "CompoundType", "tags": [], - "label": "decorateAggConfig", + "label": "filterFieldTypes", "description": [], "signature": [ - "(() => any) | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.FieldTypes", + "text": "FieldTypes" + }, + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.postFlightRequest", - "type": "Function", + "id": "def-common.BucketAggParam.onlyAggregatable", + "type": "CompoundType", "tags": [], - "label": "postFlightRequest", + "label": "onlyAggregatable", "description": [], "signature": [ - "PostFlightRequestFn | undefined" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.hasPrecisionError", + "id": "def-common.BucketAggParam.filterField", "type": "Function", "tags": [], - "label": "hasPrecisionError", - "description": [], - "signature": [ - "((aggBucket: Record) => boolean) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.hasPrecisionError.$1", - "type": "Object", - "tags": [], - "label": "aggBucket", - "description": [], - "signature": [ - "Record" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } + "label": "filterField", + "description": [ + "\nFilter available fields by passing filter fn on a {@link DataViewField}\nIf used, takes precedence over filterFieldTypes and other filter params" ], - "returnComment": [] - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getSerializedFormat", - "type": "Function", - "tags": [], - "label": "getSerializedFormat", - "description": [], "signature": [ - "((agg: TAggConfig) => ", { - "pluginId": "fieldFormats", + "pluginId": "data", "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.SerializedFieldFormat", - "text": "SerializedFieldFormat" + "docId": "kibDataSearchPluginApi", + "section": "def-common.FilterFieldFn", + "text": "FilterFieldFn" }, - "<{}, ", - "SerializableRecord", - ">) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getSerializedFormat.$1", - "type": "Uncategorized", - "tags": [], - "label": "agg", - "description": [], - "signature": [ - "TAggConfig" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getValue", - "type": "Function", - "tags": [], - "label": "getValue", - "description": [], - "signature": [ - "((agg: TAggConfig, bucket: any) => any) | undefined" + " | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getValue.$1", - "type": "Uncategorized", - "tags": [], - "label": "agg", - "description": [], - "signature": [ - "TAggConfig" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getValue.$2", - "type": "Any", - "tags": [], - "label": "bucket", - "description": [], - "signature": [ - "any" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.Cidr", + "type": "Interface", + "tags": [], + "label": "Cidr", + "description": [], + "path": "src/plugins/data/common/search/expressions/cidr.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getKey", - "type": "Function", + "id": "def-common.Cidr.mask", + "type": "string", "tags": [], - "label": "getKey", + "label": "mask", "description": [], - "signature": [ - "((bucket: any, key: any, agg: TAggConfig) => any) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/expressions/cidr.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getKey.$1", - "type": "Any", - "tags": [], - "label": "bucket", - "description": [], - "signature": [ - "any" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getKey.$2", - "type": "Any", - "tags": [], - "label": "key", - "description": [], - "signature": [ - "any" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getKey.$3", - "type": "Uncategorized", - "tags": [], - "label": "agg", - "description": [], - "signature": [ - "TAggConfig" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.CidrMaskIpRangeAggKey", + "type": "Interface", + "tags": [], + "label": "CidrMaskIpRangeAggKey", + "description": [], + "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getValueBucketPath", - "type": "Function", + "id": "def-common.CidrMaskIpRangeAggKey.type", + "type": "string", "tags": [], - "label": "getValueBucketPath", + "label": "type", "description": [], "signature": [ - "((agg: TAggConfig) => string) | undefined" + "\"mask\"" ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getValueBucketPath.$1", - "type": "Uncategorized", - "tags": [], - "label": "agg", - "description": [], - "signature": [ - "TAggConfig" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getResponseId", - "type": "Function", + "id": "def-common.CidrMaskIpRangeAggKey.mask", + "type": "string", "tags": [], - "label": "getResponseId", + "label": "mask", "description": [], - "signature": [ - "((agg: TAggConfig) => string) | undefined" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypeConfig.getResponseId.$1", - "type": "Uncategorized", - "tags": [], - "label": "agg", - "description": [], - "signature": [ - "TAggConfig" - ], - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false } ], "initialIsOpen": false }, { "parentPluginId": "data", - "id": "def-common.AggTypesDependencies", + "id": "def-common.CommonAggParamsCumulativeSum", "type": "Interface", "tags": [], - "label": "AggTypesDependencies", + "label": "CommonAggParamsCumulativeSum", "description": [], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsCumulativeSum", + "text": "CommonAggParamsCumulativeSum" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.calculateBounds", - "type": "Function", + "id": "def-common.CommonAggParamsCumulativeSum.buckets_path", + "type": "string", "tags": [], - "label": "calculateBounds", + "label": "buckets_path", "description": [], "signature": [ - "(timeRange: ", - "TimeRange", - ") => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRangeBounds", - "text": "TimeRangeBounds" - } + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.calculateBounds.$1", - "type": "Object", - "tags": [], - "label": "timeRange", - "description": [], - "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" - ], - "path": "src/plugins/data/common/search/aggs/buckets/date_histogram.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.getConfig", - "type": "Function", + "id": "def-common.CommonAggParamsCumulativeSum.metricAgg", + "type": "string", "tags": [], - "label": "getConfig", + "label": "metricAgg", "description": [], "signature": [ - "(key: string) => T" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.getConfig.$1", - "type": "string", - "tags": [], - "label": "key", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.CommonAggParamsDerivative", + "type": "Interface", + "tags": [], + "label": "CommonAggParamsDerivative", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsDerivative", + "text": "CommonAggParamsDerivative" }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.getFieldFormatsStart", - "type": "Function", + "id": "def-common.CommonAggParamsDerivative.buckets_path", + "type": "string", "tags": [], - "label": "getFieldFormatsStart", + "label": "buckets_path", "description": [], "signature": [ - "() => Pick<", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatsStartCommon", - "text": "FieldFormatsStartCommon" - }, - ", \"deserialize\" | \"getDefaultInstance\">" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AggTypesDependencies.aggExecutionContext", - "type": "Object", + "id": "def-common.CommonAggParamsDerivative.metricAgg", + "type": "string", "tags": [], - "label": "aggExecutionContext", + "label": "metricAgg", "description": [], "signature": [ - "{ shouldDetectTimeZone?: boolean | undefined; } | undefined" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/agg_types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/derivative.ts", "deprecated": false, "trackAdoption": false } @@ -23192,34 +24966,85 @@ }, { "parentPluginId": "data", - "id": "def-common.AutoBounds", + "id": "def-common.CommonAggParamsMovingAvg", "type": "Interface", "tags": [], - "label": "AutoBounds", + "label": "CommonAggParamsMovingAvg", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsMovingAvg", + "text": "CommonAggParamsMovingAvg" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.AutoBounds.min", - "type": "number", + "id": "def-common.CommonAggParamsMovingAvg.buckets_path", + "type": "string", "tags": [], - "label": "min", + "label": "buckets_path", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.AutoBounds.max", + "id": "def-common.CommonAggParamsMovingAvg.window", "type": "number", "tags": [], - "label": "max", + "label": "window", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/histogram.ts", + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.CommonAggParamsMovingAvg.script", + "type": "string", + "tags": [], + "label": "script", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.CommonAggParamsMovingAvg.metricAgg", + "type": "string", + "tags": [], + "label": "metricAgg", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/metrics/moving_avg.ts", "deprecated": false, "trackAdoption": false } @@ -23228,54 +25053,57 @@ }, { "parentPluginId": "data", - "id": "def-common.BaseAggParams", + "id": "def-common.CommonAggParamsSerialDiff", "type": "Interface", "tags": [], - "label": "BaseAggParams", + "label": "CommonAggParamsSerialDiff", "description": [], - "path": "src/plugins/data/common/search/aggs/types.ts", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.CommonAggParamsSerialDiff", + "text": "CommonAggParamsSerialDiff" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } + ], + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.BaseAggParams.json", - "type": "string", - "tags": [], - "label": "json", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/aggs/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.BaseAggParams.customLabel", + "id": "def-common.CommonAggParamsSerialDiff.buckets_path", "type": "string", "tags": [], - "label": "customLabel", + "label": "buckets_path", "description": [], "signature": [ "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BaseAggParams.timeShift", + "id": "def-common.CommonAggParamsSerialDiff.metricAgg", "type": "string", "tags": [], - "label": "timeShift", + "label": "metricAgg", "description": [], "signature": [ "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/types.ts", + "path": "src/plugins/data/common/search/aggs/metrics/serial_diff.ts", "deprecated": false, "trackAdoption": false } @@ -23284,248 +25112,191 @@ }, { "parentPluginId": "data", - "id": "def-common.BaseHit", + "id": "def-common.CommonAggParamsTerms", "type": "Interface", "tags": [], - "label": "BaseHit", + "label": "CommonAggParamsTerms", "description": [], "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BaseHit", - "text": "BaseHit" + "section": "def-common.CommonAggParamsTerms", + "text": "CommonAggParamsTerms" }, - "" + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BaseAggParams", + "text": "BaseAggParams" + } ], - "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.BaseHit._index", + "id": "def-common.CommonAggParamsTerms.field", "type": "string", "tags": [], - "label": "_index", + "label": "field", "description": [], - "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BaseHit._id", + "id": "def-common.CommonAggParamsTerms.orderBy", "type": "string", "tags": [], - "label": "_id", + "label": "orderBy", "description": [], - "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BaseHit._source", - "type": "Uncategorized", + "id": "def-common.CommonAggParamsTerms.size", + "type": "number", "tags": [], - "label": "_source", + "label": "size", "description": [], "signature": [ - "T" + "number | undefined" ], - "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BaseHit.fields", - "type": "Object", + "id": "def-common.CommonAggParamsTerms.shardSize", + "type": "number", "tags": [], - "label": "fields", + "label": "shardSize", "description": [], "signature": [ - "Record | undefined" + "number | undefined" ], - "path": "src/plugins/data/common/search/expressions/eql_raw_response.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.BucketAggParam", - "type": "Interface", - "tags": [], - "label": "BucketAggParam", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggParam", - "text": "BucketAggParam" - }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggParamType", - "text": "AggParamType" }, - "" - ], - "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "data", - "id": "def-common.BucketAggParam.scriptable", + "id": "def-common.CommonAggParamsTerms.missingBucket", "type": "CompoundType", "tags": [], - "label": "scriptable", + "label": "missingBucket", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BucketAggParam.filterFieldTypes", - "type": "CompoundType", + "id": "def-common.CommonAggParamsTerms.missingBucketLabel", + "type": "string", "tags": [], - "label": "filterFieldTypes", + "label": "missingBucketLabel", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.FieldTypes", - "text": "FieldTypes" - }, - " | undefined" + "string | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BucketAggParam.onlyAggregatable", + "id": "def-common.CommonAggParamsTerms.otherBucket", "type": "CompoundType", "tags": [], - "label": "onlyAggregatable", + "label": "otherBucket", "description": [], "signature": [ "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.BucketAggParam.filterField", - "type": "Function", + "id": "def-common.CommonAggParamsTerms.otherBucketLabel", + "type": "string", "tags": [], - "label": "filterField", - "description": [ - "\nFilter available fields by passing filter fn on a {@link DataViewField}\nIf used, takes precedence over filterFieldTypes and other filter params" + "label": "otherBucketLabel", + "description": [], + "signature": [ + "string | undefined" ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.CommonAggParamsTerms.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.FilterFieldFn", - "text": "FilterFieldFn" - }, - " | undefined" + "string | string[] | number[] | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.Cidr", - "type": "Interface", - "tags": [], - "label": "Cidr", - "description": [], - "path": "src/plugins/data/common/search/expressions/cidr.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, { "parentPluginId": "data", - "id": "def-common.Cidr.mask", - "type": "string", + "id": "def-common.CommonAggParamsTerms.include", + "type": "CompoundType", "tags": [], - "label": "mask", + "label": "include", "description": [], - "path": "src/plugins/data/common/search/expressions/cidr.ts", + "signature": [ + "string | string[] | number[] | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-common.CidrMaskIpRangeAggKey", - "type": "Interface", - "tags": [], - "label": "CidrMaskIpRangeAggKey", - "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, { "parentPluginId": "data", - "id": "def-common.CidrMaskIpRangeAggKey.type", - "type": "string", + "id": "def-common.CommonAggParamsTerms.includeIsRegex", + "type": "CompoundType", "tags": [], - "label": "type", + "label": "includeIsRegex", "description": [], "signature": [ - "\"mask\"" + "boolean | undefined" ], - "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "data", - "id": "def-common.CidrMaskIpRangeAggKey.mask", - "type": "string", + "id": "def-common.CommonAggParamsTerms.excludeIsRegex", + "type": "CompoundType", "tags": [], - "label": "mask", + "label": "excludeIsRegex", "description": [], - "path": "src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts", + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", "deprecated": false, "trackAdoption": false } @@ -31653,6 +33424,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.SHARD_DELAY_AGG_NAME", + "type": "string", + "tags": [], + "label": "SHARD_DELAY_AGG_NAME", + "description": [], + "signature": [ + "\"shard_delay\"" + ], + "path": "src/plugins/data/common/search/aggs/buckets/shard_delay.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.siblingPipelineType", diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 15b457e57a329..3cf065a957729 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3132 | 33 | 2429 | 23 | +| 3211 | 33 | 2508 | 23 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 1ed2c22bd9856..6fca87db82f91 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 2ca0a8d2f0ba2..9e4d3deac15c4 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 5277f71f1d3dd..cf43b6fa9dd9e 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index d47a4f9daa55f..a5ea60ca6db86 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index ce2cd5119ddb0..797cd5596c31f 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 826b95556b7c5..ec45435a4e875 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 849c6c1c88fc4..430ba54813520 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index cebc81eaa7f7e..30143657a7b1e 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index b7d3c963abf2b..95bc923cc9da0 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 2fa566f082091..8f51330d8bc6d 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 01d16f604fd83..7263ed936315a 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index ac1db5c87cca3..f5d8586e5194b 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 74f016f3f94c8..326d612509b6c 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 6e5dd4305864a..e5a8bb47a0520 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 0df7233b53270..23ae523bade64 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 50d60e1a1f2c9..d5982b5a2a865 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 73847652b7034..705fa73b04c1d 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 0fbe81013238d..53b40226f0d78 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 54c128a3a1609..51a1fd22bc039 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index cf24875c1242d..c23d6a727bb38 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 45b092ac58bdf..1196f07945026 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 2c755fa2fc81f..046b3c31f107b 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 8b20c473d5514..9a3e6bd0dc825 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index bce9a08357803..76fd8a4068f19 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index cdc56d029f4b0..337717de0a65a 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 5b04089ad0f1a..32c3971bd0909 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 1a25a412c39d0..ca6a5be21ec3a 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index a56fc83dd26a3..90854571dc71c 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 120617e54501e..898224f9aba1a 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 444c05f6ceb5a..f6686201cb138 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 213d1edd926fd..c090283f69746 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 2d96611dfeb39..cc0dfccc59154 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index d98963fe4d66a..116d58a6218b9 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 36a7e6d4685c1..dbb19f977cc78 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 044c6e58328ae..31c1b78e94389 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 7b6e21054a85e..149bec1a8acb5 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 66f288b6ca432..a35508ec3930f 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -9086,6 +9086,20 @@ "path": "x-pack/plugins/fleet/common/authz.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.FleetAuthz.packagePrivileges", + "type": "Object", + "tags": [], + "label": "packagePrivileges", + "description": [], + "signature": [ + "{ [packageName: string]: { actions: { [key: string]: { executePackageAction: boolean; }; }; }; } | undefined" + ], + "path": "x-pack/plugins/fleet/common/authz.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -14188,6 +14202,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.ENDPOINT_PRIVILEGES", + "type": "Array", + "tags": [], + "label": "ENDPOINT_PRIVILEGES", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/fleet/common/constants/authz.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.EsAssetReference", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 374d04b3e52e5..173f9f76092f4 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 986 | 3 | 886 | 17 | +| 988 | 3 | 888 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 6c708f5ad470c..c689891e7444b 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 604c7acbae0fd..5dfb91864c060 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 68678af8f9ee5..62db97259ee59 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 1621311ffc7df..87bd3e83a63b9 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 407e7732ad947..eee8cda64e245 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 39d7876c10861..fd32cbab99193 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index a4aaec9817ae3..00175e67b30b5 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index a3fecbf61097d..33ee5123bb5cd 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 10ea930d88d01..bf08287ddc424 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index f15645592bed5..d4cb51aa5139f 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 18c46827b5775..389117150bd55 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index d6d2fc6b2ce87..749a781cf0e47 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 0c6d93303734f..c0f7031e47a7a 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index fb40c3a6d1ebb..1595bab913e80 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index bb08718097b9f..ba7823ee64ac1 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index a2a61c275f4e5..105ff1b565b71 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index baf43cc906d1c..ef9a534fa3470 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 287bfd2ff5d81..6a263b0defbb9 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 8d826cc0958c1..defb49cbe69ea 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 0b76528343988..0a80a8f9d7c64 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 2cbf87044cd77..f87f80a173bee 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 234d6104f601c..465473d3ef7cd 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 46f49b9660db4..8bc8181b1c666 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 1459132fe2c97..26ac1ad8da546 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 68990474b42c1..79c65aec69dbb 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 24a14d2aef1c2..4b5645cc4b132 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index c589ecba7e2df..fb74c109385e8 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index fed58f168d8be..6dfdbdc6a7e19 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 08c49c3eb81aa..e5787da85b661 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 92b4083a9bb43..9f30996b585d7 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 805071e48d605..97256b7516108 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index ab69cd4d6a69f..12779749648df 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 697f32d5a32c1..89fc744b1c703 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 909e9933df071..bad36ac54b215 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index d89fb32724da4..5db786b7754f0 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 00ec4b5ef0b87..0141718a2ccd2 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 6d43aaea5d4a8..2d21be701e133 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index ca795d6887d01..1dc30aff04e5d 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index feb8249b263af..7e999f21f3e1a 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 6353b853b75da..c957561aa4eca 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 4ccd910e9c854..6ba6188770d4c 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 57ca826ee2790..09bb2339c9782 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 6edee369dba21..a036a5bb51c0e 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index eafdf4b402b44..42db1a2581375 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index cc36f15b1ebc3..cd0875458844d 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 96b693c7772ed..468ebd13bf1b3 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 0bdb1a2fd6024..c986f9acf0ef3 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 3a8a11486b91d..d6cd91c67140a 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 73fa79e82d723..7a6ba37310aaf 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 83cb7a3a226b7..24b234f7f811d 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index db2f37634a36a..c5359e8345a38 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 6a9175ac9e6d8..b847a51cd7a0d 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 6791dfa422f31..cc02f6073ea91 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 7ddb5fa99580a..b1d46f4e5fe8c 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index b282941a27a0e..ee601e6352e35 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index aa9990a0b855c..6eec129fe2615 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index cd0ed8c810ef9..983047e82691a 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index daa193327e0b1..0190b56cf9739 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 156fdda6dc807..60d444ced08da 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index b093eedb0c3a9..e9f8e03e21a24 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index ad30901f82030..cd76ecc1e5de7 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 9d57d94f5a07c..adcf635bd0895 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 1e8cf6217f00c..d8f11b0a78906 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index d0f14840512f3..04891d7eee772 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 2bc0611b6ba88..a211477391f89 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index bf2faaedb09e5..cabbd8bcaf86c 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 2b10ea2601b1e..c6a7f734219d8 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index c3b9a1bbceb2d..7af48bcbf5f1b 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 137bb7f40b9d6..ca2a47bc99331 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index b1ec099e96222..5c6a4b84856c7 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index aabccea0ba2d9..dd0e97e30fc7c 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 32511431d67ed..2d4f36ed093a2 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index b428f24f8a449..587243bb64061 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 608f4b7dfd1d8..96b79793ed095 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 154d3ddd0c6f7..d8cfe4d9f349d 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 4c8d0fe979c03..fe90efd441051 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 865624983d3a9..e7b47436fa761 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 85b0cd0b15092..8fa86908f719d 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index d9a881ae38e33..76ca7ae6c111b 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 19733f115b02c..3e17e9ed94637 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 2d09cd0aedb2b..01d683396e2b7 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 3cc3aee2a0d6e..891c8d8767547 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 13da3a5ec6169..a935d019adeb4 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index d7651228361cf..04f71c530c9a5 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 9e013dc8fe0e7..53f1d9fe670a0 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 2a05b8a951bdd..f8ff0087dbbfc 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 5d4f4c33489ac..c93bb2546524b 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 223d1f48f38d7..6121b09f8c1b4 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 394b7517d61f8..49dffda39dcc6 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 9501a0ec85e8a..74c3216b75ebc 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 5c499f63e4164..50ebcbe4058b9 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 5e53a8611610d..c7649dcf597cf 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index aaaaa91454760..7e8106d7e3c66 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index a516c58bfc4c7..32e6b9184dbe1 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index e11acda897e25..4e5cbf65974a7 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index bd3eae858db51..93c620cca65a7 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index c8dfa3b6b28a6..13734215059b0 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 5a7f145e9f9f8..08dbe296b4323 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 69a77c8c4f96f..b88675d50c204 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 62c5882d56a6a..fc53dc77eda1c 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index a3850e7f23e98..2526e6dc2b6b9 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 093ac2d0c1a13..8b557e2e502b2 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index d9e591359ab58..74231286ffe09 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 51db906dd4743..a4e8cb4241bef 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 86cc637cdf6a8..f56eecbd69552 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index c7464858aae94..2067fdbc72fda 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index eb3c5ebe3c362..a7e7a11ae05ea 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 1be122dec707e..febc5d3038933 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 6af293e6fdae8..47cc583e2df0e 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index bf82eb7970b55..9e16d8304decd 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index a696921789baf..5db83c7383b67 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index b69a8a4a78e95..8fafac5831639 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index d6c392f44c42f..9fbffa9c5df93 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index ce324b10707bb..d253278b97f15 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index e92985c2847d0..5dd3d790c3eaa 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index b9d2d41ad3c6f..ed0c7c4e240ce 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 1b3c4b7c97006..5c44022f60828 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index c6c249f893682..ef4b9a440b592 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index adfde3eb57935..559df72a076e4 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index e5b665a749de4..6160c98032fef 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index b9857a22a4aa5..5765d85478d62 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index d9d2d411504ec..09d09fe708575 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 16067815b76ab..5416044cac958 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index ee51fe5d501b1..962d718e0042b 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 2621608fb4385..07d55b8bdc9ad 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 95e59f965436e..f31451fde9e77 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 47b96c57a4d99..97d1f20eba7af 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index eb621d99fa2e3..1c1ec6407d249 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index de1c53c1af13d..678cf37d24e81 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index bd3799e8d44b0..da93d1e0b8552 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index bfae0d9378d37..73d6c70c1a9ac 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index ebb26601a4699..2b499a47657b8 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 09baa5daf0aa1..fcec7c16e5f85 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index fcaebe64cd996..197152a6dae02 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 0fde59191c733..80320e7312485 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index ff0a3055cf443..63fa1e47ac1bf 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 3b4570f7c036b..127679f751476 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index a76df96a31831..f633f52c6174d 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index c70c8ca947e65..bc446d004b249 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 66e637f6c3f6a..ecbb1447d1e5c 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index ba2752d9b3405..c694b3c9f6584 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 26a690d05280a..39099151043aa 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 3260ae2e31a22..73e0b05504386 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 441ed1b3c2600..0012db4e553fb 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index cd33f1d1f0502..1ef8c6abfc97a 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 06e62aeef187e..7c9440c92271b 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 7151aacf2084d..bd5556b6ce877 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 354e4d93b9370..364b20ab80d66 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index d5992e345f9b5..aa7990a556b57 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index d1e1d00ed8ed0..d4a0a789572fb 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 93528eb3bfbf1..826ee5d1b3459 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 6ba05ca53fd8a..e68a43d0ce9fe 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 6275d70c17032..8c938db4082e1 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 625810a6cc773..0cbb8955ac42e 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index cd3eda1a2203b..67a42c6099d97 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 2c9f352ccd8d4..791006e8cea6b 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 1e513b3ad0f7e..b1ae46812b1d2 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index dd9cb93ed4e4e..ef778256b7997 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 24945054119c8..545b597ef2b8b 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index b3a148ab80f22..99402f8c657ce 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index d492abb2efc12..a5081a8a512eb 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index a01955f4262bf..b490e18b99885 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index a33d80bd4cd2f..01b13b900344a 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 50a975d51e964..90ea1b666c8aa 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 7a7927ec2f4ae..a9fb17b3cd06f 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 03d3a1246b3ce..0afac1bca8d36 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 8c0d3ce762be5..d2e4558115131 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index d1722a8bf5614..42e02f9783509 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 6aefed8871217..5169411f49a2a 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index c1b894325cd54..5872d911b8f47 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 80533016c1c97..8f8299fb3972b 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 3258c848c7d36..93faea017a0fb 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 61fbb56f1a171..f29fe40eb21d1 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 78e17c86e33d3..2d7b26292e0e8 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index b2bbf6fdc00b0..da7db13bb38b1 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index a7e2be8113fac..3d7f8993dd9fb 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 572d41cd16fa3..db9354454f22f 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 3ab838c7557e4..5bee2fdbf5f66 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 613b02bd58925..f6c2a308c267e 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 2e71f587b78f1..52dcd1a4217e9 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 6354505d65347..e8d91e141f195 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 9c7b4486fe151..93882b49ca42b 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 85e406eb02adb..5955c7f01158f 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index a0c6fe024ffe7..e50741bdb3a60 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index ece22c3ff7096..24eedfb0387a4 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 91b1a139c1a9c..1d4fddcc02250 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 0f7cef5022e7c..59d37e8a5d09f 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 263994dcecb74..5e218789e57f1 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index c4721c56b46c4..15647319a90f0 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 428a6a2fd3411..56039851af766 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 1e3755ae194ca..807a82e9cbff7 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 0c90d5d563dfe..dfb4aea12e2d7 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 37eb518ebfb80..28487fab71400 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.devdocs.json b/api_docs/kbn_journeys.devdocs.json index d34c75a453efb..601334a03f8e8 100644 --- a/api_docs/kbn_journeys.devdocs.json +++ b/api_docs/kbn_journeys.devdocs.json @@ -792,7 +792,7 @@ "signature": [ "(step: ", "AnyStep", - ", screenshot: Buffer) => Promise" + ", screenshot: Buffer, fullscreenScreenshot: Buffer) => Promise" ], "path": "packages/kbn-journeys/journey/journey_screenshots.ts", "deprecated": false, @@ -827,6 +827,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-server.JourneyScreenshots.addError.$3", + "type": "Object", + "tags": [], + "label": "fullscreenScreenshot", + "description": [], + "signature": [ + "Buffer" + ], + "path": "packages/kbn-journeys/journey/journey_screenshots.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -841,7 +856,7 @@ "signature": [ "(step: ", "AnyStep", - ", screenshot: Buffer) => Promise" + ", screenshot: Buffer, fullscreenScreenshot: Buffer) => Promise" ], "path": "packages/kbn-journeys/journey/journey_screenshots.ts", "deprecated": false, @@ -876,6 +891,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-server.JourneyScreenshots.addSuccess.$3", + "type": "Object", + "tags": [], + "label": "fullscreenScreenshot", + "description": [], + "signature": [ + "Buffer" + ], + "path": "packages/kbn-journeys/journey/journey_screenshots.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -888,7 +918,7 @@ "label": "get", "description": [], "signature": [ - "() => { path: string; type: \"success\" | \"failure\"; title: string; filename: string; }[]" + "() => { path: string; fullscreenPath: string; type: \"success\" | \"failure\"; title: string; filename: string; fullscreenFilename: string; }[]" ], "path": "packages/kbn-journeys/journey/journey_screenshots.ts", "deprecated": false, diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index a8348f70822d4..01244be181bf2 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 62 | 0 | 57 | 5 | +| 64 | 0 | 59 | 5 | ## Server diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 10fae76d04b1e..7d70916d448e4 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 7ff3186afb099..63d9dc96362af 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 34569da5d7028..6b1049cee9be2 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 8e29bd95f0456..a175904d02573 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 9e17319c10337..ca5e2566d884c 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index cceee4bc2eb2e..78c0d9fbb8369 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 78eda05c5b44f..ffdc48a0fb0d3 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index d197afe74668f..a1f168d3fc91d 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index abf0f29de3277..7da86649ddf39 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 52929cddf140f..29b3596a8bc29 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 6a156288e0d69..fb729fc0747ed 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 053dfb0beb5d5..54d57200bd5a1 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 86d5c3669cd80..ba91201a52c6c 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index cf2e35c77d2a7..493ca235ab7a9 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 125bda46f869c..b16b234ee0f50 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index b950dc5c5fe54..f7d3bada225b9 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index eca63370271b2..65a1a0d424f15 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 08f9cfb7699d9..ef453ad073e4f 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 771ef4c3664dd..f441cc10aa506 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index bc9e95fce1374..c81350b38632b 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 2d5f008c026bb..8e6bdc15f9807 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json index e313879791dd2..b42cebbc4fb84 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json @@ -227,7 +227,7 @@ "label": "Language", "description": [], "signature": [ - "\"eql\" | \"kuery\" | \"lucene\"" + "\"eql\" | \"lucene\" | \"kuery\"" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", "deprecated": false, @@ -242,7 +242,7 @@ "label": "LanguageOrUndefined", "description": [], "signature": [ - "\"eql\" | \"kuery\" | \"lucene\" | undefined" + "\"eql\" | \"lucene\" | \"kuery\" | undefined" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", "deprecated": false, @@ -660,7 +660,7 @@ "label": "ThreatLanguage", "description": [], "signature": [ - "\"eql\" | \"kuery\" | \"lucene\" | undefined" + "\"eql\" | \"lucene\" | \"kuery\" | undefined" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, @@ -675,7 +675,7 @@ "label": "ThreatLanguageOrUndefined", "description": [], "signature": [ - "\"eql\" | \"kuery\" | \"lucene\" | undefined" + "\"eql\" | \"lucene\" | \"kuery\" | undefined" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 0638d5f8bf083..4066f50c33520 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index b1a1a3fc64da3..720ede9fa9e87 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index b03d18fec4ced..f2220252df9f4 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 7c360b9333e54..4ddd805beceec 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index f18f2b1aead14..837b102f35b7c 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index b7433466578b1..5e117e7fdd5ff 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 2654598d4dcc9..a0d87f4a94bbb 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 91475331ebad8..a42cbdfe87318 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 79adceb20aeae..c978507107c7d 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index a5bb0c13967e9..d7cd694497c7f 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index c0b27f428d0b7..c6c4f053e9525 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 1c193a552796d..fc18c7757d1d9 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index b521420154f80..2c9d51b468868 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index f5eb308795aca..1a40ade046a8a 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 42a0beca9e7ea..6d9064ab9d9ac 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 2df8ddd83c85d..e28b92febd738 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index b305a644f9271..3515b5ee35bf4 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index f0c440f375743..ba2f64392e72c 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 82f90f66491df..6942dcbd38fca 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index bccf44bcdb7d1..69a6794fbccd5 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 1de00675c4406..a8465df7126e6 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index a74f39651be04..9e930a85deb81 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 357f925da370f..d96c9079cf5fd 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 8752e5a774370..86595fb7cfb1f 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index d939e7321db44..9132796651764 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index e914e2f8a891c..e5b6a7845bbeb 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index e193d62f4ee6b..09f6ed5cf1fa0 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index dc7bc516665c5..ef0e7d2b60bf3 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index bbd8f3cca0e03..43ef0de76bb28 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index ca8e953699da6..eecbe82f148c7 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 3dfe50049855e..001790b9086e2 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index f20ef222d9e8e..42efbc3a489cc 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index f1dfbd73ce704..71404d9e00a6f 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 5bbf41024b26d..a24b3d7d985a9 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index cd7b6bfac4e7c..b833764dfacbc 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index b12315abf9fc6..bf5525e9d8ea8 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index b7e0841ee703d..16c867044187d 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 9c495c4aa7743..ef2f54962c583 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 7b767e626b7c9..8fa73d4c68dad 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index ab4be4010f901..8a049d4872b72 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 43a7444c0c030..f1ba970a48042 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 020326e9a0c53..2ff62c2d04f35 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 72fb4fa068c7e..b77c7bfc44e93 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 140c6462d2e78..f81111a502799 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 229feacde942d..eaf2d94c9a05d 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 691a582e60a70..96f5d4d6ab2a7 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index e4f3ab2faf109..db3273ea8883f 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 1bfcb9c72707a..fe4e9cade439f 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 8910379b7ebe3..9744d7e40941f 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 2f874f70f25c7..5bf8d7c641755 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 0d8d08d774ef7..24144f46636bc 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 09f25106acbcf..fe7f2f02b8192 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 85eb0e283b7a7..7855bb68d62db 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 65c18d1961171..5adb3b961a126 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index c6acb5b0fbc56..879d594892e8b 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 5ec4f40c0dae5..13a63d5a655f6 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index ee036d3e7c04b..17c5bc67927ee 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 7471c9772caa9..661dda80a0227 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index fffdeb850c4c8..3a54ee39d7116 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 4873a6d3d2afc..62af983d79330 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 3f7dbbba241a9..c101218ee3f45 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index d183dd7fce455..e6eb90ca75ba8 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 0fbef0861888c..745b2e56c1c9d 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index f942edc6beb32..3aa129254bb54 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 0b0b6996ac826..f4406dfe719d8 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index e8ff8b692628b..6aa4f4f225193 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 13c0aab065080..92b5f7e806788 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index b5d5c37c7c2d4..2e5da06f70dd9 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index a2e0b0c3660d1..a2dd183ea48bc 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index d9b70e1d9a354..55b3f0eff7da0 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index e5a8151f36d80..dbf1d15ca06df 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 3ceb6a607ee4f..16eb11d15bbf2 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index b6fa1bd6b9520..e9f3d869a2077 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 28d4231ae4121..95e0ce94137e7 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 625317137d6c3..7d399eaa890b1 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -7737,13 +7737,35 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, + "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"GET /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; } & { name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: true; }; budgeting_method: \"occurrences\"; objective: { target: number; }; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, "> | undefined; \"POST /api/observability/slos\"?: ", "ServerRoute", "<\"POST /api/observability/slos\", ", "TypeC", "<{ body: ", - "IntersectionC", - "<[", "TypeC", "<{ name: ", "StringC", @@ -7841,13 +7863,7 @@ "TypeC", "<{ target: ", "NumberC", - "; }>; }>, ", - "PartialC", - "<{ settings: ", - "PartialC", - "<{ destination_index: ", - "StringC", - "; }>; }>]>; }>, ", + "; }>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -7949,13 +7965,35 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, + "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"GET /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; } & { name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: true; }; budgeting_method: \"occurrences\"; objective: { target: number; }; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, "> | undefined; \"POST /api/observability/slos\"?: ", "ServerRoute", "<\"POST /api/observability/slos\", ", "TypeC", "<{ body: ", - "IntersectionC", - "<[", "TypeC", "<{ name: ", "StringC", @@ -8053,13 +8091,7 @@ "TypeC", "<{ target: ", "NumberC", - "; }>; }>, ", - "PartialC", - "<{ settings: ", - "PartialC", - "<{ destination_index: ", - "StringC", - "; }>; }>]>; }>, ", + "; }>; }>; }>, ", { "pluginId": "observability", "scope": "server", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index ede6a978b1972..afdb6cd4fbb3c 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 33f2325a9d42b..a634c2e2206cc 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index f52aa4bd15337..00a92aa5b8132 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 31641 | 179 | 21224 | 998 | +| 31772 | 179 | 21354 | 1002 | ## Plugin Directory @@ -47,7 +47,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 103 | 0 | 84 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 144 | 0 | 139 | 10 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3132 | 33 | 2429 | 23 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3211 | 33 | 2508 | 23 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 15 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | @@ -81,7 +81,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 15 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 986 | 3 | 886 | 17 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 988 | 3 | 888 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -175,7 +175,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 631 | 12 | 602 | 14 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 679 | 12 | 649 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -367,7 +367,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | App Services | - | 35 | 4 | 35 | 0 | | | [Owner missing] | - | 20 | 0 | 20 | 2 | | | [Owner missing] | - | 13 | 0 | 13 | 0 | -| | [Owner missing] | - | 62 | 0 | 57 | 5 | +| | [Owner missing] | - | 64 | 0 | 59 | 5 | | | [Owner missing] | - | 96 | 0 | 95 | 0 | | | Kibana Core | - | 30 | 0 | 5 | 37 | | | Kibana Core | - | 8 | 0 | 8 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 8bd637653c617..971ed39aee0ed 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index cb52176e574d2..8d2be8c0f9dd7 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 41f98b3aa5474..916ffb9e1e4e0 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 9de91bdd316fe..aa8b69de45190 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index e881b7e1d8d1d..7ecab5c862bf0 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index b3f7b8baeea0d..42e1a51c2ed9f 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 84762f6833dfc..4c4a4a4be8d7f 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 5297a1c4dfeab..d180b9df678f8 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index b48dac4d77ca7..d8ee1bd800f99 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index da2c95cf361b5..a57ba63fec003 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 50cc4890609aa..785d7f8ea6a32 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index d308998e912cf..bf65a9aa5c648 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index ee3015be49b3b..5e294d8d6ea59 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 1ab87605dcb7b..4c436c7b23e66 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index b978b8ae17757..5bc1fcd058003 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 43109981e1bd7..952c20eac620f 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 1d69c84ad5c82..8ad8f2f5fe218 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -58,7 +58,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly threatIntelligenceEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -1061,7 +1061,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; threatIntelligenceEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; responseActionsEnabled: boolean; }>; }" + "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; responseActionsEnabled: boolean; endpointRbacEnabled: boolean; }>; }" ], "path": "x-pack/plugins/security_solution/server/config.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 12f05e5363bc6..9cf5459a07a3e 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index b44a921a532cb..23f8e18b5e278 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index a9333634f6450..7ba452349a66b 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 39bb856edb320..194b818f081bf 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 8614e0abbad77..95118ea8b8058 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index fbfeb304d9bab..b3467e6a698dc 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 0941e34da7c6b..aabbd1805b61d 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 268f95227b6b2..d35966ac57c9c 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 8710f593cbf06..1dbef1ee43f39 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index b8aacfc863f72..438e8d307b1de 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 42d0c24ed78a8..f032f6eee5e0a 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index b42450f650ec3..0b1523b1a10d3 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 0604fc8cf5698..b584556618afe 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index be005c7cd4081..5875aa47c1309 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -6073,7 +6073,7 @@ "label": "language", "description": [], "signature": [ - "\"eql\" | \"kuery\" | \"lucene\"" + "\"eql\" | \"lucene\" | \"kuery\"" ], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts", "deprecated": false, diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 9791e2787c44a..14ef93c594cc2 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index ec5f484f4641c..b66dc03b52acd 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 541b2194d3f33..0a3cbb630285f 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 0905d751b5fa1..2a01df283087a 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index b3fd83a428f3a..f46cdf9d62ee1 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 16370d522a392..f70e303ebe4d7 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index bdf37515d75f1..2e10e0a54e2cd 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 62acbd2c862fd..18b782fa7081b 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 245f43ae0e848..7a9ded502796d 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index f6a52d69a1e43..34bbb721a3728 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 38b3afeff9e02..c3afd8d86827c 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 5454be9ab6cd1..271d79629a0b7 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 58b5bd6f2338b..f28ab39c716fb 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index f90f2436dcc85..32a96558c72f8 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 0238df60ca4da..827eb1f6f0969 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 03c32627ef241..d9fc5b663679c 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index af604e573f216..e8dfbf76d6ffa 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 52000bc18ca5e..c962200fe1fdc 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 1cff837940b2b..abe648760bf12 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 2a1f1ce82cb39..4260c1aaeb253 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.devdocs.json b/api_docs/vis_type_xy.devdocs.json index 5468fa97bdb7a..e9d569dcbde48 100644 --- a/api_docs/vis_type_xy.devdocs.json +++ b/api_docs/vis_type_xy.devdocs.json @@ -825,12 +825,20 @@ "Omit<", { "pluginId": "visualizations", - "scope": "public", + "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-public.SchemaConfig", + "section": "def-common.SchemaConfig", "text": "SchemaConfig" }, - ", \"params\"> & { params: {} | ", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SupportedAggregation", + "text": "SupportedAggregation" + }, + ">, \"params\"> & { params: {} | ", { "pluginId": "visualizations", "scope": "common", diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 2d2db30e66054..f03c9b37afbb4 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index 086c698e01e8e..adc8f3022cb1a 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -97,21 +97,21 @@ "label": "navigateToLens", "description": [], "signature": [ - "((params?: ", + "((vis?: ", { "pluginId": "visualizations", - "scope": "common", + "scope": "public", "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" + "section": "def-public.Vis", + "text": "Vis" }, - " | undefined, timeRange?: ", + " | undefined, timeFilter?: ", { "pluginId": "data", - "scope": "common", + "scope": "public", "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" }, " | undefined) => Promise<", { @@ -126,8 +126,8 @@ "pluginId": "visualizations", "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-common.XYConfiguration", - "text": "XYConfiguration" + "section": "def-common.Configuration", + "text": "Configuration" }, "> | null> | undefined) | undefined" ], @@ -135,6 +135,36 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.BaseVisType.getExpressionVariables", + "type": "Function", + "tags": [], + "label": "getExpressionVariables", + "description": [], + "signature": [ + "((vis?: ", + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.Vis", + "text": "Vis" + }, + " | undefined, timeFilter?: ", + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataQueryPluginApi", + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" + }, + " | undefined) => Promise>) | undefined" + ], + "path": "src/plugins/visualizations/public/vis_types/base_vis_type.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "visualizations", "id": "def-public.BaseVisType.icon", @@ -1981,6 +2011,76 @@ } ], "functions": [ + { + "parentPluginId": "visualizations", + "id": "def-public.getDataViewByIndexPatternId", + "type": "Function", + "tags": [], + "label": "getDataViewByIndexPatternId", + "description": [], + "signature": [ + "(indexPatternId: string | undefined, dataViews: ", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + }, + ") => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | null>" + ], + "path": "src/plugins/visualizations/public/convert_to_lens/datasource.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.getDataViewByIndexPatternId.$1", + "type": "string", + "tags": [], + "label": "indexPatternId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/public/convert_to_lens/datasource.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "visualizations", + "id": "def-public.getDataViewByIndexPatternId.$2", + "type": "Object", + "tags": [], + "label": "dataViews", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + } + ], + "path": "src/plugins/visualizations/public/convert_to_lens/datasource.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-public.getFullPath", @@ -2030,7 +2130,7 @@ "section": "def-public.Vis", "text": "Vis" }, - ", { timeRange, timefilter }: ", + ", params: ", { "pluginId": "visualizations", "scope": "public", @@ -2072,7 +2172,7 @@ "id": "def-public.getVisSchemas.$2", "type": "Object", "tags": [], - "label": "{ timeRange, timefilter }", + "label": "params", "description": [], "signature": [ { @@ -3219,83 +3319,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig", - "type": "Interface", - "tags": [], - "label": "SchemaConfig", - "description": [], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig.accessor", - "type": "number", - "tags": [], - "label": "accessor", - "description": [], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig.label", - "type": "string", - "tags": [], - "label": "label", - "description": [], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig.format", - "type": "Object", - "tags": [], - "label": "format", - "description": [], - "signature": [ - "{ id?: string | undefined; params?: ", - "SerializableRecord", - " | undefined; }" - ], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig.params", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "SchemaConfigParams" - ], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.SchemaConfig.aggType", - "type": "string", - "tags": [], - "label": "aggType", - "description": [], - "path": "src/plugins/visualizations/public/vis_schemas.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "visualizations", "id": "def-public.SerializedVis", @@ -4538,21 +4561,21 @@ "\nIf given, it will navigateToLens with the given viz params.\nEvery visualization that wants to be edited also in Lens should have this function.\nIt receives the current visualization params as a parameter and should return the correct config\nin order to be displayed in the Lens editor." ], "signature": [ - "((params?: ", + "((vis?: ", { "pluginId": "visualizations", - "scope": "common", + "scope": "public", "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" + "section": "def-public.Vis", + "text": "Vis" }, - " | undefined, timeRange?: ", + " | undefined, timeFilter?: ", { "pluginId": "data", - "scope": "common", + "scope": "public", "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" }, " | undefined) => Promise<", { @@ -4567,8 +4590,8 @@ "pluginId": "visualizations", "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-common.XYConfiguration", - "text": "XYConfiguration" + "section": "def-common.Configuration", + "text": "Configuration" }, "> | null> | undefined) | undefined" ], @@ -4581,17 +4604,17 @@ "id": "def-public.VisTypeDefinition.navigateToLens.$1", "type": "Object", "tags": [], - "label": "params", + "label": "vis", "description": [], "signature": [ { "pluginId": "visualizations", - "scope": "common", + "scope": "public", "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" + "section": "def-public.Vis", + "text": "Vis" }, - " | undefined" + " | undefined" ], "path": "src/plugins/visualizations/public/vis_types/types.ts", "deprecated": false, @@ -4603,15 +4626,94 @@ "id": "def-public.VisTypeDefinition.navigateToLens.$2", "type": "Object", "tags": [], - "label": "timeRange", + "label": "timeFilter", "description": [], "signature": [ { "pluginId": "data", - "scope": "common", + "scope": "public", + "docId": "kibDataQueryPluginApi", + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/public/vis_types/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisTypeDefinition.getExpressionVariables", + "type": "Function", + "tags": [], + "label": "getExpressionVariables", + "description": [ + "\nIf given, it will provide variables for expression params.\nEvery visualization that wants to add variables for expression params should have this method." + ], + "signature": [ + "((vis?: ", + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.Vis", + "text": "Vis" + }, + " | undefined, timeFilter?: ", + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataQueryPluginApi", + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" + }, + " | undefined) => Promise>) | undefined" + ], + "path": "src/plugins/visualizations/public/vis_types/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisTypeDefinition.getExpressionVariables.$1", + "type": "Object", + "tags": [], + "label": "vis", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.Vis", + "text": "Vis" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/public/vis_types/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisTypeDefinition.getExpressionVariables.$2", + "type": "Object", + "tags": [], + "label": "timeFilter", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "public", "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "section": "def-public.TimefilterContract", + "text": "TimefilterContract" }, " | undefined" ], @@ -5639,13 +5741,13 @@ "misc": [ { "parentPluginId": "visualizations", - "id": "def-public.ACTION_CONVERT_TO_LENS", + "id": "def-public.ACTION_CONVERT_AGG_BASED_TO_LENS", "type": "string", "tags": [], - "label": "ACTION_CONVERT_TO_LENS", + "label": "ACTION_CONVERT_AGG_BASED_TO_LENS", "description": [], "signature": [ - "\"ACTION_CONVERT_TO_LENS\"" + "\"ACTION_CONVERT_AGG_BASED_TO_LENS\"" ], "path": "src/plugins/visualizations/public/triggers/index.ts", "deprecated": false, @@ -5654,17 +5756,47 @@ }, { "parentPluginId": "visualizations", - "id": "def-public.DEFAULT_LEGEND_SIZE", + "id": "def-public.ACTION_CONVERT_TO_LENS", "type": "string", "tags": [], - "label": "DEFAULT_LEGEND_SIZE", + "label": "ACTION_CONVERT_TO_LENS", "description": [], "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.LegendSize", + "\"ACTION_CONVERT_TO_LENS\"" + ], + "path": "src/plugins/visualizations/public/triggers/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-public.AGG_BASED_VISUALIZATION_TRIGGER", + "type": "string", + "tags": [], + "label": "AGG_BASED_VISUALIZATION_TRIGGER", + "description": [], + "signature": [ + "\"AGG_BASED_VISUALIZATION_TRIGGER\"" + ], + "path": "src/plugins/visualizations/public/triggers/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-public.DEFAULT_LEGEND_SIZE", + "type": "string", + "tags": [], + "label": "DEFAULT_LEGEND_SIZE", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LegendSize", "text": "LegendSize" }, ".MEDIUM" @@ -5804,6 +5936,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.SchemaConfig", + "type": "Type", + "tags": [], + "label": "SchemaConfig", + "description": [], + "signature": [ + "{ [Agg in Aggs]: ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.GenericSchemaConfig", + "text": "GenericSchemaConfig" + }, + "; }[Aggs]" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-public.VisToExpressionAst", @@ -6383,6 +6538,23 @@ } ], "objects": [ + { + "parentPluginId": "visualizations", + "id": "def-public.convertToLensModule", + "type": "Object", + "tags": [], + "label": "convertToLensModule", + "description": [], + "signature": [ + "Promise" + ], + "path": "src/plugins/visualizations/public/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-public.LegendSizeToPixels", @@ -6679,6 +6851,69 @@ "common": { "classes": [], "functions": [ + { + "parentPluginId": "visualizations", + "id": "def-common.convertToSchemaConfig", + "type": "Function", + "tags": [], + "label": "convertToSchemaConfig", + "description": [], + "signature": [ + "(agg: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + }, + ") => ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SchemaConfig", + "text": "SchemaConfig" + }, + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.METRIC_TYPES", + "text": "METRIC_TYPES" + }, + ">" + ], + "path": "src/plugins/visualizations/common/vis_schemas.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.convertToSchemaConfig.$1", + "type": "Object", + "tags": [], + "label": "agg", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggConfig", + "text": "AggConfig" + } + ], + "path": "src/plugins/visualizations/common/vis_schemas.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.findAccessorOrFail", @@ -7080,6 +7315,54 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.getIndexPatternIds", + "type": "Function", + "tags": [], + "label": "getIndexPatternIds", + "description": [], + "signature": [ + "(layers: ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Layer", + "text": "Layer" + }, + "[]) => string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.getIndexPatternIds.$1", + "type": "Array", + "tags": [], + "label": "layers", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Layer", + "text": "Layer" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.isAnnotationsLayer", @@ -7136,6 +7419,78 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.isFieldValid", + "type": "Function", + "tags": [], + "label": "isFieldValid", + "description": [], + "signature": [ + "(field: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + " | undefined, aggregation: ", + "SupportedMetric", + ") => field is ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.isFieldValid.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.isFieldValid.$2", + "type": "CompoundType", + "tags": [], + "label": "aggregation", + "description": [], + "signature": [ + "SupportedMetric" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.isVisDimension", @@ -7884,6 +8239,73 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState", + "type": "Interface", + "tags": [], + "label": "ColumnState", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState.columnId", + "type": "string", + "tags": [], + "label": "columnId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState.summaryRow", + "type": "CompoundType", + "tags": [], + "label": "summaryRow", + "description": [], + "signature": [ + "\"min\" | \"none\" | \"max\" | \"count\" | \"sum\" | \"avg\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState.alignment", + "type": "CompoundType", + "tags": [], + "label": "alignment", + "description": [], + "signature": [ + "\"left\" | \"right\" | \"center\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState.collapseFn", + "type": "string", + "tags": [], + "label": "collapseFn", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.ColumnWithReferences", @@ -8227,6 +8649,9 @@ "tags": [], "label": "label", "description": [], + "signature": [ + "string | undefined" + ], "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", "deprecated": false, "trackAdoption": false @@ -8402,6 +8827,131 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig", + "type": "Interface", + "tags": [], + "label": "GenericSchemaConfig", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.GenericSchemaConfig", + "text": "GenericSchemaConfig" + }, + "" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.accessor", + "type": "number", + "tags": [], + "label": "accessor", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.format", + "type": "Object", + "tags": [], + "label": "format", + "description": [], + "signature": [ + "{ id?: string | undefined; params?: ", + "SerializableRecord", + " | undefined; }" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.params", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "SchemaConfigParams" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.aggType", + "type": "Uncategorized", + "tags": [], + "label": "aggType", + "description": [], + "signature": [ + "Agg" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.aggId", + "type": "string", + "tags": [], + "label": "aggId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericSchemaConfig.aggParams", + "type": "Uncategorized", + "tags": [], + "label": "aggParams", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsMapping", + "text": "AggParamsMapping" + }, + "[Agg] | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.LabelsOrientationConfig", @@ -8825,6 +9375,20 @@ "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NavigateToLensContext.indexPatternIds", + "type": "Array", + "tags": [], + "label": "indexPatternIds", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8868,6 +9432,42 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.PagingState", + "type": "Interface", + "tags": [], + "label": "PagingState", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.PagingState.size", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.PagingState.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.PercentileParams", @@ -9333,21 +9933,63 @@ }, " | undefined; }" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData.savedSearchId", + "type": "string", + "tags": [], + "label": "savedSearchId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SortingState", + "type": "Interface", + "tags": [], + "label": "SortingState", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.SortingState.columnId", + "type": "string", + "tags": [], + "label": "columnId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData.savedSearchId", - "type": "string", + "id": "def-common.SortingState.direction", + "type": "CompoundType", "tags": [], - "label": "savedSearchId", + "label": "direction", "description": [], "signature": [ - "string | undefined" + "\"none\" | \"asc\" | \"desc\"" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false } @@ -9399,6 +10041,164 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration", + "type": "Interface", + "tags": [], + "label": "TableVisConfiguration", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnState", + "text": "ColumnState" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"data\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.sorting", + "type": "Object", + "tags": [], + "label": "sorting", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SortingState", + "text": "SortingState" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.rowHeight", + "type": "CompoundType", + "tags": [], + "label": "rowHeight", + "description": [], + "signature": [ + "\"auto\" | \"custom\" | \"single\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.headerRowHeight", + "type": "CompoundType", + "tags": [], + "label": "headerRowHeight", + "description": [], + "signature": [ + "\"auto\" | \"custom\" | \"single\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.rowHeightLines", + "type": "number", + "tags": [], + "label": "rowHeightLines", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.headerRowHeightLines", + "type": "number", + "tags": [], + "label": "headerRowHeightLines", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TableVisConfiguration.paging", + "type": "Object", + "tags": [], + "label": "paging", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PagingState", + "text": "PagingState" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.TermsParams", @@ -10689,6 +11489,39 @@ } ], "misc": [ + { + "parentPluginId": "visualizations", + "id": "def-common.AggBasedColumn", + "type": "Type", + "tags": [], + "label": "AggBasedColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.GenericColumnWithMeta", + "text": "GenericColumnWithMeta" + }, + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Column", + "text": "Column" + }, + ", ", + "Meta", + "> | ", + "BucketColumn" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.AnyColumnWithReferences", @@ -11022,7 +11855,14 @@ "label": "ColumnWithMeta", "description": [], "signature": [ - "Col & (Meta extends undefined ? undefined : { meta: Meta; })" + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Column", + "text": "Column" + }, + " & { meta: unknown; }" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, @@ -11043,6 +11883,14 @@ "docId": "kibVisualizationsPluginApi", "section": "def-common.XYConfiguration", "text": "XYConfiguration" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.TableVisConfiguration", + "text": "TableVisConfiguration" } ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", @@ -11434,6 +12282,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.GenericColumnWithMeta", + "type": "Type", + "tags": [], + "label": "GenericColumnWithMeta", + "description": [], + "signature": [ + "Col & (Meta extends undefined ? undefined : { meta: Meta; })" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.LastValueColumn", @@ -11782,6 +12645,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeMode", + "type": "Type", + "tags": [], + "label": "RangeMode", + "description": [], + "signature": [ + "\"range\" | \"histogram\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.SavedVisState", @@ -11799,6 +12677,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.SchemaConfig", + "type": "Type", + "tags": [], + "label": "SchemaConfig", + "description": [], + "signature": [ + "{ [Agg in Aggs]: ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.GenericSchemaConfig", + "text": "GenericSchemaConfig" + }, + "; }[Aggs]" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.SeriesType", @@ -11961,6 +12862,35 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.SupportedAggregation", + "type": "Type", + "tags": [], + "label": "SupportedAggregation", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.METRIC_TYPES", + "text": "METRIC_TYPES" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BUCKET_TYPES", + "text": "BUCKET_TYPES" + } + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.TermsColumn", @@ -12150,6 +13080,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.RANGE_MODES", + "type": "Object", + "tags": [], + "label": "RANGE_MODES", + "description": [], + "signature": [ + "{ readonly Range: \"range\"; readonly Histogram: \"histogram\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.SeriesTypes", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 81c4149912c26..c4ea2e6c61e76 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-09-26 +date: 2022-09-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 631 | 12 | 602 | 14 | +| 679 | 12 | 649 | 18 | ## Client From 67a07e8b69d04e206ab1755045b2ca9fa95b6213 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 27 Sep 2022 08:54:44 +0200 Subject: [PATCH 049/172] Move client-side `CoreSystem` to packages (#141606) * create empty package * copy files to package * clean duplicate constant * add dependencies to bazel file * adapt usages * update readme * [CI] Auto-commit changed files from 'node scripts/generate codeowners' * uppdate README Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 2 + packages/BUILD.bazel | 2 + .../index.ts | 1 - .../core-root-browser-internal/BUILD.bazel | 172 ++++++++++++++++++ .../root/core-root-browser-internal/README.md | 5 + .../core-root-browser-internal/index.ts} | 4 +- .../core-root-browser-internal/jest.config.js | 13 ++ .../core-root-browser-internal/kibana.jsonc | 7 + .../core-root-browser-internal/package.json | 9 + .../src}/apm_resource_counter.ts | 0 .../src}/apm_system.test.ts | 0 .../src}/apm_system.ts | 0 .../src/core_system.scss | 0 .../src}/core_system.test.mocks.ts | 0 .../src}/core_system.test.ts | 0 .../src}/core_system.ts | 11 +- .../core-root-browser-internal/src}/events.ts | 3 - .../src}/fetch_optional_memory_info.test.ts | 0 .../src}/fetch_optional_memory_info.ts | 0 .../core-root-browser-internal/src/index.ts | 11 ++ .../src}/kbn_bootstrap.test.mocks.ts | 0 .../src}/kbn_bootstrap.test.ts | 0 .../src}/kbn_bootstrap.ts | 2 +- .../core-root-browser-internal/tsconfig.json | 18 ++ src/core/public/index.scss | 1 - src/core/public/index.ts | 4 +- yarn.lock | 8 + 28 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 packages/core/root/core-root-browser-internal/BUILD.bazel create mode 100644 packages/core/root/core-root-browser-internal/README.md rename packages/core/{mount-utils/core-mount-utils-browser-internal/src/consts.ts => root/core-root-browser-internal/index.ts} (77%) create mode 100644 packages/core/root/core-root-browser-internal/jest.config.js create mode 100644 packages/core/root/core-root-browser-internal/kibana.jsonc create mode 100644 packages/core/root/core-root-browser-internal/package.json rename {src/core/public => packages/core/root/core-root-browser-internal/src}/apm_resource_counter.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/apm_system.test.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/apm_system.ts (100%) rename src/core/public/_core.scss => packages/core/root/core-root-browser-internal/src/core_system.scss (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/core_system.test.mocks.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/core_system.test.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/core_system.ts (98%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/events.ts (97%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/fetch_optional_memory_info.test.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/fetch_optional_memory_info.ts (100%) create mode 100644 packages/core/root/core-root-browser-internal/src/index.ts rename {src/core/public => packages/core/root/core-root-browser-internal/src}/kbn_bootstrap.test.mocks.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/kbn_bootstrap.test.ts (100%) rename {src/core/public => packages/core/root/core-root-browser-internal/src}/kbn_bootstrap.ts (95%) create mode 100644 packages/core/root/core-root-browser-internal/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 480acad008f00..63c5c8047c6b4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -786,6 +786,7 @@ packages/core/preboot/core-preboot-server-internal @elastic/kibana-core packages/core/preboot/core-preboot-server-mocks @elastic/kibana-core packages/core/rendering/core-rendering-browser-internal @elastic/kibana-core packages/core/rendering/core-rendering-browser-mocks @elastic/kibana-core +packages/core/root/core-root-browser-internal @elastic/kibana-core packages/core/saved-objects/core-saved-objects-api-browser @elastic/kibana-core packages/core/saved-objects/core-saved-objects-api-server @elastic/kibana-core packages/core/saved-objects/core-saved-objects-api-server-internal @elastic/kibana-core diff --git a/package.json b/package.json index 3c439a01a9b3d..02511a00b56c3 100644 --- a/package.json +++ b/package.json @@ -260,6 +260,7 @@ "@kbn/core-preboot-server-mocks": "link:bazel-bin/packages/core/preboot/core-preboot-server-mocks", "@kbn/core-rendering-browser-internal": "link:bazel-bin/packages/core/rendering/core-rendering-browser-internal", "@kbn/core-rendering-browser-mocks": "link:bazel-bin/packages/core/rendering/core-rendering-browser-mocks", + "@kbn/core-root-browser-internal": "link:bazel-bin/packages/core/root/core-root-browser-internal", "@kbn/core-saved-objects-api-browser": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-browser", "@kbn/core-saved-objects-api-server": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-server", "@kbn/core-saved-objects-api-server-internal": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-server-internal", @@ -981,6 +982,7 @@ "@types/kbn__core-public-internal-base": "link:bazel-bin/packages/core/public/internal-base/npm_module_types", "@types/kbn__core-rendering-browser-internal": "link:bazel-bin/packages/core/rendering/core-rendering-browser-internal/npm_module_types", "@types/kbn__core-rendering-browser-mocks": "link:bazel-bin/packages/core/rendering/core-rendering-browser-mocks/npm_module_types", + "@types/kbn__core-root-browser-internal": "link:bazel-bin/packages/core/root/core-root-browser-internal/npm_module_types", "@types/kbn__core-saved-objects-api-browser": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-browser/npm_module_types", "@types/kbn__core-saved-objects-api-server": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-server/npm_module_types", "@types/kbn__core-saved-objects-api-server-internal": "link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-server-internal/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 48af6f87aef5d..62cc4b4ce9ad2 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -126,6 +126,7 @@ filegroup( "//packages/core/preboot/core-preboot-server-mocks:build", "//packages/core/rendering/core-rendering-browser-internal:build", "//packages/core/rendering/core-rendering-browser-mocks:build", + "//packages/core/root/core-root-browser-internal:build", "//packages/core/saved-objects/core-saved-objects-api-browser:build", "//packages/core/saved-objects/core-saved-objects-api-server:build", "//packages/core/saved-objects/core-saved-objects-api-server-internal:build", @@ -456,6 +457,7 @@ filegroup( "//packages/core/preboot/core-preboot-server-mocks:build_types", "//packages/core/rendering/core-rendering-browser-internal:build_types", "//packages/core/rendering/core-rendering-browser-mocks:build_types", + "//packages/core/root/core-root-browser-internal:build_types", "//packages/core/saved-objects/core-saved-objects-api-browser:build_types", "//packages/core/saved-objects/core-saved-objects-api-server:build_types", "//packages/core/saved-objects/core-saved-objects-api-server-internal:build_types", diff --git a/packages/core/mount-utils/core-mount-utils-browser-internal/index.ts b/packages/core/mount-utils/core-mount-utils-browser-internal/index.ts index 50a2868ddb711..192ca09f6e85b 100644 --- a/packages/core/mount-utils/core-mount-utils-browser-internal/index.ts +++ b/packages/core/mount-utils/core-mount-utils-browser-internal/index.ts @@ -7,4 +7,3 @@ */ export { MountWrapper, mountReactNode } from './src/mount'; -export { KBN_LOAD_MARKS } from './src/consts'; diff --git a/packages/core/root/core-root-browser-internal/BUILD.bazel b/packages/core/root/core-root-browser-internal/BUILD.bazel new file mode 100644 index 0000000000000..d0d5e786d7867 --- /dev/null +++ b/packages/core/root/core-root-browser-internal/BUILD.bazel @@ -0,0 +1,172 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-root-browser-internal" +PKG_REQUIRE_NAME = "@kbn/core-root-browser-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + "**/*.scss", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//rxjs", + "@npm//@elastic/apm-rum", + "//packages/kbn-std", + "//packages/kbn-i18n", + "//packages/kbn-ebt-tools", + "//packages/core/application/core-application-browser-internal", + "//packages/core/injected-metadata/core-injected-metadata-browser-internal", + "//packages/core/doc-links/core-doc-links-browser-internal", + "//packages/core/theme/core-theme-browser-internal", + "//packages/core/analytics/core-analytics-browser-internal", + "//packages/core/i18n/core-i18n-browser-internal", + "//packages/core/execution-context/core-execution-context-browser-internal", + "//packages/core/fatal-errors/core-fatal-errors-browser-internal", + "//packages/core/http/core-http-browser-internal", + "//packages/core/ui-settings/core-ui-settings-browser-internal", + "//packages/core/deprecations/core-deprecations-browser-internal", + "//packages/core/integrations/core-integrations-browser-internal", + "//packages/core/overlays/core-overlays-browser-internal", + "//packages/core/saved-objects/core-saved-objects-browser-internal", + "//packages/core/notifications/core-notifications-browser-internal", + "//packages/core/chrome/core-chrome-browser-internal", + "//packages/core/rendering/core-rendering-browser-internal", + "//packages/core/apps/core-apps-browser-internal", + "//packages/core/lifecycle/core-lifecycle-browser-internal", + "//packages/core/plugins/core-plugins-browser-internal", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//rxjs", + "@npm//@elastic/apm-rum", + "//packages/kbn-std:npm_module_types", + "//packages/kbn-i18n:npm_module_types", + "//packages/kbn-ebt-tools:npm_module_types", + "//packages/core/execution-context/core-execution-context-browser:npm_module_types", + "//packages/core/application/core-application-browser-internal:npm_module_types", + "//packages/core/base/core-base-browser-internal:npm_module_types", + "//packages/core/injected-metadata/core-injected-metadata-browser-internal:npm_module_types", + "//packages/core/doc-links/core-doc-links-browser-internal:npm_module_types", + "//packages/core/theme/core-theme-browser-internal:npm_module_types", + "//packages/core/analytics/core-analytics-browser:npm_module_types", + "//packages/core/analytics/core-analytics-browser-internal:npm_module_types", + "//packages/core/i18n/core-i18n-browser-internal:npm_module_types", + "//packages/core/execution-context/core-execution-context-browser-internal:npm_module_types", + "//packages/core/fatal-errors/core-fatal-errors-browser:npm_module_types", + "//packages/core/fatal-errors/core-fatal-errors-browser-internal:npm_module_types", + "//packages/core/http/core-http-browser-internal:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-browser-internal:npm_module_types", + "//packages/core/deprecations/core-deprecations-browser-internal:npm_module_types", + "//packages/core/integrations/core-integrations-browser-internal:npm_module_types", + "//packages/core/overlays/core-overlays-browser-internal:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-browser-internal:npm_module_types", + "//packages/core/notifications/core-notifications-browser-internal:npm_module_types", + "//packages/core/chrome/core-chrome-browser-internal:npm_module_types", + "//packages/core/rendering/core-rendering-browser-internal:npm_module_types", + "//packages/core/apps/core-apps-browser-internal:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-browser-internal:npm_module_types", + "//packages/core/plugins/core-plugins-browser-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, + additional_args = [ + "--copy-files", + "--quiet" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/root/core-root-browser-internal/README.md b/packages/core/root/core-root-browser-internal/README.md new file mode 100644 index 0000000000000..522df736da29c --- /dev/null +++ b/packages/core/root/core-root-browser-internal/README.md @@ -0,0 +1,5 @@ +# @kbn/core-root-browser-internal + +This package exposes the root components required to start the Core system on the browser side. +- `CoreSystem` +- `__kbnBootstrap__` diff --git a/packages/core/mount-utils/core-mount-utils-browser-internal/src/consts.ts b/packages/core/root/core-root-browser-internal/index.ts similarity index 77% rename from packages/core/mount-utils/core-mount-utils-browser-internal/src/consts.ts rename to packages/core/root/core-root-browser-internal/index.ts index 8372eafec8147..032a4bd1eb8b1 100644 --- a/packages/core/mount-utils/core-mount-utils-browser-internal/src/consts.ts +++ b/packages/core/root/core-root-browser-internal/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -/** @internal */ -export const KBN_LOAD_MARKS = 'kbnLoad'; +export { CoreSystem, __kbnBootstrap__ } from './src'; +export type { CoreSystemParams } from './src'; diff --git a/packages/core/root/core-root-browser-internal/jest.config.js b/packages/core/root/core-root-browser-internal/jest.config.js new file mode 100644 index 0000000000000..4f81bcdaaf118 --- /dev/null +++ b/packages/core/root/core-root-browser-internal/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/core/root/core-root-browser-internal'], +}; diff --git a/packages/core/root/core-root-browser-internal/kibana.jsonc b/packages/core/root/core-root-browser-internal/kibana.jsonc new file mode 100644 index 0000000000000..0dd7d5ae6beb4 --- /dev/null +++ b/packages/core/root/core-root-browser-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-root-browser-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/root/core-root-browser-internal/package.json b/packages/core/root/core-root-browser-internal/package.json new file mode 100644 index 0000000000000..30a34c02fc4eb --- /dev/null +++ b/packages/core/root/core-root-browser-internal/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-root-browser-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/public/apm_resource_counter.ts b/packages/core/root/core-root-browser-internal/src/apm_resource_counter.ts similarity index 100% rename from src/core/public/apm_resource_counter.ts rename to packages/core/root/core-root-browser-internal/src/apm_resource_counter.ts diff --git a/src/core/public/apm_system.test.ts b/packages/core/root/core-root-browser-internal/src/apm_system.test.ts similarity index 100% rename from src/core/public/apm_system.test.ts rename to packages/core/root/core-root-browser-internal/src/apm_system.test.ts diff --git a/src/core/public/apm_system.ts b/packages/core/root/core-root-browser-internal/src/apm_system.ts similarity index 100% rename from src/core/public/apm_system.ts rename to packages/core/root/core-root-browser-internal/src/apm_system.ts diff --git a/src/core/public/_core.scss b/packages/core/root/core-root-browser-internal/src/core_system.scss similarity index 100% rename from src/core/public/_core.scss rename to packages/core/root/core-root-browser-internal/src/core_system.scss diff --git a/src/core/public/core_system.test.mocks.ts b/packages/core/root/core-root-browser-internal/src/core_system.test.mocks.ts similarity index 100% rename from src/core/public/core_system.test.mocks.ts rename to packages/core/root/core-root-browser-internal/src/core_system.test.mocks.ts diff --git a/src/core/public/core_system.test.ts b/packages/core/root/core-root-browser-internal/src/core_system.test.ts similarity index 100% rename from src/core/public/core_system.test.ts rename to packages/core/root/core-root-browser-internal/src/core_system.test.ts diff --git a/src/core/public/core_system.ts b/packages/core/root/core-root-browser-internal/src/core_system.ts similarity index 98% rename from src/core/public/core_system.ts rename to packages/core/root/core-root-browser-internal/src/core_system.ts index 4381cfdd2abf9..b3eae041b785d 100644 --- a/src/core/public/core_system.ts +++ b/packages/core/root/core-root-browser-internal/src/core_system.ts @@ -26,7 +26,6 @@ import { DeprecationsService } from '@kbn/core-deprecations-browser-internal'; import { IntegrationsService } from '@kbn/core-integrations-browser-internal'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { OverlayService } from '@kbn/core-overlays-browser-internal'; -import { KBN_LOAD_MARKS } from '@kbn/core-mount-utils-browser-internal'; import { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; import { NotificationsService } from '@kbn/core-notifications-browser-internal'; import { ChromeService } from '@kbn/core-chrome-browser-internal'; @@ -35,6 +34,7 @@ import { RenderingService } from '@kbn/core-rendering-browser-internal'; import { CoreAppsService } from '@kbn/core-apps-browser-internal'; import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-browser-internal'; import { PluginsService } from '@kbn/core-plugins-browser-internal'; +import { KBN_LOAD_MARKS } from './events'; import { fetchOptionalMemoryInfo } from './fetch_optional_memory_info'; import { @@ -47,7 +47,12 @@ import { LOAD_START, } from './events'; -interface Params { +import './core_system.scss'; + +/** + * @internal + */ +export interface CoreSystemParams { rootDomElement: HTMLElement; browserSupportsCsp: boolean; injectedMetadata: InjectedMetadataParams['injectedMetadata']; @@ -96,7 +101,7 @@ export class CoreSystem { private readonly executionContext: ExecutionContextService; private fatalErrorsSetup: FatalErrorsSetup | null = null; - constructor(params: Params) { + constructor(params: CoreSystemParams) { const { rootDomElement, browserSupportsCsp, injectedMetadata } = params; this.rootDomElement = rootDomElement; diff --git a/src/core/public/events.ts b/packages/core/root/core-root-browser-internal/src/events.ts similarity index 97% rename from src/core/public/events.ts rename to packages/core/root/core-root-browser-internal/src/events.ts index e2f5d48ddfe3d..50337778c8c3c 100644 --- a/src/core/public/events.ts +++ b/packages/core/root/core-root-browser-internal/src/events.ts @@ -6,11 +6,8 @@ * Side Public License, v 1. */ -/** @internal */ export const KBN_LOAD_MARKS = 'kbnLoad'; - export const KIBANA_LOADED_EVENT = 'kibana_loaded'; - export const LOAD_START = 'load_started'; export const LOAD_BOOTSTRAP_START = 'bootstrap_started'; export const LOAD_CORE_CREATED = 'core_created'; diff --git a/src/core/public/fetch_optional_memory_info.test.ts b/packages/core/root/core-root-browser-internal/src/fetch_optional_memory_info.test.ts similarity index 100% rename from src/core/public/fetch_optional_memory_info.test.ts rename to packages/core/root/core-root-browser-internal/src/fetch_optional_memory_info.test.ts diff --git a/src/core/public/fetch_optional_memory_info.ts b/packages/core/root/core-root-browser-internal/src/fetch_optional_memory_info.ts similarity index 100% rename from src/core/public/fetch_optional_memory_info.ts rename to packages/core/root/core-root-browser-internal/src/fetch_optional_memory_info.ts diff --git a/packages/core/root/core-root-browser-internal/src/index.ts b/packages/core/root/core-root-browser-internal/src/index.ts new file mode 100644 index 0000000000000..663af54e573eb --- /dev/null +++ b/packages/core/root/core-root-browser-internal/src/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { __kbnBootstrap__ } from './kbn_bootstrap'; +export { CoreSystem } from './core_system'; +export type { CoreSystemParams } from './core_system'; diff --git a/src/core/public/kbn_bootstrap.test.mocks.ts b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.test.mocks.ts similarity index 100% rename from src/core/public/kbn_bootstrap.test.mocks.ts rename to packages/core/root/core-root-browser-internal/src/kbn_bootstrap.test.mocks.ts diff --git a/src/core/public/kbn_bootstrap.test.ts b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.test.ts similarity index 100% rename from src/core/public/kbn_bootstrap.test.ts rename to packages/core/root/core-root-browser-internal/src/kbn_bootstrap.test.ts diff --git a/src/core/public/kbn_bootstrap.ts b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts similarity index 95% rename from src/core/public/kbn_bootstrap.ts rename to packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts index 0359f9e4d6520..c1ca8cb752d2d 100644 --- a/src/core/public/kbn_bootstrap.ts +++ b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import { KBN_LOAD_MARKS } from '@kbn/core-mount-utils-browser-internal'; +import { KBN_LOAD_MARKS } from './events'; import { CoreSystem } from './core_system'; import { ApmSystem } from './apm_system'; diff --git a/packages/core/root/core-root-browser-internal/tsconfig.json b/packages/core/root/core-root-browser-internal/tsconfig.json new file mode 100644 index 0000000000000..4283cbe1b760b --- /dev/null +++ b/packages/core/root/core-root-browser-internal/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/src/core/public/index.scss b/src/core/public/index.scss index 4b034af74fa1b..c056b0f851801 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,4 +1,3 @@ @import './variables'; @import './mixins'; -@import './core'; @import './styles/index'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index ff666e0f3a187..643bc705814a0 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -226,6 +226,6 @@ export type { export type { CoreSetup, CoreStart, StartServicesAccessor } from '@kbn/core-lifecycle-browser'; -export type { CoreSystem } from './core_system'; +export type { CoreSystem } from '@kbn/core-root-browser-internal'; -export { __kbnBootstrap__ } from './kbn_bootstrap'; +export { __kbnBootstrap__ } from '@kbn/core-root-browser-internal'; diff --git a/yarn.lock b/yarn.lock index 23369504cad0a..0752e8a1611c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3142,6 +3142,10 @@ version "0.0.0" uid "" +"@kbn/core-root-browser-internal@link:bazel-bin/packages/core/root/core-root-browser-internal": + version "0.0.0" + uid "" + "@kbn/core-saved-objects-api-browser@link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-browser": version "0.0.0" uid "" @@ -7263,6 +7267,10 @@ version "0.0.0" uid "" +"@types/kbn__core-root-browser-internal@link:bazel-bin/packages/core/root/core-root-browser-internal/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-saved-objects-api-browser@link:bazel-bin/packages/core/saved-objects/core-saved-objects-api-browser/npm_module_types": version "0.0.0" uid "" From 76b26246100732f0d3f79aac461d037a06c41761 Mon Sep 17 00:00:00 2001 From: Julian Gernun Date: Tue, 27 Sep 2022 09:24:52 +0200 Subject: [PATCH 050/172] 141189 alerts table performance (#141385) * retrieve data without loops and work with columns instead of ids * not found column when category doesnt exist --- .../hooks/use_columns/toggle_column.ts | 44 +++++----- .../hooks/use_columns/use_columns.ts | 83 ++++++++++++------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts index f7f181c60b024..ae7bceed0ac80 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts @@ -7,57 +7,59 @@ import { EuiDataGridColumn } from '@elastic/eui'; -const remove = ({ columnIds, index }: { columnIds: string[]; index: number }) => { - return [...columnIds.slice(0, index), ...columnIds.slice(index + 1)]; +const remove = ({ columns, index }: { columns: EuiDataGridColumn[]; index: number }) => { + return [...columns.slice(0, index), ...columns.slice(index + 1)]; }; const insert = ({ - columnId, - columnIds, + column, + columns, defaultColumns, }: { - columnId: string; - columnIds: string[]; + column: EuiDataGridColumn; + columns: EuiDataGridColumn[]; defaultColumns: EuiDataGridColumn[]; }) => { const defaultIndex = defaultColumns.findIndex( - (column: EuiDataGridColumn) => column.id === columnId + (defaultColumn: EuiDataGridColumn) => defaultColumn.id === column.id ); const isInDefaultConfig = defaultIndex >= 0; // if the column isn't shown but it's part of the default config // insert into the same position as in the default config if (isInDefaultConfig) { - return [...columnIds.slice(0, defaultIndex), columnId, ...columnIds.slice(defaultIndex)]; + return [...columns.slice(0, defaultIndex), column, ...columns.slice(defaultIndex)]; } // if the column isn't shown and it's not part of the default config // push it into the second position. Behaviour copied by t_grid, security // does this to insert right after the timestamp column - return [columnIds[0], columnId, ...columnIds.slice(1)]; + return [columns[0], column, ...columns.slice(1)]; }; /** - * @param param.columnId id of the column to be removed/inserted - * @param param.columnIds Current array of columnIds in the grid - * @param param.defaultColumns Those initial columns set up in the configuration before being modified by the user - * @returns the new list of columns to be shown + * @param param.column column to be removed/inserted + * @param param.columns current array of columns in the grid + * @param param.defaultColumns Initial columns set up in the configuration before being modified by the user + * @returns the new list of columns */ export const toggleColumn = ({ - columnId, - columnIds, + column, + columns, defaultColumns, }: { - columnId: string; - columnIds: string[]; + column: EuiDataGridColumn; + columns: EuiDataGridColumn[]; defaultColumns: EuiDataGridColumn[]; -}): string[] => { - const currentIndex = columnIds.indexOf(columnId); +}): EuiDataGridColumn[] => { + const currentIndex = columns.findIndex( + (currentColumn: EuiDataGridColumn) => currentColumn.id === column.id + ); const isVisible = currentIndex >= 0; if (isVisible) { - return remove({ columnIds, index: currentIndex }); + return remove({ columns, index: currentIndex }); } - return insert({ defaultColumns, columnId, columnIds }); + return insert({ defaultColumns, column, columns }); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts index eb6f6adad1cc5..f10807824e8fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts @@ -7,12 +7,12 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { AlertConsumers } from '@kbn/rule-data-utils'; import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common'; import { useCallback, useEffect, useState } from 'react'; +import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertsTableStorage } from '../../alerts_table_state'; -import { useFetchBrowserFieldCapabilities } from '../use_fetch_browser_fields_capabilities'; import { toggleColumn } from './toggle_column'; +import { useFetchBrowserFieldCapabilities } from '../use_fetch_browser_fields_capabilities'; interface UseColumnsArgs { featureIds: AlertConsumers[]; @@ -29,43 +29,62 @@ const fieldTypeToDataGridColumnTypeMapper = (fieldType: string | undefined) => { return fieldType; }; +const getFieldCategoryFromColumnId = (columnId: string): string => { + const fieldName = columnId.split('.'); + + if (fieldName.length === 1) { + return 'base'; + } + + return fieldName[0]; +}; + /** * EUI Data Grid expects the columns to have a property 'schema' defined for proper sorting * this schema as its own types as can be check out in the docs so we add it here manually * https://eui.elastic.co/#/tabular-content/data-grid-schema-columns */ const euiColumnFactory = ( - column: EuiDataGridColumn, - browserFields: BrowserFields + columnId: string, + browserFields: BrowserFields, + defaultColumns: EuiDataGridColumn[] ): EuiDataGridColumn => { - const browserFieldsProps = getBrowserFieldProps(column.id, browserFields); + const defaultColumn = getColumnByColumnId(defaultColumns, columnId); + const column = defaultColumn ? defaultColumn : { id: columnId }; + + const browserFieldsProps = getBrowserFieldProps(columnId, browserFields); return { ...column, schema: fieldTypeToDataGridColumnTypeMapper(browserFieldsProps.type), }; }; -/** - * Searches in browser fields object for a specific field - */ const getBrowserFieldProps = ( columnId: string, browserFields: BrowserFields ): Partial => { - for (const [, categoryDescriptor] of Object.entries(browserFields)) { - if (!categoryDescriptor.fields) { - continue; - } - - for (const [fieldName, fieldDescriptor] of Object.entries(categoryDescriptor.fields)) { - if (fieldName === columnId) { - return fieldDescriptor; - } - } + const notFoundSpecs = { type: 'string' }; + + if (!browserFields || Object.keys(browserFields).length === 0) { + return notFoundSpecs; + } + + const category = getFieldCategoryFromColumnId(columnId); + if (!browserFields[category]) { + return notFoundSpecs; + } + + const categorySpecs = browserFields[category].fields; + if (!categorySpecs) { + return notFoundSpecs; } - return { type: 'string' }; + + const fieldSpecs = categorySpecs[columnId]; + return fieldSpecs ? fieldSpecs : notFoundSpecs; }; +const isPopulatedColumn = (column: EuiDataGridColumn) => Boolean(column.schema); + /** * @param columns Columns to be considered in the alerts table * @param browserFields constant object with all field capabilities @@ -73,10 +92,13 @@ const getBrowserFieldProps = ( */ const populateColumns = ( columns: EuiDataGridColumn[], - browserFields: BrowserFields + browserFields: BrowserFields, + defaultColumns: EuiDataGridColumn[] ): EuiDataGridColumn[] => { return columns.map((column: EuiDataGridColumn) => { - return euiColumnFactory(column, browserFields); + return isPopulatedColumn(column) + ? column + : euiColumnFactory(column.id, browserFields, defaultColumns); }); }; @@ -128,10 +150,10 @@ export const useColumns = ({ useEffect(() => { if (isBrowserFieldDataLoading !== false || isColumnsPopulated) return; - const populatedColumns = populateColumns(columns, browserFields); + const populatedColumns = populateColumns(columns, browserFields, defaultColumns); setColumnsPopulated(true); setColumns(populatedColumns); - }, [browserFields, columns, isBrowserFieldDataLoading, isColumnsPopulated]); + }, [browserFields, columns, defaultColumns, isBrowserFieldDataLoading, isColumnsPopulated]); const setColumnsAndSave = useCallback( (newColumns: EuiDataGridColumn[]) => { @@ -156,15 +178,12 @@ export const useColumns = ({ const onToggleColumn = useCallback( (columnId: string): void => { - const newColumnIds = toggleColumn({ - columnId, - columnIds: getColumnIds(columns), - defaultColumns, - }); + const column = euiColumnFactory(columnId, browserFields, defaultColumns); - const newColumns = newColumnIds.map((_columnId: string) => { - const column = getColumnByColumnId(defaultColumns, _columnId); - return euiColumnFactory(column ? column : { id: _columnId }, browserFields); + const newColumns = toggleColumn({ + column, + columns, + defaultColumns, }); setColumnsAndSave(newColumns); @@ -173,7 +192,7 @@ export const useColumns = ({ ); const onResetColumns = useCallback(() => { - const populatedDefaultColumns = populateColumns(defaultColumns, browserFields); + const populatedDefaultColumns = populateColumns(defaultColumns, browserFields, defaultColumns); setColumnsAndSave(populatedDefaultColumns); }, [browserFields, defaultColumns, setColumnsAndSave]); From 8ce172d25ca251097c67b5ef4e0193d69e55f3e6 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 27 Sep 2022 10:02:08 +0200 Subject: [PATCH 051/172] [Fleet] Updated bulk action api to return actionId instead of agents success for consistency (#141757) * bulk action api returns actionId instead of agents success * updated action modals * fixed tests * updated openapi * fix typo * removed unused translations --- .../plugins/fleet/common/openapi/bundled.json | 68 ++----------------- .../plugins/fleet/common/openapi/bundled.yaml | 40 ----------- .../openapi/paths/agents@bulk_reassign.yaml | 10 --- .../openapi/paths/agents@bulk_unenroll.yaml | 10 --- .../paths/agents@bulk_update_tags.yaml | 10 --- .../openapi/paths/agents@bulk_upgrade.yaml | 10 --- .../fleet/common/types/rest_spec/agent.ts | 10 +-- .../agent_reassign_policy_modal/index.tsx | 11 +-- .../components/agent_unenroll_modal/index.tsx | 13 +--- .../components/agent_upgrade_modal/index.tsx | 51 ++------------ .../fleet/server/routes/agent/handlers.ts | 22 +----- .../server/routes/agent/unenroll_handler.ts | 14 +--- .../server/routes/agent/upgrade_handler.ts | 17 +---- .../server/services/agents/action_runner.ts | 23 +++---- .../fleet/server/services/agents/crud.test.ts | 36 +--------- .../fleet/server/services/agents/crud.ts | 28 -------- .../fleet/server/services/agents/reassign.ts | 13 +--- .../services/agents/reassign_action_runner.ts | 28 +++----- .../server/services/agents/unenroll.test.ts | 31 +-------- .../fleet/server/services/agents/unenroll.ts | 4 +- .../services/agents/unenroll_action_runner.ts | 15 ++-- .../services/agents/update_agent_tags.ts | 16 ++--- .../agents/update_agent_tags_action_runner.ts | 18 ++--- .../fleet/server/services/agents/upgrade.ts | 4 +- .../services/agents/upgrade_action_runner.ts | 27 ++------ .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../apis/agents/reassign.ts | 38 +---------- .../apis/agents/unenroll.ts | 21 +----- .../apis/agents/update_agent_tags.ts | 10 +-- .../apis/agents/upgrade.ts | 18 +---- 32 files changed, 87 insertions(+), 538 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 88f091ad98310..71b4ec04d4e98 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1254,23 +1254,8 @@ "type": "object", "properties": { "actionId": { - "type": "string", - "description": "action id when running in async mode (>10k agents)" + "type": "string" } - }, - "additionalProperties": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "error": { - "type": "string" - } - }, - "required": [ - "success" - ] } } } @@ -1849,23 +1834,8 @@ "type": "object", "properties": { "actionId": { - "type": "string", - "description": "action id when running in async mode (>10k agents)" + "type": "string" } - }, - "additionalProperties": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "error": { - "type": "string" - } - }, - "required": [ - "success" - ] } } } @@ -1931,23 +1901,8 @@ "type": "object", "properties": { "actionId": { - "type": "string", - "description": "action id when running in async mode (>10k agents)" + "type": "string" } - }, - "additionalProperties": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "error": { - "type": "string" - } - }, - "required": [ - "success" - ] } } } @@ -2020,23 +1975,8 @@ "type": "object", "properties": { "actionId": { - "type": "string", - "description": "action id when running in async mode (>10k agents)" + "type": "string" } - }, - "additionalProperties": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "error": { - "type": "string" - } - }, - "required": [ - "success" - ] } } } diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 86928fca0b2f4..92347809b0616 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -774,16 +774,6 @@ paths: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success '400': description: BAD REQUEST content: @@ -1145,16 +1135,6 @@ paths: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-reassign-agents parameters: - $ref: '#/components/parameters/kbn_xsrf' @@ -1195,16 +1175,6 @@ paths: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-unenroll-agents parameters: - $ref: '#/components/parameters/kbn_xsrf' @@ -1250,16 +1220,6 @@ paths: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-update-agent-tags parameters: - $ref: '#/components/parameters/kbn_xsrf' diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml index cd333e94e4750..17c4365f01e32 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml @@ -11,16 +11,6 @@ post: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-reassign-agents parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml index ff04eda0be8a8..f5e244f86f74b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml @@ -11,16 +11,6 @@ post: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-unenroll-agents parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml index 80643ebd3625e..2eb129e802179 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml @@ -11,16 +11,6 @@ post: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success operationId: bulk-update-agent-tags parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml index 1d9136353f01b..9a74054f492a7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml @@ -11,16 +11,6 @@ post: properties: actionId: type: string - description: action id when running in async mode (>10k agents) - additionalProperties: - type: object - properties: - success: - type: boolean - error: - type: string - required: - - success '400': description: BAD REQUEST content: diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index f1fbe4469de44..9a18613d95834 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -72,13 +72,9 @@ export interface PostBulkAgentUnenrollRequest { }; } -export type BulkAgentAction = Record< - Agent['id'], - { - success: boolean; - error?: string; - } -> & { actionId?: string }; +export interface BulkAgentAction { + actionId: string; +} export type PostBulkAgentUnenrollResponse = BulkAgentAction; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx index cae8b00fb6d3d..cb3f0d77eed34 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx @@ -82,20 +82,13 @@ export const AgentReassignAgentPolicyModal: React.FunctionComponent = ({ throw res.error; } setIsSubmitting(false); - const hasCompleted = isSingleAgent || Object.keys(res.data ?? {}).length > 0; const successMessage = i18n.translate( 'xpack.fleet.agentReassignPolicy.successSingleNotificationTitle', { - defaultMessage: 'Agent policy reassigned', + defaultMessage: 'Reassigning agent policy', } ); - const submittedMessage = i18n.translate( - 'xpack.fleet.agentReassignPolicy.submittedNotificationTitle', - { - defaultMessage: 'Agent policy reassign submitted', - } - ); - notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage); + notifications.toasts.addSuccess(successMessage); onClose(); } catch (error) { setIsSubmitting(false); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index 7c0c0136c3d09..72b1e00c1ed02 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -40,7 +40,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ async function onSubmit() { try { setIsSubmitting(true); - const { error, data } = isSingleAgent + const { error } = isSingleAgent ? await sendPostAgentUnenroll((agents[0] as Agent).id, { revoke: forceUnenroll, }) @@ -52,13 +52,6 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ throw error; } setIsSubmitting(false); - const hasCompleted = isSingleAgent || Object.keys(data ?? {}).length > 0; - const submittedMessage = i18n.translate( - 'xpack.fleet.unenrollAgents.submittedNotificationTitle', - { - defaultMessage: 'Agent(s) unenroll submitted', - } - ); if (forceUnenroll) { const successMessage = isSingleAgent ? i18n.translate('xpack.fleet.unenrollAgents.successForceSingleNotificationTitle', { @@ -67,7 +60,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ : i18n.translate('xpack.fleet.unenrollAgents.successForceMultiNotificationTitle', { defaultMessage: 'Agents unenrolled', }); - notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage); + notifications.toasts.addSuccess(successMessage); } else { const successMessage = isSingleAgent ? i18n.translate('xpack.fleet.unenrollAgents.successSingleNotificationTitle', { @@ -76,7 +69,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ : i18n.translate('xpack.fleet.unenrollAgents.successMultiNotificationTitle', { defaultMessage: 'Unenrolling agents', }); - notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage); + notifications.toasts.addSuccess(successMessage); } onClose(); } catch (error) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index c1c45f47ff02f..d7d376e83e316 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -162,7 +162,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent { - ++acc.total; - ++acc[result.success ? 'success' : 'error']; - return acc; - }, - { - total: 0, - success: 0, - error: 0, - } - ); setIsSubmitting(false); - const hasCompleted = isSingleAgent || Object.keys(data ?? {}).length > 0; - const submittedMessage = i18n.translate( - 'xpack.fleet.upgradeAgents.submittedNotificationTitle', - { - defaultMessage: 'Agent(s) upgrade submitted', - } + notifications.toasts.addSuccess( + i18n.translate('xpack.fleet.upgradeAgents.successNotificationTitle', { + defaultMessage: 'Upgrading agent(s)', + }) ); - - if (!hasCompleted) { - notifications.toasts.addSuccess(submittedMessage); - } else if (counts.success === counts.total) { - notifications.toasts.addSuccess( - i18n.translate('xpack.fleet.upgradeAgents.successSingleNotificationTitle', { - defaultMessage: 'Upgrading {count, plural, one {# agent} other {# agents}}', - values: { count: isSingleAgent ? 1 : counts.total }, - }) - ); - } else if (counts.error === counts.total) { - notifications.toasts.addDanger( - i18n.translate('xpack.fleet.upgradeAgents.bulkResultAllErrorsNotificationTitle', { - defaultMessage: - 'Error upgrading {count, plural, one {agent} other {{count} agents} =true {all selected agents}}', - values: { count: isAllAgents || agentCount }, - }) - ); - } else { - notifications.toasts.addWarning({ - text: i18n.translate('xpack.fleet.upgradeAgents.bulkResultErrorResultsSummary', { - defaultMessage: - '{count} {count, plural, one {agent was} other {agents were}} not successful', - values: { count: counts.error }, - }), - }); - } onClose(); } catch (error) { setIsSubmitting(false); diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 1ae6fb81742d1..86f202381119f 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -25,8 +25,6 @@ import type { GetOneAgentResponse, GetAgentStatusResponse, PutAgentReassignResponse, - PostBulkAgentReassignResponse, - PostBulkUpdateAgentTagsResponse, GetAgentTagsResponse, GetAvailableVersionsResponse, GetActionStatusResponse, @@ -151,15 +149,7 @@ export const bulkUpdateAgentTagsHandler: RequestHandler< request.body.tagsToRemove ?? [] ); - const body = results.items.reduce((acc: any, so: any) => { - acc[so.id] = { - success: !so.error, - error: so.error?.message, - }; - return acc; - }, {}); - - return response.ok({ body: { ...body, actionId: results.actionId } }); + return response.ok({ body: { actionId: results.actionId } }); } catch (error) { return defaultFleetErrorHandler({ error, response }); } @@ -267,15 +257,7 @@ export const postBulkAgentsReassignHandler: RequestHandler< request.body.policy_id ); - const body = results.items.reduce((acc, so) => { - acc[so.id] = { - success: !so.error, - error: so.error?.message, - }; - return acc; - }, {}); - - return response.ok({ body: { ...body, actionId: results.actionId } }); + return response.ok({ body: { actionId: results.actionId } }); } catch (error) { return defaultFleetErrorHandler({ error, response }); } diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index db5a497a30869..740cf3a7b716c 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -8,10 +8,7 @@ import type { RequestHandler } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; -import type { - PostAgentUnenrollResponse, - PostBulkAgentUnenrollResponse, -} from '../../../common/types'; +import type { PostAgentUnenrollResponse } from '../../../common/types'; import type { PostAgentUnenrollRequestSchema, PostBulkAgentUnenrollRequestSchema, @@ -59,15 +56,8 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< force: request.body?.force, batchSize: request.body?.batchSize, }); - const body = results.items.reduce((acc, so) => { - acc[so.id] = { - success: !so.error, - error: so.error?.message, - }; - return acc; - }, {}); - return response.ok({ body: { ...body, actionId: results.actionId } }); + return response.ok({ body: { actionId: results.actionId } }); } catch (error) { return defaultFleetErrorHandler({ error, response }); } diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index 43a3987f29f60..a79edbaa36856 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -11,11 +11,7 @@ import type { TypeOf } from '@kbn/config-schema'; import semverCoerce from 'semver/functions/coerce'; import semverGt from 'semver/functions/gt'; -import type { - PostAgentUpgradeResponse, - PostBulkAgentUpgradeResponse, - GetCurrentUpgradesResponse, -} from '../../../common/types'; +import type { PostAgentUpgradeResponse, GetCurrentUpgradesResponse } from '../../../common/types'; import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; import * as AgentService from '../../services/agents'; import { appContextService } from '../../services'; @@ -142,15 +138,8 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< batchSize, }; const results = await AgentService.sendUpgradeAgentsActions(soClient, esClient, upgradeOptions); - const body = results.items.reduce((acc, so) => { - acc[so.id] = { - success: !so.error, - error: so.error?.message, - }; - return acc; - }, {}); - - return response.ok({ body: { ...body, actionId: results.actionId } }); + + return response.ok({ body: { actionId: results.actionId } }); } catch (error) { return defaultFleetErrorHandler({ error, response }); } diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index d46c88728e341..2d6e1cf9cbec4 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -14,7 +14,7 @@ import { isResponseError } from '@kbn/es-errors'; import moment from 'moment'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { appContextService } from '..'; import { SO_SEARCH_LIMIT } from '../../../common/constants'; @@ -65,7 +65,7 @@ export abstract class ActionRunner { protected abstract getTaskType(): string; - protected abstract processAgents(agents: Agent[]): Promise<{ items: BulkActionResult[] }>; + protected abstract processAgents(agents: Agent[]): Promise<{ actionId: string }>; /** * Common runner logic accross all agent bulk actions @@ -73,7 +73,7 @@ export abstract class ActionRunner { * On errors, starts a task with Task Manager to retry max 3 times * If the last batch was stored in state, retry continues from there (searchAfter) */ - public async runActionAsyncWithRetry(): Promise<{ items: BulkActionResult[]; actionId: string }> { + public async runActionAsyncWithRetry(): Promise<{ actionId: string }> { appContextService .getLogger() .info( @@ -131,7 +131,7 @@ export abstract class ActionRunner { }) ); - return { items: [], actionId: this.actionParams.actionId! }; + return { actionId: this.actionParams.actionId! }; } private async createCheckResultTask() { @@ -146,7 +146,7 @@ export abstract class ActionRunner { ); } - private async processBatch(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { + private async processBatch(agents: Agent[]): Promise<{ actionId: string }> { if (this.retryParams.retryCount) { try { const actions = await getAgentActions(this.esClient, this.actionParams!.actionId!); @@ -154,7 +154,7 @@ export abstract class ActionRunner { // skipping batch if there is already an action document present with last agent ids for (const action of actions) { if (action.agents?.[0] === agents[0].id) { - return { items: [] }; + return { actionId: this.actionParams.actionId! }; } } } catch (error) { @@ -165,7 +165,7 @@ export abstract class ActionRunner { return await this.processAgents(agents); } - async processAgentsInBatches(): Promise<{ items: BulkActionResult[] }> { + async processAgentsInBatches(): Promise<{ actionId: string }> { const start = Date.now(); const pitId = this.retryParams.pitId; @@ -188,10 +188,10 @@ export abstract class ActionRunner { appContextService .getLogger() .debug('currentAgents returned 0 hits, returning from bulk action query'); - return { items: [] }; // stop executing if there are no more results + return { actionId: this.actionParams.actionId! }; // stop executing if there are no more results } - let results = await this.processBatch(currentAgents); + await this.processBatch(currentAgents); let allAgentsProcessed = currentAgents.length; while (allAgentsProcessed < res.total) { @@ -205,8 +205,7 @@ export abstract class ActionRunner { .debug('currentAgents returned 0 hits, returning from bulk action query'); break; // stop executing if there are no more results } - const currentResults = await this.processBatch(currentAgents); - results = { items: results.items.concat(currentResults.items) }; + await this.processBatch(currentAgents); allAgentsProcessed += currentAgents.length; if (this.checkTaskId) { // updating check task with latest checkpoint (this.retryParams.searchAfter) @@ -220,6 +219,6 @@ export abstract class ActionRunner { appContextService .getLogger() .info(`processed ${allAgentsProcessed} agents, took ${Date.now() - start}ms`); - return { ...results }; + return { actionId: this.actionParams.actionId! }; } } diff --git a/x-pack/plugins/fleet/server/services/agents/crud.test.ts b/x-pack/plugins/fleet/server/services/agents/crud.test.ts index c6d63b35ac7be..bee3d9fef09fc 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.test.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import type { Agent } from '../../types'; -import { errorsToResults, getAgentsByKuery, getAgentTags } from './crud'; +import { getAgentsByKuery, getAgentTags } from './crud'; jest.mock('../../../common/services/is_agent_upgradeable', () => ({ isAgentUpgradeable: jest.fn().mockImplementation((agent: Agent) => agent.id.includes('up')), @@ -292,38 +292,4 @@ describe('Agents CRUD test', () => { ]); }); }); - - describe('errorsToResults', () => { - it('should transform errors to results', () => { - const results = errorsToResults([{ id: '1' } as Agent, { id: '2' } as Agent], { - '1': new Error('error'), - }); - expect(results).toEqual([ - { id: '1', success: false, error: new Error('error') }, - { id: '2', success: true }, - ]); - }); - - it('should transform errors to results with skip success', () => { - const results = errorsToResults( - [{ id: '1' } as Agent, { id: '2' } as Agent], - { '1': new Error('error') }, - undefined, - true - ); - expect(results).toEqual([{ id: '1', success: false, error: new Error('error') }]); - }); - - it('should transform errors to results preserve order', () => { - const results = errorsToResults( - [{ id: '1' } as Agent, { id: '2' } as Agent], - { '1': new Error('error') }, - ['2', '1'] - ); - expect(results).toEqual([ - { id: '2', success: true }, - { id: '1', success: false, error: new Error('error') }, - ]); - }); - }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 7b3af0c77e626..305bf2b6bacb4 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -250,34 +250,6 @@ export async function getAgentsByKuery( }; } -export function errorsToResults( - agents: Agent[], - errors: Record, - agentIds?: string[], - skipSuccess?: boolean -): BulkActionResult[] { - if (!skipSuccess) { - const givenOrder = agentIds ? agentIds : agents.map((agent) => agent.id); - return givenOrder.map((agentId) => { - const hasError = agentId in errors; - const result: BulkActionResult = { - id: agentId, - success: !hasError, - }; - if (hasError) { - result.error = errors[agentId]; - } - return result; - }); - } else { - return Object.entries(errors).map(([agentId, error]) => ({ - id: agentId, - success: false, - error, - })); - } -} - export async function getAllAgentsByKuery( esClient: ElasticsearchClient, options: Omit & { diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index c04a5b3b9ad3e..6a81606697228 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -8,7 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; import Boom from '@hapi/boom'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { agentPolicyService } from '../agent_policy'; import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; @@ -89,7 +89,7 @@ export async function reassignAgents( batchSize?: number; }, newAgentPolicyId: string -): Promise<{ items: BulkActionResult[]; actionId?: string }> { +): Promise<{ actionId: string }> { const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); if (!newAgentPolicy) { throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`); @@ -141,12 +141,5 @@ export async function reassignAgents( } } - return await reassignBatch( - soClient, - esClient, - { newAgentPolicyId }, - givenAgents, - outgoingErrors, - 'agentIds' in options ? options.agentIds : undefined - ); + return await reassignBatch(soClient, esClient, { newAgentPolicyId }, givenAgents, outgoingErrors); } diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index 8724a68d330e7..55c0e00728d16 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -7,7 +7,7 @@ import uuid from 'uuid'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; @@ -15,22 +15,14 @@ import { appContextService } from '../app_context'; import { ActionRunner } from './action_runner'; -import { errorsToResults, bulkUpdateAgents } from './crud'; +import { bulkUpdateAgents } from './crud'; import { bulkCreateAgentActionResults, createAgentAction } from './actions'; import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; export class ReassignActionRunner extends ActionRunner { - protected async processAgents(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { - return await reassignBatch( - this.soClient, - this.esClient, - this.actionParams! as any, - agents, - {}, - undefined, - true - ); + protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { + return await reassignBatch(this.soClient, this.esClient, this.actionParams! as any, agents, {}); } protected getTaskType() { @@ -51,10 +43,8 @@ export async function reassignBatch( total?: number; }, givenAgents: Agent[], - outgoingErrors: Record, - agentIds?: string[], - skipSuccess?: boolean -): Promise<{ items: BulkActionResult[] }> { + outgoingErrors: Record +): Promise<{ actionId: string }> { const errors: Record = { ...outgoingErrors }; const hostedPolicies = await getHostedPolicies(soClient, givenAgents); @@ -74,14 +64,12 @@ export async function reassignBatch( return agents; }, []); - const result = { items: errorsToResults(givenAgents, errors, agentIds, skipSuccess) }; - if (agentsToUpdate.length === 0) { // early return if all agents failed validation appContextService .getLogger() .debug('No agents to update, skipping agent update and action creation'); - return result; + throw new AgentReassignmentError('No agents to reassign, already assigned or hosted agents'); } await bulkUpdateAgents( @@ -129,5 +117,5 @@ export async function reassignBatch( ); } - return result; + return { actionId }; } diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index d96e8b89d2fc1..79612b0bcbf06 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -223,21 +223,7 @@ describe('unenrollAgents (plural)', () => { agentIds: idsToUnenroll, revoke: true, }); - - expect(unenrolledResponse.items).toMatchObject([ - { - id: 'agent-in-regular-policy', - success: true, - }, - { - id: 'agent-in-hosted-policy', - success: false, - }, - { - id: 'agent-in-regular-policy2', - success: true, - }, - ]); + expect(unenrolledResponse.actionId).toBeDefined(); // calls ES update with correct values const onlyRegular = [agentInRegularDoc._id, agentInRegularDoc2._id]; @@ -301,20 +287,7 @@ describe('unenrollAgents (plural)', () => { force: true, }); - expect(unenrolledResponse.items).toMatchObject([ - { - id: 'agent-in-regular-policy', - success: true, - }, - { - id: 'agent-in-hosted-policy', - success: true, - }, - { - id: 'agent-in-regular-policy2', - success: true, - }, - ]); + expect(unenrolledResponse.actionId).toBeDefined(); // calls ES update with correct values const calledWith = esClient.bulk.mock.calls[1][0]; diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index f7c49f3efcf1c..078b9ce3aef37 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/ import uuid from 'uuid'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; @@ -74,7 +74,7 @@ export async function unenrollAgents( revoke?: boolean; batchSize?: number; } -): Promise<{ items: BulkActionResult[]; actionId?: string }> { +): Promise<{ actionId: string }> { if ('agentIds' in options) { const givenAgents = await getAgents(esClient, options); return await unenrollBatch(soClient, esClient, givenAgents, options); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts index f9ba2e4be44a1..dd5b4e023c2a3 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts @@ -11,7 +11,7 @@ import { intersection } from 'lodash'; import { AGENT_ACTIONS_RESULTS_INDEX } from '../../../common'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { FleetError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; @@ -21,7 +21,7 @@ import { appContextService } from '../app_context'; import { ActionRunner } from './action_runner'; -import { errorsToResults, bulkUpdateAgents } from './crud'; +import { bulkUpdateAgents } from './crud'; import { bulkCreateAgentActionResults, createAgentAction, @@ -31,8 +31,8 @@ import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; export class UnenrollActionRunner extends ActionRunner { - protected async processAgents(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { - return await unenrollBatch(this.soClient, this.esClient, agents, this.actionParams!, true); + protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { + return await unenrollBatch(this.soClient, this.esClient, agents, this.actionParams!); } protected getTaskType() { @@ -60,9 +60,8 @@ export async function unenrollBatch( revoke?: boolean; actionId?: string; total?: number; - }, - skipSuccess?: boolean -): Promise<{ items: BulkActionResult[] }> { + } +): Promise<{ actionId: string }> { const hostedPolicies = await getHostedPolicies(soClient, givenAgents); const outgoingErrors: Record = {}; @@ -134,7 +133,7 @@ export async function unenrollBatch( ); return { - items: errorsToResults(givenAgents, outgoingErrors, undefined, skipSuccess), + actionId, }; } diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts index 4496e16cbc476..a8c5496a6b028 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts @@ -8,7 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { AgentReassignmentError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; @@ -28,7 +28,7 @@ export async function updateAgentTags( options: ({ agents: Agent[] } | GetAgentsOptions) & { batchSize?: number }, tagsToAdd: string[], tagsToRemove: string[] -): Promise<{ items: BulkActionResult[]; actionId?: string }> { +): Promise<{ actionId: string }> { const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; @@ -69,12 +69,8 @@ export async function updateAgentTags( } } - return await updateTagsBatch( - soClient, - esClient, - givenAgents, - outgoingErrors, - { tagsToAdd, tagsToRemove }, - 'agentIds' in options ? options.agentIds : undefined - ); + return await updateTagsBatch(soClient, esClient, givenAgents, outgoingErrors, { + tagsToAdd, + tagsToRemove, + }); } diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts index 807f2ab1cf073..48f7b455d36b7 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts @@ -9,19 +9,19 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/ import uuid from 'uuid'; import { difference, uniq } from 'lodash'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { appContextService } from '../app_context'; import { ActionRunner } from './action_runner'; -import { errorsToResults, bulkUpdateAgents } from './crud'; +import { bulkUpdateAgents } from './crud'; import { BulkActionTaskType } from './bulk_actions_resolver'; import { filterHostedPolicies } from './filter_hosted_agents'; import { bulkCreateAgentActionResults, createAgentAction } from './actions'; export class UpdateAgentTagsActionRunner extends ActionRunner { - protected async processAgents(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { + protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { return await updateTagsBatch( this.soClient, this.esClient, @@ -32,9 +32,7 @@ export class UpdateAgentTagsActionRunner extends ActionRunner { tagsToRemove: this.actionParams?.tagsToRemove, actionId: this.actionParams.actionId, total: this.actionParams.total, - }, - undefined, - true + } ); } @@ -57,10 +55,8 @@ export async function updateTagsBatch( tagsToRemove: string[]; actionId?: string; total?: number; - }, - agentIds?: string[], - skipSuccess?: boolean -): Promise<{ items: BulkActionResult[] }> { + } +): Promise<{ actionId: string }> { const errors: Record = { ...outgoingErrors }; const filteredAgents = await filterHostedPolicies( @@ -135,5 +131,5 @@ export async function updateTagsBatch( ); } - return { items: errorsToResults(filteredAgents, errors, agentIds, skipSuccess) }; + return { actionId }; } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index b6c50a3b5dc3c..cf298ecb5997d 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -7,7 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; @@ -73,7 +73,7 @@ export async function sendUpgradeAgentsActions( startTime?: string; batchSize?: number; } -): Promise<{ items: BulkActionResult[]; actionId?: string }> { +): Promise<{ actionId: string }> { // Full set of agents const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index 28c1ec3d26007..c757a9f1b5482 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -12,7 +12,7 @@ import uuid from 'uuid'; import { isAgentUpgradeable } from '../../../common/services'; -import type { Agent, BulkActionResult } from '../../types'; +import type { Agent } from '../../types'; import { HostedAgentPolicyRestrictionRelatedError, FleetError } from '../../errors'; @@ -21,21 +21,14 @@ import { appContextService } from '../app_context'; import { ActionRunner } from './action_runner'; import type { GetAgentsOptions } from './crud'; -import { errorsToResults, bulkUpdateAgents } from './crud'; +import { bulkUpdateAgents } from './crud'; import { bulkCreateAgentActionResults, createAgentAction } from './actions'; import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; export class UpgradeActionRunner extends ActionRunner { - protected async processAgents(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { - return await upgradeBatch( - this.soClient, - this.esClient, - agents, - {}, - this.actionParams! as any, - true - ); + protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { + return await upgradeBatch(this.soClient, this.esClient, agents, {}, this.actionParams! as any); } protected getTaskType() { @@ -60,9 +53,8 @@ export async function upgradeBatch( upgradeDurationSeconds?: number; startTime?: string; total?: number; - }, - skipSuccess?: boolean -): Promise<{ items: BulkActionResult[] }> { + } +): Promise<{ actionId: string }> { const errors: Record = { ...outgoingErrors }; const hostedPolicies = await getHostedPolicies(soClient, givenAgents); @@ -161,12 +153,7 @@ export async function upgradeBatch( ); return { - items: errorsToResults( - givenAgents, - errors, - 'agentIds' in options ? options.agentIds : undefined, - skipSuccess - ), + actionId, }; } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 1066277da776f..1e9bf2a737964 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12510,9 +12510,7 @@ "xpack.fleet.unenrollAgents.forceDeleteMultipleTitle": "Désenregistrer {count} agents", "xpack.fleet.unenrollAgents.forceUnenrollCheckboxLabel": "Supprimer {count, plural, one {l'agent} other {les agents}} immédiatement. N'attendez pas que l'agent envoie les dernières données.", "xpack.fleet.unenrollAgents.forceUnenrollLegendText": "Forcer le désenregistrement {count, plural, one {de l'agent} other {des agents}}", - "xpack.fleet.upgradeAgents.bulkResultErrorResultsSummary": "Échec de {count} {count, plural, one {agent} other {agents}}", "xpack.fleet.upgradeAgents.hourLabel": "{option} {count, plural, one {heure} other {heures}}", - "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "Mise à niveau de l'agent {count}", "xpack.fleet.upgradeAgents.upgradeMultipleDescription": "Cette action met à niveau plusieurs agents vers la version {version}. Impossible d'annuler cette action. Voulez-vous vraiment continuer ?", "xpack.fleet.upgradeAgents.upgradeSingleDescription": "Cette action met à niveau l'agent qui s'exécute sur \"{hostName}\" vers la version {version}. Impossible d'annuler cette action. Voulez-vous vraiment continuer ?", "xpack.fleet.upgradeAgents.warningCallout": "Les mises à niveau propagées sont uniquement disponible pour Elastic Agent à partir de la version {version}.", @@ -13415,7 +13413,6 @@ "xpack.fleet.unenrollAgents.unenrollFleetServerTitle": "Cet agent exécute le serveur Fleet", "xpack.fleet.updateAgentTags.errorNotificationTitle": "Échec de la mise à jour des balises", "xpack.fleet.updateAgentTags.successNotificationTitle": "Balises mises à jour", - "xpack.fleet.upgradeAgents.bulkResultAllErrorsNotificationTitle": "Erreur lors de la mise à niveau de {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}", "xpack.fleet.upgradeAgents.cancelButtonLabel": "Annuler", "xpack.fleet.upgradeAgents.chooseVersionLabel": "Mettre à niveau la version", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "Mettre à niveau {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e077421392645..493f2b2e7a885 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12496,9 +12496,7 @@ "xpack.fleet.unenrollAgents.forceDeleteMultipleTitle": "{count}個のエージェントを登録解除", "xpack.fleet.unenrollAgents.forceUnenrollCheckboxLabel": "{count, plural, other {エージェント}}を直ちに削除します。エージェントが最後のデータを送信するまで待機しない。", "xpack.fleet.unenrollAgents.forceUnenrollLegendText": "{count, plural, other {エージェント}}を強制的に登録解除", - "xpack.fleet.upgradeAgents.bulkResultErrorResultsSummary": "{count} {count, plural, other {個のエージェント}}が成功しませんでした", "xpack.fleet.upgradeAgents.hourLabel": "{option} {count, plural, other {時間}}", - "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "{count}個のエージェントをアップグレードしています", "xpack.fleet.upgradeAgents.upgradeMultipleDescription": "このアクションにより、複数のエージェントがバージョン{version}にアップグレードされます。このアクションは元に戻せません。続行していいですか?", "xpack.fleet.upgradeAgents.upgradeSingleDescription": "このアクションにより、「{hostName}」で実行中のエージェントがバージョン{version}にアップグレードされます。このアクションは元に戻せません。続行していいですか?", "xpack.fleet.upgradeAgents.warningCallout": "ローリングアップグレードは、Elasticエージェントバージョン{version}以降でのみ使用できます", @@ -13401,7 +13399,6 @@ "xpack.fleet.unenrollAgents.unenrollFleetServerTitle": "このエージェントはFleetサーバーを実行しています", "xpack.fleet.updateAgentTags.errorNotificationTitle": "タグの更新が失敗しました", "xpack.fleet.updateAgentTags.successNotificationTitle": "タグが更新されました", - "xpack.fleet.upgradeAgents.bulkResultAllErrorsNotificationTitle": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}のアップグレードエラー", "xpack.fleet.upgradeAgents.cancelButtonLabel": "キャンセル", "xpack.fleet.upgradeAgents.chooseVersionLabel": "バージョンのアップグレード", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}をアップグレード", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 41c4320dae010..45552726be4e8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12513,9 +12513,7 @@ "xpack.fleet.unenrollAgents.forceDeleteMultipleTitle": "取消注册 {count} 个代理", "xpack.fleet.unenrollAgents.forceUnenrollCheckboxLabel": "立即移除{count, plural, other {代理}}。不用等待代理发送任何最终数据。", "xpack.fleet.unenrollAgents.forceUnenrollLegendText": "强制取消注册{count, plural, other {代理}}", - "xpack.fleet.upgradeAgents.bulkResultErrorResultsSummary": "{count} 个{count, plural, other {代理}}未成功", "xpack.fleet.upgradeAgents.hourLabel": "{option} {count, plural, other {小时}}", - "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "正在升级 {count} 个代理", "xpack.fleet.upgradeAgents.upgradeMultipleDescription": "此操作会将多个代理升级到版本 {version}。此操作无法撤消。是否确定要继续?", "xpack.fleet.upgradeAgents.upgradeSingleDescription": "此操作会将“{hostName}”上运行的代理升级到版本 {version}。此操作无法撤消。是否确定要继续?", "xpack.fleet.upgradeAgents.warningCallout": "滚动升级仅适用于 Elastic 代理版本 {version} 及更高版本", @@ -13418,7 +13416,6 @@ "xpack.fleet.unenrollAgents.unenrollFleetServerTitle": "此代理正在运行 Fleet 服务器", "xpack.fleet.updateAgentTags.errorNotificationTitle": "标签更新失败", "xpack.fleet.updateAgentTags.successNotificationTitle": "标签已更新", - "xpack.fleet.upgradeAgents.bulkResultAllErrorsNotificationTitle": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}时出错", "xpack.fleet.upgradeAgents.cancelButtonLabel": "取消", "xpack.fleet.upgradeAgents.chooseVersionLabel": "升级版本", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}", diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 746a2fcda1ba1..1283c9433ebba 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -107,7 +107,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should allow to reassign multiple agents by id -- mix valid & invalid', async () => { - const { body } = await supertest + await supertest .post(`/api/fleet/agents/bulk_reassign`) .set('kbn-xsrf', 'xxx') .send({ @@ -115,23 +115,6 @@ export default function (providerContext: FtrProviderContext) { policy_id: 'policy2', }); - expect(body).to.eql({ - agent2: { success: true }, - INVALID_ID: { - success: false, - error: 'Cannot find agent INVALID_ID', - }, - agent3: { success: true }, - MISSING_ID: { - success: false, - error: 'Cannot find agent MISSING_ID', - }, - etc: { - success: false, - error: 'Cannot find agent etc', - }, - }); - const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), supertest.get(`/api/fleet/agents/agent3`), @@ -148,7 +131,7 @@ export default function (providerContext: FtrProviderContext) { .send({ name: 'Test policy', namespace: 'default', is_managed: true }) .expect(200); - const { body } = await supertest + await supertest .post(`/api/fleet/agents/bulk_reassign`) .set('kbn-xsrf', 'xxx') .send({ @@ -156,23 +139,6 @@ export default function (providerContext: FtrProviderContext) { policy_id: 'policy2', }); - expect(body).to.eql({ - agent2: { - success: false, - error: - 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', - }, - INVALID_ID: { - success: false, - error: 'Cannot find agent INVALID_ID', - }, - agent3: { - success: false, - error: - 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', - }, - }); - const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), supertest.get(`/api/fleet/agents/agent3`), diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index 058fccfb376d8..fe31806038a5c 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -120,7 +120,7 @@ export default function (providerContext: FtrProviderContext) { .expect(200); // try to unenroll - const { body: unenrolledBody } = await supertest + await supertest .post(`/api/fleet/agents/bulk_unenroll`) .set('kbn-xsrf', 'xxx') .send({ @@ -129,18 +129,6 @@ export default function (providerContext: FtrProviderContext) { // http request succeeds .expect(200); - expect(unenrolledBody).to.eql({ - agent2: { - success: false, - error: - 'Cannot unenroll agent2 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', - }, - agent3: { - success: false, - error: - 'Cannot unenroll agent3 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', - }, - }); // but agents are still enrolled const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), @@ -161,18 +149,13 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ name: 'Test policy', namespace: 'default', is_managed: false }) .expect(200); - const { body: unenrolledBody } = await supertest + await supertest .post(`/api/fleet/agents/bulk_unenroll`) .set('kbn-xsrf', 'xxx') .send({ agents: ['agent2', 'agent3'], }); - expect(unenrolledBody).to.eql({ - agent2: { success: true }, - agent3: { success: true }, - }); - const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), supertest.get(`/api/fleet/agents/agent3`), diff --git a/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts b/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts index afe9f8d677d35..88079ae5e1aff 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts @@ -140,7 +140,7 @@ export default function (providerContext: FtrProviderContext) { }); // attempt to update tags of agent in hosted agent policy - const { body } = await supertest + await supertest .post(`/api/fleet/agents/bulk_update_agent_tags`) .set('kbn-xsrf', 'xxx') .send({ @@ -149,14 +149,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(body).to.eql({ - agent1: { - success: false, - error: `Cannot modify tags on a hosted agent in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.`, - }, - agent2: { success: true }, - }); - const [agent1data, agent2data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent1`), supertest.get(`/api/fleet/agents/agent2`), diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index e941752f5f1b6..e2762e21f33ad 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -1016,7 +1016,7 @@ export default function (providerContext: FtrProviderContext) { }, }); // attempt to upgrade agent in hosted agent policy - const { body } = await supertest + await supertest .post(`/api/fleet/agents/bulk_upgrade`) .set('kbn-xsrf', 'xxx') .send({ @@ -1025,15 +1025,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(body).to.eql({ - agent1: { - success: false, - error: - 'Cannot upgrade agent in hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', - }, - agent2: { success: true }, - }); - const [agent1data, agent2data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent1`), supertest.get(`/api/fleet/agents/agent2`), @@ -1076,7 +1067,7 @@ export default function (providerContext: FtrProviderContext) { }, }); // attempt to upgrade agent in hosted agent policy - const { body } = await supertest + await supertest .post(`/api/fleet/agents/bulk_upgrade`) .set('kbn-xsrf', 'xxx') .send({ @@ -1085,11 +1076,6 @@ export default function (providerContext: FtrProviderContext) { force: true, }); - expect(body).to.eql({ - agent1: { success: true }, - agent2: { success: true }, - }); - const [agent1data, agent2data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent1`), supertest.get(`/api/fleet/agents/agent2`), From 5f8f1e55638d2d68ad41469619506159139dac82 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 27 Sep 2022 10:50:27 +0200 Subject: [PATCH 052/172] [Security Solution][Endpoint][Response Actions] Update table pagination header colour (#141847) fixes elastic/security-team/issues/5042 --- .../endpoint_response_actions_list/response_actions_log.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index d7b923dec5058..bea5a3eb07aaf 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -597,7 +597,7 @@ export const ResponseActionsLog = memo< // create range label to display const recordRangeLabel = useMemo( () => ( - + Date: Tue, 27 Sep 2022 10:27:10 +0100 Subject: [PATCH 053/172] skip failing test (#141901) --- .../tests/services/sorted_and_filtered_services.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts index fdc1e84cddfc3..f084f8cdca187 100644 --- a/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts @@ -50,7 +50,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { type ServiceListItem = ValuesType>>; - registry.when('Sorted and filtered services', { config: 'trial', archives: [] }, () => { + registry.when.skip('Sorted and filtered services', { config: 'trial', archives: [] }, () => { before(async () => { const serviceA = apm .service({ name: SERVICE_NAME_PREFIX + 'a', environment: 'production', agentName: 'java' }) From 405997e47ffb56b4df034bbd8cbbbacdc918409c Mon Sep 17 00:00:00 2001 From: Klim Markelov Date: Tue, 27 Sep 2022 11:30:34 +0200 Subject: [PATCH 054/172] Fix analytics js file path on integration screen (#141892) --- .../analytics_collection_integrate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx index 36b527097a304..47f0f161263af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx @@ -49,7 +49,7 @@ export const AnalyticsCollectionIntegrate: React.FC From ead1cf386d75c75cabc5470c02ad7cd86e9403d5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 27 Sep 2022 12:45:06 +0200 Subject: [PATCH 055/172] [Files] Update the download method on the files client (#141904) * update the download method on the files client * rather link to FileJSON --- x-pack/plugins/files/public/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index ac5ec40c2c252..25aab6e787b6b 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -117,8 +117,10 @@ export interface FilesClient extends GlobalEndpoints { /** * Get a string for downloading a file that can be passed to a button element's * href for download. + * + * @param args - get download URL args */ - getDownloadHref: (file: FileJSON) => string; + getDownloadHref: (args: Pick) => string; /** * Share a file by creating a new file share instance. * From 3edba25c2dacc0a80d712cbe4334d3c65eb090a6 Mon Sep 17 00:00:00 2001 From: Tre Date: Tue, 27 Sep 2022 12:03:01 +0100 Subject: [PATCH 056/172] [Archive Migration] x-pack-banners/multispace (#135783) * [Archive Migration] x-pack-banners/multispace I've the before() fn loading the new kbn archive containing one config, for the default space. Then, the fn creates a new space and changes the ui settings of this new space, w/o using an archive. * Move to enabled per: https://github.com/elastic/kibana/pull/135783#issuecomment-1237228021 * Just run my stuff this go-round. * Fixup. * Drop new archive, re-enable all tests, skip the last test, and add the filed bugs. * Whoops * Drop redundant test. * Not sure what's what yet, but I think perhaps an override is occuring. * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Change value in test and config. * Drop double quotes, seems to have caused parsing issue. * One more try before asking in kibana-core * try snake case * back to space case but without quotes in the config * Just to see what happens I guess. * Merge fixups. * Change refs of "global_banner_text" to "global banner text" * Stop the shell expansion * Drop duplicate. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- scripts/archive_migration_functions.sh | 22 +- src/dev/build/tasks/bin/scripts/kibana | 2 +- .../banners/server/ui_settings.test.ts | 6 +- x-pack/test/banners_functional/config.ts | 2 +- .../test/banners_functional/tests/global.ts | 2 +- .../test/banners_functional/tests/spaces.ts | 23 +- .../es_archives/banners/multispace/data.json | 62 ----- .../banners/multispace/mappings.json | 248 ------------------ 8 files changed, 23 insertions(+), 344 deletions(-) delete mode 100644 x-pack/test/functional/es_archives/banners/multispace/data.json delete mode 100644 x-pack/test/functional/es_archives/banners/multispace/mappings.json diff --git a/scripts/archive_migration_functions.sh b/scripts/archive_migration_functions.sh index a37d52a1417c7..7dcba01175731 100755 --- a/scripts/archive_migration_functions.sh +++ b/scripts/archive_migration_functions.sh @@ -1,27 +1,13 @@ -#!/bin/bash - -# ??? Should we migrate -# x-pack/test/functional/es_archives/logstash/example_pipelines -# !!! No, we've found 0 saved objects that are listed in the standard_list -# !!! It contains the following saved object(s) -# config -# space - standard_list="url,index-pattern,query,graph-workspace,tag,visualization,canvas-element,canvas-workpad,dashboard,search,lens,map,cases,uptime-dynamic-settings,osquery-saved-query,osquery-pack,infrastructure-ui-source,metrics-explorer-view,inventory-view,infrastructure-monitoring-log-view,apm-indices" -orig_archive="x-pack/test/functional/es_archives/spaces/multi_space" -new_archive="x-pack/test/functional/fixtures/kbn_archiver/spaces/multi_space" +orig_archive="x-pack/test/functional/es_archives/banners/multispace" +new_archive="x-pack/test/functional/fixtures/kbn_archiver/banners/multi_space" # newArchives=("x-pack/test/functional/fixtures/kbn_archiver/dashboard/session_in_space") -# newArchives+=("x-pack/test/functional/fixtures/kbn_archiver/dashboard/session_in_another_space") -testFiles=("x-pack/test/functional/apps/discover/preserve_url.ts") -testFiles+=("x-pack/test/functional/apps/visualize/preserve_url.ts") -testFiles+=("x-pack/test/functional/apps/dashboard/group1/preserve_url.ts") +# testFiles=("x-pack/test/functional/apps/discover/preserve_url.ts") -test_config="x-pack/test/functional/apps/dashboard/group1/config.ts" -# test_config="x-pack/test/functional/apps/discover/config.ts" -# test_config="x-pack/test/functional/apps/visualize/config.ts" +test_config="x-pack/test/banners_functional/config.ts" list_stragglers() { diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana index a4fc5385500b5..f96fac236b55c 100755 --- a/src/dev/build/tasks/bin/scripts/kibana +++ b/src/dev/build/tasks/bin/scripts/kibana @@ -26,4 +26,4 @@ if [ -f "${CONFIG_DIR}/node.options" ]; then KBN_NODE_OPTS="$(grep -v ^# < ${CONFIG_DIR}/node.options | xargs)" fi -NODE_OPTIONS="--no-warnings --max-http-header-size=65536 $KBN_NODE_OPTS $NODE_OPTIONS" NODE_ENV=production exec "${NODE}" "${DIR}/src/cli/dist" ${@} +NODE_OPTIONS="--no-warnings --max-http-header-size=65536 $KBN_NODE_OPTS $NODE_OPTIONS" NODE_ENV=production exec "${NODE}" "${DIR}/src/cli/dist" "${@}" diff --git a/x-pack/plugins/banners/server/ui_settings.test.ts b/x-pack/plugins/banners/server/ui_settings.test.ts index f83ef6f4bba59..10cf250cc7468 100644 --- a/x-pack/plugins/banners/server/ui_settings.test.ts +++ b/x-pack/plugins/banners/server/ui_settings.test.ts @@ -13,7 +13,7 @@ const createConfig = (parts: Partial = {}): BannersConfigType placement: 'disabled', backgroundColor: '#0000', textColor: '#FFFFFF', - textContent: 'Hello from the banner', + textContent: 'some global banner text', disableSpaceBanners: false, ...parts, }); @@ -31,7 +31,9 @@ describe('registerSettings', () => { expect(uiSettings.register).toHaveBeenCalledTimes(1); expect(uiSettings.register).toHaveBeenCalledWith({ 'banners:placement': expect.any(Object), - 'banners:textContent': expect.any(Object), + 'banners:textContent': expect.objectContaining({ + value: 'some global banner text', + }), 'banners:textColor': expect.any(Object), 'banners:backgroundColor': expect.any(Object), }); diff --git a/x-pack/test/banners_functional/config.ts b/x-pack/test/banners_functional/config.ts index 2280cf11cfbf2..83d0c4656572c 100644 --- a/x-pack/test/banners_functional/config.ts +++ b/x-pack/test/banners_functional/config.ts @@ -35,7 +35,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { serverArgs: [ ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), '--xpack.banners.placement=top', - '--xpack.banners.textContent=global_banner_text', + '--xpack.banners.textContent="global banner text"', ], }, }; diff --git a/x-pack/test/banners_functional/tests/global.ts b/x-pack/test/banners_functional/tests/global.ts index a36d98589163a..cef404d7ed132 100644 --- a/x-pack/test/banners_functional/tests/global.ts +++ b/x-pack/test/banners_functional/tests/global.ts @@ -16,7 +16,7 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('login'); expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); - expect(await PageObjects.banners.getTopBannerText()).to.eql('global_banner_text'); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); }); }); } diff --git a/x-pack/test/banners_functional/tests/spaces.ts b/x-pack/test/banners_functional/tests/spaces.ts index 43c9612fa5696..2ec7d0939b100 100644 --- a/x-pack/test/banners_functional/tests/spaces.ts +++ b/x-pack/test/banners_functional/tests/spaces.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); + const spacesService = getService('spaces'); const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects([ 'common', @@ -21,14 +21,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('per-spaces banners', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/banners/multispace'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/banners/multispace'); - }); - - before(async () => { + await spacesService.create({ + id: 'another-space', + name: 'Another Space', + disabledFeatures: [], + }); await kibanaServer.uiSettings.replace( { 'banners:textContent': 'default space banner text', @@ -39,6 +36,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expectSpaceSelector: true, }); }); + after(async () => { + await spacesService.delete('another-space'); + await kibanaServer.savedObjects.cleanStandardList(); + }); it('displays the space-specific banner within the space', async () => { await PageObjects.common.navigateToApp('home'); @@ -51,7 +52,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/another-space' }); expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); - expect(await PageObjects.banners.getTopBannerText()).to.eql('global_banner_text'); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); }); it('displays the global banner on the login page', async () => { @@ -59,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('login'); expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); - expect(await PageObjects.banners.getTopBannerText()).to.eql('global_banner_text'); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); }); }); } diff --git a/x-pack/test/functional/es_archives/banners/multispace/data.json b/x-pack/test/functional/es_archives/banners/multispace/data.json deleted file mode 100644 index fc0e0dc7b7eee..0000000000000 --- a/x-pack/test/functional/es_archives/banners/multispace/data.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "type": "doc", - "value": { - "id": "config:6.0.0", - "index": ".kibana", - "source": { - "config": { - "buildNum": 8467, - "dateFormat:tz": "UTC", - "defaultRoute": "http://example.com/evil" - }, - "type": "config" - } - } -} - -{ - "type": "doc", - "value": { - "id": "another-space:config:6.0.0", - "index": ".kibana", - "source": { - "namespace": "another-space", - "config": { - "buildNum": 8467, - "dateFormat:tz": "UTC", - "defaultRoute": "/app/canvas" - }, - "type": "config" - } - } -} - -{ - "type": "doc", - "value": { - "id": "space:default", - "index": ".kibana", - "source": { - "space": { - "description": "This is the default space!", - "name": "Default" - }, - "type": "space" - } - } -} - -{ - "type": "doc", - "value": { - "id": "space:another-space", - "index": ".kibana", - "source": { - "space": { - "description": "This is another space", - "name": "Another Space" - }, - "type": "space" - } - } -} diff --git a/x-pack/test/functional/es_archives/banners/multispace/mappings.json b/x-pack/test/functional/es_archives/banners/multispace/mappings.json deleted file mode 100644 index f813fca64c328..0000000000000 --- a/x-pack/test/functional/es_archives/banners/multispace/mappings.json +++ /dev/null @@ -1,248 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "dateFormat:tz": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "defaultRoute": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} From 4c3cd034aaaa1e8a0962af5bde20ed83ff7ccf80 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 27 Sep 2022 14:32:52 +0300 Subject: [PATCH 057/172] [Lens] Enables text-based languages (SQL) (#140469) * [Lens] Enable text-based languages-sql * Display data * Chart switcher and further fixes * Drag and drop fixes * Small fix * Multiple improvements * Errors implementation and save and exit * Some cleanup * Fix types failures * Revert change * Fix underlying data error * Fix jest test * Fixes app test * Rename datasource to textBased * display the dataview picker component but disabled * Fix functional test * Refactoring * Populate the dataview to theembeddable * Load sync * sync load of the new dtsource * Fix * Fix bug when the dtaview is not found * Refactoring * Add some unit tests * Add some unit tests * Add more unit tests * Add a functional test * Add all files * Update lens limit * Fixes bug * Bump lens size * Fix flakiness * Further fixes * Fix check * More fixes * Fix test * Wait for query to run * More changes * Fix * Fix the function input to fetch from variable * Remove the colorFullBackground check * Fix dashboard bug when timeRange changes * Remove the clear on query update, show column error instead * Render a field missing label in case the label is not defined * Fix Co-authored-by: Joe Reuter --- src/plugins/data/common/index.ts | 3 +- src/plugins/data/common/query/index.ts | 3 +- ... => text_based_query_state_to_ast.test.ts} | 29 +- ...st.ts => text_based_query_state_to_ast.ts} | 31 +- ...query_state_to_ast_with_validation.test.ts | 102 +++ ...ased_query_state_to_ast_with_validation.ts | 65 ++ .../application/main/utils/fetch_sql.ts | 4 +- src/plugins/discover/server/ui_settings.ts | 2 +- .../dataview_picker/change_dataview.test.tsx | 21 +- .../dataview_picker/change_dataview.tsx | 14 +- .../text_languages_transition_modal.tsx | 2 + .../public/search_bar/search_bar.tsx | 2 +- .../page_objects/unified_search_page.ts | 16 +- x-pack/plugins/lens/common/constants.ts | 2 + .../expressions/map_to_columns/index.ts | 1 + x-pack/plugins/lens/common/types.ts | 2 + .../lens/public/app_plugin/app.test.tsx | 118 ++-- x-pack/plugins/lens/public/app_plugin/app.tsx | 32 +- .../lens/public/app_plugin/lens_top_nav.tsx | 100 ++- .../app_plugin/save_modal_container.tsx | 13 +- .../app_plugin/show_underlying_data.test.ts | 3 + .../public/app_plugin/show_underlying_data.ts | 3 +- .../plugins/lens/public/app_plugin/types.ts | 1 + x-pack/plugins/lens/public/async_services.ts | 2 + .../config_panel/config_panel.test.tsx | 31 +- .../config_panel/config_panel.tsx | 27 +- .../layer_actions/clone_layer_action.tsx | 3 +- .../layer_actions/layer_actions.tsx | 2 + .../editor_frame/config_panel/layer_panel.tsx | 3 + .../editor_frame/data_panel_wrapper.tsx | 49 +- .../editor_frame/editor_frame.test.tsx | 36 +- .../editor_frame/expression_helpers.ts | 1 - .../editor_frame/suggestion_helpers.test.ts | 3 + .../workspace_panel/workspace_panel.tsx | 15 +- .../indexpattern_datasource/datapanel.tsx | 56 +- .../indexpattern.test.ts | 9 + .../indexpattern_datasource/indexpattern.tsx | 13 +- .../lens/public/mocks/data_plugin_mock.ts | 1 + .../lens/public/mocks/datasource_mock.ts | 2 + .../lens/public/mocks/services_mock.tsx | 8 +- x-pack/plugins/lens/public/plugin.ts | 6 + .../dataview_picker/dataview_picker.tsx | 1 + .../lens/public/state_management/index.ts | 1 + .../state_management/lens_slice.test.ts | 52 ++ .../public/state_management/lens_slice.ts | 73 ++- .../lens/public/state_management/types.ts | 2 +- .../datapanel.test.tsx | 237 +++++++ .../datapanel.tsx | 167 +++++ .../fetch_data_from_aggregate_query.ts | 65 ++ .../field_select.test.tsx | 111 ++++ .../field_select.tsx | 78 +++ .../text_based_languages_datasource/index.ts | 46 ++ .../layerpanel.test.tsx | 88 +++ .../layerpanel.tsx | 44 ++ .../text_based_languages_datasource/mocks.ts | 22 + .../text_based_languages.test.ts | 595 ++++++++++++++++++ .../text_based_languages.tsx | 587 +++++++++++++++++ .../to_expression.ts | 62 ++ .../text_based_languages_datasource/types.ts | 43 ++ .../utils.test.ts | 193 ++++++ .../text_based_languages_datasource/utils.ts | 99 +++ x-pack/plugins/lens/public/types.ts | 6 + .../datatable/visualization.tsx | 6 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../test/functional/apps/lens/group1/index.ts | 1 + .../apps/lens/group1/text_based_languages.ts | 139 ++++ .../test/functional/page_objects/lens_page.ts | 40 +- 69 files changed, 3321 insertions(+), 276 deletions(-) rename src/plugins/data/common/query/{to_expression_ast.test.ts => text_based_query_state_to_ast.test.ts} (65%) rename src/plugins/data/common/query/{to_expression_ast.ts => text_based_query_state_to_ast.ts} (62%) create mode 100644 src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.test.ts create mode 100644 src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.test.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/fetch_data_from_aggregate_query.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/field_select.test.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/field_select.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/index.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.test.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/mocks.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/to_expression.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/types.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts create mode 100644 x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts create mode 100644 x-pack/test/functional/apps/lens/group1/text_based_languages.ts diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index f76b6b903fe95..38e4d802cd838 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -21,7 +21,8 @@ export { getTime, isQuery, isTimeRange, - queryStateToExpressionAst, + textBasedQueryStateToAstWithValidation, + textBasedQueryStateToExpressionAst, } from './query'; export type { QueryState } from './query'; export * from './search'; diff --git a/src/plugins/data/common/query/index.ts b/src/plugins/data/common/query/index.ts index dedc4b6a4d839..1aebdd09244e4 100644 --- a/src/plugins/data/common/query/index.ts +++ b/src/plugins/data/common/query/index.ts @@ -10,4 +10,5 @@ export * from './timefilter'; export * from './types'; export * from './is_query'; export * from './query_state'; -export { queryStateToExpressionAst } from './to_expression_ast'; +export { textBasedQueryStateToAstWithValidation } from './text_based_query_state_to_ast_with_validation'; +export { textBasedQueryStateToExpressionAst } from './text_based_query_state_to_ast'; diff --git a/src/plugins/data/common/query/to_expression_ast.test.ts b/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts similarity index 65% rename from src/plugins/data/common/query/to_expression_ast.test.ts rename to src/plugins/data/common/query/text_based_query_state_to_ast.test.ts index d7c1424869aa8..64f9c5ca59111 100644 --- a/src/plugins/data/common/query/to_expression_ast.test.ts +++ b/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts @@ -5,20 +5,17 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { DataViewsContract } from '@kbn/data-views-plugin/common'; -import { queryStateToExpressionAst } from './to_expression_ast'; +import { textBasedQueryStateToExpressionAst } from './text_based_query_state_to_ast'; -describe('queryStateToExpressionAst', () => { +describe('textBasedQueryStateToExpressionAst', () => { it('returns an object with the correct structure', async () => { - const dataViewsService = {} as unknown as DataViewsContract; - const actual = await queryStateToExpressionAst({ + const actual = await textBasedQueryStateToExpressionAst({ filters: [], query: { language: 'lucene', query: '' }, time: { from: 'now', to: 'now+7d', }, - dataViewsService, }); expect(actual).toHaveProperty( @@ -33,31 +30,13 @@ describe('queryStateToExpressionAst', () => { }); it('returns an object with the correct structure for an SQL query', async () => { - const dataViewsService = { - getIdsWithTitle: jest.fn(() => { - return [ - { - title: 'foo', - id: 'bar', - }, - ]; - }), - get: jest.fn(() => { - return { - title: 'foo', - id: 'bar', - timeFieldName: 'baz', - }; - }), - } as unknown as DataViewsContract; - const actual = await queryStateToExpressionAst({ + const actual = await textBasedQueryStateToExpressionAst({ filters: [], query: { sql: 'SELECT * FROM foo' }, time: { from: 'now', to: 'now+7d', }, - dataViewsService, }); expect(actual).toHaveProperty( diff --git a/src/plugins/data/common/query/to_expression_ast.ts b/src/plugins/data/common/query/text_based_query_state_to_ast.ts similarity index 62% rename from src/plugins/data/common/query/to_expression_ast.ts rename to src/plugins/data/common/query/text_based_query_state_to_ast.ts index daf75ceb7a0c2..cb34d9c9c405c 100644 --- a/src/plugins/data/common/query/to_expression_ast.ts +++ b/src/plugins/data/common/query/text_based_query_state_to_ast.ts @@ -5,14 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { - isOfAggregateQueryType, - getAggregateQueryMode, - getIndexPatternFromSQLQuery, - Query, -} from '@kbn/es-query'; +import { isOfAggregateQueryType, getAggregateQueryMode, Query } from '@kbn/es-query'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/common'; -import type { DataViewsContract } from '@kbn/data-views-plugin/common'; import { ExpressionFunctionKibana, ExpressionFunctionKibanaContext, @@ -24,7 +18,7 @@ import { } from '..'; interface Args extends QueryState { - dataViewsService: DataViewsContract; + timeFieldName?: string; inputQuery?: Query; } @@ -34,12 +28,12 @@ interface Args extends QueryState { * @param query kibana query or aggregate query * @param time kibana time range */ -export async function queryStateToExpressionAst({ +export function textBasedQueryStateToExpressionAst({ filters, query, inputQuery, time, - dataViewsService, + timeFieldName, }: Args) { const kibana = buildExpressionFunction('kibana', {}); let q; @@ -52,24 +46,15 @@ export async function queryStateToExpressionAst({ filters: filters && filtersToAst(filters), }); const ast = buildExpression([kibana, kibanaContext]).toAst(); + if (query && isOfAggregateQueryType(query)) { const mode = getAggregateQueryMode(query); // sql query if (mode === 'sql' && 'sql' in query) { - const idxPattern = getIndexPatternFromSQLQuery(query.sql); - const idsTitles = await dataViewsService.getIdsWithTitle(); - const dataViewIdTitle = idsTitles.find(({ title }) => title === idxPattern); - - if (dataViewIdTitle) { - const dataView = await dataViewsService.get(dataViewIdTitle.id); - const timeFieldName = dataView.timeFieldName; - const essql = aggregateQueryToAst(query, timeFieldName); + const essql = aggregateQueryToAst(query, timeFieldName); - if (essql) { - ast.chain.push(essql); - } - } else { - throw new Error(`No data view found for index pattern ${idxPattern}`); + if (essql) { + ast.chain.push(essql); } } } diff --git a/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.test.ts b/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.test.ts new file mode 100644 index 0000000000000..d326f9cc70fa1 --- /dev/null +++ b/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.test.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { DataViewsContract } from '@kbn/data-views-plugin/common'; +import { textBasedQueryStateToAstWithValidation } from './text_based_query_state_to_ast_with_validation'; + +describe('textBasedQueryStateToAstWithValidation', () => { + it('returns undefined for a non text based query', async () => { + const dataViewsService = {} as unknown as DataViewsContract; + const actual = await textBasedQueryStateToAstWithValidation({ + filters: [], + query: { language: 'lucene', query: '' }, + time: { + from: 'now', + to: 'now+7d', + }, + dataViewsService, + }); + + expect(actual).toBeUndefined(); + }); + + it('returns an object with the correct structure for an SQL query with existing dataview', async () => { + const dataViewsService = { + getIdsWithTitle: jest.fn(() => { + return [ + { + title: 'foo', + id: 'bar', + }, + ]; + }), + get: jest.fn(() => { + return { + title: 'foo', + id: 'bar', + timeFieldName: 'baz', + }; + }), + } as unknown as DataViewsContract; + const actual = await textBasedQueryStateToAstWithValidation({ + filters: [], + query: { sql: 'SELECT * FROM foo' }, + time: { + from: 'now', + to: 'now+7d', + }, + dataViewsService, + }); + + expect(actual).toHaveProperty( + 'chain.1.arguments.timeRange.0.chain.0.arguments', + expect.objectContaining({ + from: ['now'], + to: ['now+7d'], + }) + ); + + expect(actual).toHaveProperty( + 'chain.2.arguments', + expect.objectContaining({ + query: ['SELECT * FROM foo'], + }) + ); + }); + + it('returns an error for text based language with non existing dataview', async () => { + const dataViewsService = { + getIdsWithTitle: jest.fn(() => { + return [ + { + title: 'foo', + id: 'bar', + }, + ]; + }), + get: jest.fn(() => { + return { + title: 'foo', + id: 'bar', + timeFieldName: 'baz', + }; + }), + } as unknown as DataViewsContract; + + await expect( + textBasedQueryStateToAstWithValidation({ + filters: [], + query: { sql: 'SELECT * FROM another_dataview' }, + time: { + from: 'now', + to: 'now+7d', + }, + dataViewsService, + }) + ).rejects.toThrow('No data view found for index pattern another_dataview'); + }); +}); diff --git a/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts b/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts new file mode 100644 index 0000000000000..c065e8af8e914 --- /dev/null +++ b/src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { + isOfAggregateQueryType, + getIndexPatternFromSQLQuery, + Query, + AggregateQuery, +} from '@kbn/es-query'; +import type { DataViewsContract } from '@kbn/data-views-plugin/common'; +import type { QueryState } from '..'; +import { textBasedQueryStateToExpressionAst } from './text_based_query_state_to_ast'; + +interface Args extends QueryState { + dataViewsService: DataViewsContract; + inputQuery?: Query; +} + +const getIndexPatternFromAggregateQuery = (query: AggregateQuery) => { + if ('sql' in query) { + return getIndexPatternFromSQLQuery(query.sql); + } +}; + +/** + * Converts QueryState to expression AST + * @param filters array of kibana filters + * @param query kibana query or aggregate query + * @param time kibana time range + */ +export async function textBasedQueryStateToAstWithValidation({ + filters, + query, + inputQuery, + time, + dataViewsService, +}: Args) { + let ast; + if (query && isOfAggregateQueryType(query)) { + // sql query + const idxPattern = getIndexPatternFromAggregateQuery(query); + const idsTitles = await dataViewsService.getIdsWithTitle(); + const dataViewIdTitle = idsTitles.find(({ title }) => title === idxPattern); + + if (dataViewIdTitle) { + const dataView = await dataViewsService.get(dataViewIdTitle.id); + const timeFieldName = dataView.timeFieldName; + + ast = textBasedQueryStateToExpressionAst({ + filters, + query, + inputQuery, + time, + timeFieldName, + }); + } else { + throw new Error(`No data view found for index pattern ${idxPattern}`); + } + } + return ast; +} diff --git a/src/plugins/discover/public/application/main/utils/fetch_sql.ts b/src/plugins/discover/public/application/main/utils/fetch_sql.ts index 2faa821bc66ec..057ddc5886da5 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_sql.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_sql.ts @@ -12,7 +12,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; import type { Datatable } from '@kbn/expressions-plugin/public'; import type { DataViewsContract } from '@kbn/data-views-plugin/common'; -import { queryStateToExpressionAst } from '@kbn/data-plugin/common'; +import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common'; import { DataTableRecord } from '../../../types'; interface SQLErrorResponse { @@ -31,7 +31,7 @@ export function fetchSql( inputQuery?: Query ) { const timeRange = data.query.timefilter.timefilter.getTime(); - return queryStateToExpressionAst({ + return textBasedQueryStateToAstWithValidation({ filters, query, time: timeRange, diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index 3f7ddf34331ca..3c0fe05348324 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -329,7 +329,7 @@ export const getUiSettings: (docLinks: DocLinksServiceSetup) => Record` + diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx index 376cbf267a738..8497b599650b2 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx @@ -105,7 +105,7 @@ describe('DataView component', () => { expect(addFieldSpy).toHaveBeenCalled(); }); - it('should not render the add datavuew menu if onDataViewCreated is not given', async () => { + it('should not render the add dataview menu if onDataViewCreated is not given', async () => { await act(async () => { const component = mount(wrapDataViewComponentInContext(props, true)); findTestSubject(component, 'dataview-trigger').simulate('click'); @@ -113,7 +113,7 @@ describe('DataView component', () => { }); }); - it('should render the add datavuew menu if onDataViewCreated is given', async () => { + it('should render the add dataview menu if onDataViewCreated is given', async () => { const addDataViewSpy = jest.fn(); const component = mount( wrapDataViewComponentInContext({ ...props, onDataViewCreated: addDataViewSpy }, false) @@ -141,4 +141,21 @@ describe('DataView component', () => { const text = component.find('[data-test-subj="select-text-based-language-panel"]'); expect(text.length).not.toBe(0); }); + + it('should cleanup the query is on text based mode and add new dataview', async () => { + const component = mount( + wrapDataViewComponentInContext( + { + ...props, + onDataViewCreated: jest.fn(), + textBasedLanguages: [TextBasedLanguages.ESQL, TextBasedLanguages.SQL], + textBasedLanguage: TextBasedLanguages.SQL, + }, + false + ) + ); + findTestSubject(component, 'dataview-trigger').simulate('click'); + component.find('[data-test-subj="dataview-create-new"]').first().simulate('click'); + expect(props.onTextLangQuerySubmit).toHaveBeenCalled(); + }); }); diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 94c869bb54a1c..2f641cd2d4e28 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -235,6 +235,16 @@ export function ChangeDataView({ onClick={() => { setPopoverIsOpen(false); onDataViewCreated(); + // go to dataview mode + if (isTextBasedLangSelected) { + setIsTextBasedLangSelected(false); + // clean up the Text based language query + onTextLangQuerySubmit?.({ + language: 'kuery', + query: '', + }); + setTriggerLabel(trigger.label); + } }} size="xs" iconType="plusInCircleFilled" @@ -262,7 +272,7 @@ export function ChangeDataView({ setIsTextBasedLangSelected(false); // clean up the Text based language query onTextLangQuerySubmit?.({ - language: 'kql', + language: 'kuery', query: '', }); onChangeDataView(newId); @@ -335,7 +345,7 @@ export function ChangeDataView({ setIsTextBasedLangSelected(false); // clean up the Text based language query onTextLangQuerySubmit?.({ - language: 'kql', + language: 'kuery', query: '', }); if (selectedDataViewId) { diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx index 12f5414b92b9e..f7e8b11671556 100644 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx +++ b/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx @@ -86,6 +86,7 @@ export default function TextBasedLanguagesTransitionModal({ onClick={() => closeModal(dismissModalChecked)} color="warning" iconType="merge" + data-test-subj="unifiedSearch_switch_noSave" > {i18n.translate( 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton', @@ -101,6 +102,7 @@ export default function TextBasedLanguagesTransitionModal({ fill color="success" iconType="save" + data-test-subj="unifiedSearch_switch_andSave" > {i18n.translate( 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton', diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index b58c53dea73a0..76746d8a86979 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -353,7 +353,7 @@ class SearchBarUI extends C () => { if (this.props.onQuerySubmit) { this.props.onQuerySubmit({ - query: this.state.query, + query: query as QT, dateRange: { from: this.state.dateRangeFrom, to: this.state.dateRangeTo, diff --git a/test/functional/page_objects/unified_search_page.ts b/test/functional/page_objects/unified_search_page.ts index c46e79a2f1546..6dc77ce91cc85 100644 --- a/test/functional/page_objects/unified_search_page.ts +++ b/test/functional/page_objects/unified_search_page.ts @@ -13,13 +13,21 @@ export class UnifiedSearchPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly find = this.ctx.getService('find'); - public async switchDataView(switchButtonSelector: string, dataViewTitle: string) { + public async switchDataView( + switchButtonSelector: string, + dataViewTitle: string, + transitionFromTextBasedLanguages?: boolean + ) { await this.testSubjects.click(switchButtonSelector); const indexPatternSwitcher = await this.testSubjects.find('indexPattern-switcher', 500); await this.testSubjects.setValue('indexPattern-switcher--input', dataViewTitle); await (await indexPatternSwitcher.findByCssSelector(`[title="${dataViewTitle}"]`)).click(); + if (Boolean(transitionFromTextBasedLanguages)) { + await this.testSubjects.click('unifiedSearch_switch_noSave'); + } + await this.retry.waitFor( 'wait for updating switcher', async () => (await this.getSelectedDataView(switchButtonSelector)) === dataViewTitle @@ -66,4 +74,10 @@ export class UnifiedSearchPageObject extends FtrService { }); await this.testSubjects.click(adHoc ? 'exploreIndexPatternButton' : 'saveIndexPatternButton'); } + + public async selectTextBasedLanguage(language: string) { + await this.find.clickByCssSelector( + `[data-test-subj="text-based-languages-switcher"] [title="${language}"]` + ); + } } diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index 65cd5cdc73a9a..95cba8b75c4bb 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -16,6 +16,8 @@ export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_API_URL = '/api/lens'; export const LENS_EDIT_BY_VALUE = 'edit_by_value'; +export const ENABLE_SQL = 'discover:enableSql'; + export const PieChartTypes = { PIE: 'pie', DONUT: 'donut', diff --git a/x-pack/plugins/lens/common/expressions/map_to_columns/index.ts b/x-pack/plugins/lens/common/expressions/map_to_columns/index.ts index 8ce71d06f6579..4432a2963987f 100644 --- a/x-pack/plugins/lens/common/expressions/map_to_columns/index.ts +++ b/x-pack/plugins/lens/common/expressions/map_to_columns/index.ts @@ -6,3 +6,4 @@ */ export { mapToColumns } from './map_to_columns'; +export type { OriginalColumn } from './types'; diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts index bb176babf9d2f..c21da9e31475c 100644 --- a/x-pack/plugins/lens/common/types.ts +++ b/x-pack/plugins/lens/common/types.ts @@ -20,6 +20,8 @@ import { PieChartTypes, } from './constants'; +export type { OriginalColumn } from './expressions/map_to_columns'; + export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat; export interface ExistingFields { diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index ab74c115d39db..58e65b3b79bbe 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -202,7 +202,7 @@ describe('Lens App', () => { }, }); const navigationComponent = services.navigation.ui - .TopNavMenu as unknown as React.ReactElement; + .AggregateQueryTopNavMenu as unknown as React.ReactElement; const extraEntry = instance.find(navigationComponent).prop('config')[0]; expect(extraEntry.label).toEqual('My entry'); expect(extraEntry.run).toBe(runFn); @@ -367,7 +367,7 @@ describe('Lens App', () => { Promise.resolve({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) ); const { services } = await mountWith({ services: customServices }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: true }), {} ); @@ -382,7 +382,7 @@ describe('Lens App', () => { const customProps = makeDefaultProps(); customProps.datasourceMap.testDatasource.isTimeBased = () => true; const { services } = await mountWith({ props: customProps, services: customServices }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: true }), {} ); @@ -397,7 +397,7 @@ describe('Lens App', () => { const customProps = makeDefaultProps(); customProps.datasourceMap.testDatasource.isTimeBased = () => false; const { services } = await mountWith({ props: customProps, services: customServices }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: false }), {} ); @@ -495,7 +495,7 @@ describe('Lens App', () => { }); instance.update(); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: 'fake query', indexPatterns: [ @@ -518,7 +518,7 @@ describe('Lens App', () => { .mockImplementation((id) => Promise.reject({ reason: 'Could not locate that data view' })); const customProps = makeDefaultProps(); const { services } = await mountWith({ props: customProps, services: customServices }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [] }), {} ); @@ -633,7 +633,9 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(false); await act(async () => { - const topNavMenuConfig = instance.find(services.navigation.ui.TopNavMenu).prop('config'); + const topNavMenuConfig = instance + .find(services.navigation.ui.AggregateQueryTopNavMenu) + .prop('config'); expect(topNavMenuConfig).not.toContainEqual( expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) ); @@ -671,7 +673,9 @@ describe('Lens App', () => { }); await act(async () => { - const topNavMenuConfig = instance.find(services.navigation.ui.TopNavMenu).prop('config'); + const topNavMenuConfig = instance + .find(services.navigation.ui.AggregateQueryTopNavMenu) + .prop('config'); expect(topNavMenuConfig).toContainEqual( expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) ); @@ -699,7 +703,9 @@ describe('Lens App', () => { }); await act(async () => { - const topNavMenuConfig = instance.find(services.navigation.ui.TopNavMenu).prop('config'); + const topNavMenuConfig = instance + .find(services.navigation.ui.AggregateQueryTopNavMenu) + .prop('config'); expect(topNavMenuConfig).toContainEqual( expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) ); @@ -1002,7 +1008,7 @@ describe('Lens App', () => { describe('query bar state management', () => { it('uses the default time and query language settings', async () => { const { lensStore, services } = await mountWith({}); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: '', language: 'lucene' }, dateRangeFrom: 'now-7d', @@ -1029,13 +1035,13 @@ describe('Lens App', () => { max: moment('2021-01-09T08:00:00.000Z'), }); await act(async () => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); instance.update(); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'new', language: 'lucene' }, dateRangeFrom: 'now-14d', @@ -1089,7 +1095,7 @@ describe('Lens App', () => { }); act(() => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: '', language: 'lucene' }, }) @@ -1103,7 +1109,7 @@ describe('Lens App', () => { }); // trigger again, this time changing just the query act(() => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) @@ -1139,7 +1145,7 @@ describe('Lens App', () => { }, }; await mountWith({ services }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: false }), {} ); @@ -1147,7 +1153,7 @@ describe('Lens App', () => { it('persists the saved query ID when the query is saved', async () => { const { instance, services } = await mountWith({}); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: true, savedQuery: undefined, @@ -1158,7 +1164,7 @@ describe('Lens App', () => { {} ); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSaved')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1167,7 +1173,7 @@ describe('Lens App', () => { }, }); }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { id: '1', @@ -1185,7 +1191,7 @@ describe('Lens App', () => { it('changes the saved query ID when the query is updated', async () => { const { instance, services } = await mountWith({}); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSaved')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1195,16 +1201,18 @@ describe('Lens App', () => { }); }); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSavedQueryUpdated')!({ - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: '', language: 'lucene' }, - }, - }); + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( + { + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: '', language: 'lucene' }, + }, + } + ); }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { id: '2', @@ -1222,16 +1230,18 @@ describe('Lens App', () => { it('updates the query if saved query is selected', async () => { const { instance, services } = await mountWith({}); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSavedQueryUpdated')!({ - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: 'abc:def', language: 'lucene' }, - }, - }); + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( + { + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: 'abc:def', language: 'lucene' }, + }, + } + ); }); - expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'abc:def', language: 'lucene' }, }), @@ -1242,7 +1252,7 @@ describe('Lens App', () => { it('clears all existing unpinned filters when the active saved query is cleared', async () => { const { instance, services, lensStore } = await mountWith({}); act(() => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) @@ -1255,7 +1265,9 @@ describe('Lens App', () => { FilterManager.setFiltersStore([pinned], FilterStateStore.GLOBAL_STATE); act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); instance.update(); - act(() => instance.find(services.navigation.ui.TopNavMenu).prop('onClearSavedQuery')!()); + act(() => + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onClearSavedQuery')!() + ); instance.update(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ @@ -1269,7 +1281,7 @@ describe('Lens App', () => { it('updates the searchSessionId when the query is updated', async () => { const { instance, lensStore, services } = await mountWith({}); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSaved')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1279,14 +1291,16 @@ describe('Lens App', () => { }); }); act(() => { - instance.find(services.navigation.ui.TopNavMenu).prop('onSavedQueryUpdated')!({ - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: '', language: 'lucene' }, - }, - }); + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( + { + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: '', language: 'lucene' }, + }, + } + ); }); instance.update(); expect(lensStore.getState()).toEqual({ @@ -1299,7 +1313,7 @@ describe('Lens App', () => { it('updates the searchSessionId when the active saved query is cleared', async () => { const { instance, services, lensStore } = await mountWith({}); act(() => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) @@ -1312,7 +1326,9 @@ describe('Lens App', () => { FilterManager.setFiltersStore([pinned], FilterStateStore.GLOBAL_STATE); act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); instance.update(); - act(() => instance.find(services.navigation.ui.TopNavMenu).prop('onClearSavedQuery')!()); + act(() => + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onClearSavedQuery')!() + ); instance.update(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ @@ -1324,7 +1340,7 @@ describe('Lens App', () => { it('dispatches update to searchSessionId and dateRange when the user hits refresh', async () => { const { instance, services, lensStore } = await mountWith({}); act(() => - instance.find(services.navigation.ui.TopNavMenu).prop('onQuerySubmit')!({ + instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-7d', to: 'now' }, }) ); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 46233dc6db6c6..22bff02cb5925 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -6,7 +6,7 @@ */ import './app.scss'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBreadcrumb, EuiConfirmModal } from '@elastic/eui'; import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public'; @@ -79,6 +79,8 @@ export function App({ dashboardFeatureFlag, } = lensAppServices; + const saveAndExit = useRef<() => void>(); + const dispatch = useLensDispatch(); const dispatchSetState: DispatchSetState = useCallback( (state: Partial) => dispatch(setState(state)), @@ -115,6 +117,7 @@ export function App({ undefined ); const [isGoBackToVizEditorModalVisible, setIsGoBackToVizEditorModalVisible] = useState(false); + const [shouldCloseAndSaveTextBasedQuery, setShouldCloseAndSaveTextBasedQuery] = useState(false); const savedObjectId = (initialInput as LensByReferenceInput)?.savedObjectId; useEffect(() => { @@ -261,6 +264,12 @@ export function App({ initialContext, ]); + const switchDatasource = useCallback(() => { + if (saveAndExit && saveAndExit.current) { + saveAndExit.current(); + } + }, []); + const runSave = useCallback( (saveProps: SaveProps, options: { saveToLibrary: boolean }) => { dispatch(applyChanges()); @@ -274,7 +283,9 @@ export function App({ persistedDoc, onAppLeave, redirectTo, + switchDatasource, originatingApp: incomingState?.originatingApp, + textBasedLanguageSave: shouldCloseAndSaveTextBasedQuery, ...lensAppServices, }, saveProps, @@ -284,6 +295,7 @@ export function App({ if (newState) { dispatchSetState(newState); setIsSaveModalVisible(false); + setShouldCloseAndSaveTextBasedQuery(false); } }, () => { @@ -293,19 +305,20 @@ export function App({ ); }, [ - incomingState?.originatingApp, + dispatch, lastKnownDoc, - persistedDoc, getIsByValueMode, savedObjectsTagging, initialInput, redirectToOrigin, + persistedDoc, onAppLeave, redirectTo, + switchDatasource, + incomingState?.originatingApp, + shouldCloseAndSaveTextBasedQuery, lensAppServices, dispatchSetState, - dispatch, - setIsSaveModalVisible, ] ); @@ -392,6 +405,14 @@ export function App({ [dataViews, uiActions, http, notifications, uiSettings, data, initialContext, dispatch] ); + const onTextBasedSavedAndExit = useCallback(async ({ onSave, onCancel }) => { + setIsSaveModalVisible(true); + setShouldCloseAndSaveTextBasedQuery(true); + saveAndExit.current = () => { + onSave(); + }; + }, []); + return ( <>
@@ -416,6 +437,7 @@ export function App({ initialContext={initialContext} theme$={theme$} indexPatternService={indexPatternService} + onTextBasedSavedAndExit={onTextBasedSavedAndExit} /> {getLegacyUrlConflictCallout()} {(!isLoading || persistedDoc) && ( diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 47ccc5aac74e8..8875d2d0f9839 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -8,13 +8,16 @@ import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { useStore } from 'react-redux'; import { TopNavMenuData } from '@kbn/navigation-plugin/public'; import { downloadMultipleAs } from '@kbn/share-plugin/public'; import { tableHasFormulas } from '@kbn/data-plugin/common'; import { exporters, getEsQueryConfig } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ENABLE_SQL } from '../../common'; import { LensAppServices, LensTopNavActions, @@ -28,6 +31,7 @@ import { useLensDispatch, LensAppState, DispatchSetState, + switchAndCleanDatasource, } from '../state_management'; import { getIndexPatternsObjects, @@ -220,6 +224,7 @@ export const LensTopNavMenu = ({ theme$, indexPatternService, currentDoc, + onTextBasedSavedAndExit, }: LensTopNavMenuProps) => { const { data, @@ -254,7 +259,9 @@ export const LensTopNavMenu = ({ [dispatch] ); const [indexPatterns, setIndexPatterns] = useState([]); + const [dataViewsList, setDataViewsList] = useState([]); const [currentIndexPattern, setCurrentIndexPattern] = useState(); + const [isOnTextBasedMode, setIsOnTextBasedMode] = useState(false); const [rejectedIndexPatterns, setRejectedIndexPatterns] = useState([]); const dispatchChangeIndexPattern = React.useCallback( @@ -355,6 +362,25 @@ export const LensTopNavMenu = ({ } }, [indexPatterns]); + useEffect(() => { + const fetchDataViews = async () => { + const totalDataViewsList = []; + const dataViewsIds = await data.dataViews.getIds(); + for (let i = 0; i < dataViewsIds.length; i++) { + const d = await data.dataViews.get(dataViewsIds[i]); + totalDataViewsList.push(d); + } + setDataViewsList(totalDataViewsList); + }; + fetchDataViews(); + }, [data]); + + useEffect(() => { + if (typeof query === 'object' && query !== null && isOfAggregateQueryType(query)) { + setIsOnTextBasedMode(true); + } + }, [query]); + useEffect(() => { return () => { // Make sure to close the editors when unmounting @@ -363,7 +389,7 @@ export const LensTopNavMenu = ({ }; }, []); - const { TopNavMenu } = navigation.ui; + const { AggregateQueryTopNavMenu } = navigation.ui; const { from, to } = data.query.timefilter.timefilter.getTime(); const savingToLibraryPermitted = Boolean(isSaveable && application.capabilities.visualize.save); @@ -550,7 +576,7 @@ export const LensTopNavMenu = ({ ); return discover.locator!.getRedirectUrl({ - dataViewSpec: dataViews.indexPatterns[meta.id].spec, + dataViewSpec: dataViews.indexPatterns[meta.id]?.spec, timeRange: data.query.timefilter.timefilter.getTime(), filters: newFilters, query: newQuery, @@ -617,10 +643,30 @@ export const LensTopNavMenu = ({ if (newQuery) { if (!isEqual(newQuery, query)) { dispatchSetState({ query: newQuery }); + // check if query is text-based (sql, essql etc) and switchAndCleanDatasource + if (isOfAggregateQueryType(newQuery) && !isOnTextBasedMode) { + setIsOnTextBasedMode(true); + dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'textBasedLanguages', + visualizationId: visualization?.activeId, + currentIndexPatternId: currentIndexPattern?.id, + }) + ); + } } } }, - [data.query.timefilter.timefilter, data.search.session, dispatchSetState, query] + [ + currentIndexPattern?.id, + data.query.timefilter.timefilter, + data.search.session, + dispatch, + dispatchSetState, + isOnTextBasedMode, + query, + visualization?.activeId, + ] ); const onSavedWrapped = useCallback( @@ -722,6 +768,13 @@ export const LensTopNavMenu = ({ closeDataViewEditor.current = dataViewEditor.openEditor({ onSave: async (dataView) => { if (dataView.id) { + dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'indexpattern', + visualizationId: visualization?.activeId, + currentIndexPatternId: dataView?.id, + }) + ); dispatchChangeIndexPattern(dataView); setCurrentIndexPattern(dataView); } @@ -730,9 +783,16 @@ export const LensTopNavMenu = ({ }); } : undefined, - [canEditDataView, dataViewEditor, dispatchChangeIndexPattern] + [canEditDataView, dataViewEditor, dispatch, dispatchChangeIndexPattern, visualization?.activeId] ); + // setting that enables/disables SQL + const isSQLModeEnabled = uiSettings.get(ENABLE_SQL); + const supportedTextBasedLanguages = []; + if (isSQLModeEnabled) { + supportedTextBasedLanguages.push('SQL'); + } + const dataViewPickerProps = { trigger: { label: currentIndexPattern?.getName?.() || '', @@ -744,16 +804,42 @@ export const LensTopNavMenu = ({ onDataViewCreated: createNewDataView, adHocDataViews: indexPatterns.filter((pattern) => !pattern.isPersisted()), onChangeDataView: (newIndexPatternId: string) => { - const currentDataView = indexPatterns.find( + const currentDataView = dataViewsList.find( (indexPattern) => indexPattern.id === newIndexPatternId ); setCurrentIndexPattern(currentDataView); dispatchChangeIndexPattern(newIndexPatternId); + if (isOnTextBasedMode) { + dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'indexpattern', + visualizationId: visualization?.activeId, + currentIndexPatternId: newIndexPatternId, + }) + ); + setIsOnTextBasedMode(false); + } }, + textBasedLanguages: supportedTextBasedLanguages as DataViewPickerProps['textBasedLanguages'], }; + // text based languages errors should also appear to the unified search bar + const textBasedLanguageModeErrors: Error[] = []; + if (activeDatasourceId && allLoaded) { + if ( + datasourceMap[activeDatasourceId] && + datasourceMap[activeDatasourceId].getUnifiedSearchErrors + ) { + const errors = datasourceMap[activeDatasourceId].getUnifiedSearchErrors?.( + datasourceStates[activeDatasourceId].state + ); + if (errors) { + textBasedLanguageModeErrors.push(...errors); + } + } + } return ( - boolean; persistedDoc?: Document; originatingApp?: string; + textBasedLanguageSave?: boolean; + switchDatasource?: () => void; } & ExtraProps & LensAppServices, saveProps: SaveProps, @@ -211,6 +213,9 @@ export const runSaveLensVisualization = async ( onAppLeave, redirectTo, dashboardFeatureFlag, + textBasedLanguageSave, + switchDatasource, + application, } = props; if (!lastKnownDoc) { @@ -318,8 +323,12 @@ export const runSaveLensVisualization = async ( // remove editor state so the connection is still broken after reload stateTransfer.clearEditorState?.(APP_ID); - - redirectTo?.(newInput.savedObjectId); + if (textBasedLanguageSave) { + switchDatasource?.(); + application.navigateToApp('lens', { path: '/' }); + } else { + redirectTo?.(newInput.savedObjectId); + } return { isLinkedToOriginatingApp: false }; } diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts index 8a8c7bbe1f973..7030e58982ee5 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts @@ -81,6 +81,7 @@ describe('getLayerMetaInfo', () => { getSourceId: jest.fn(), getMaxPossibleNumValues: jest.fn(), getFilters: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }; mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI); expect( @@ -99,6 +100,7 @@ describe('getLayerMetaInfo', () => { getSourceId: jest.fn(), getMaxPossibleNumValues: jest.fn(), getFilters: jest.fn(() => ({ error: 'filters error' })), + isTextBasedLanguage: jest.fn(() => false), }; mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI); expect( @@ -158,6 +160,7 @@ describe('getLayerMetaInfo', () => { getVisualDefaults: jest.fn(), getSourceId: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), getFilters: jest.fn(() => ({ enabled: { kuery: [[{ language: 'kuery', query: 'memory > 40000' }]], diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts index 0390b516bcd4a..d5c264c5ade0c 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts @@ -14,6 +14,7 @@ import { FilterStateStore, TimeRange, EsQueryConfig, + isOfQueryType, } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; @@ -183,7 +184,7 @@ export function combineQueryAndFilters( lucene: [], }; - const allQueries = Array.isArray(query) ? query : query ? [query] : []; + const allQueries = Array.isArray(query) ? query : query && isOfQueryType(query) ? [query] : []; const nonEmptyQueries = allQueries.filter((q) => Boolean(q.query.trim())); [queries.lucene, queries.kuery] = partition(nonEmptyQueries, (q) => q.language === 'lucene'); diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index c57e580e1ad35..b2466c60e6e4e 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -119,6 +119,7 @@ export interface LensTopNavMenuProps { currentDoc: Document | undefined; theme$: Observable; indexPatternService: IndexPatternServiceAPI; + onTextBasedSavedAndExit: ({ onSave }: { onSave: () => void }) => Promise; } export interface HistoryLocationState { diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index 1740354de7880..c0ccd2a71ce2b 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -30,8 +30,10 @@ export * from './visualizations/gauge/gauge_visualization'; export * from './visualizations/gauge'; export * from './indexpattern_datasource/indexpattern'; +export { getTextBasedLanguagesDatasource } from './text_based_languages_datasource/text_based_languages'; export { createFormulaPublicApi } from './indexpattern_datasource/operations/definitions/formula/formula_public_api'; +export * from './text_based_languages_datasource'; export * from './indexpattern_datasource'; export * from './lens_ui_telemetry'; export * from './lens_ui_errors'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 93d336b9445e6..102b1e3f2dd27 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; +import type { Query, AggregateQuery } from '@kbn/es-query'; + import { createMockFramePublicAPI, mockVisualizationMap, @@ -25,6 +27,7 @@ import { mountWithProvider } from '../../../mocks'; import { LayerType, layerTypes } from '../../../../common'; import { ReactWrapper } from 'enzyme'; import { addLayer } from '../../../state_management'; +import { AddLayerButton } from './add_layer'; import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock'; jest.mock('../../../id_generator'); @@ -68,7 +71,8 @@ describe('ConfigPanel', () => { function prepareAndMountComponent( props: ReturnType, - customStoreProps?: Partial + customStoreProps?: Partial, + query?: Query | AggregateQuery ) { (generateId as jest.Mock).mockReturnValue(`newId`); return mountWithProvider( @@ -82,6 +86,7 @@ describe('ConfigPanel', () => { }, }, activeDatasourceId: 'testDatasource', + query: query as Query, }, storeDeps: mockStoreDeps({ datasourceMap: props.datasourceMap, @@ -466,4 +471,28 @@ describe('ConfigPanel', () => { expect(datasourceMap.testDatasource.initializeDimension).not.toHaveBeenCalled(); }); }); + + describe('text based languages', () => { + it('should not allow to add a new layer', async () => { + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ + { type: layerTypes.DATA, label: 'Data Layer' }, + { + type: layerTypes.REFERENCELINE, + label: 'Reference layer', + }, + ]); + datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ datasourceMap, visualizationMap }); + + const { instance } = await prepareAndMountComponent( + props, + {}, + { sql: 'SELECT * from "foo"' } + ); + expect(instance.find(AddLayerButton).exists()).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index c410bce76626d..77005154fa4e2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -8,6 +8,7 @@ import React, { useMemo, memo, useCallback } from 'react'; import { EuiForm } from '@elastic/eui'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { UPDATE_FILTER_REFERENCES_ACTION, UPDATE_FILTER_REFERENCES_TRIGGER, @@ -52,7 +53,7 @@ export function LayerPanels( } ) { const { activeVisualization, datasourceMap, indexPatternService } = props; - const { activeDatasourceId, visualization, datasourceStates } = useLensSelector( + const { activeDatasourceId, visualization, datasourceStates, query } = useLensSelector( (state) => state.lens ); @@ -185,6 +186,8 @@ export function LayerPanels( [dispatchLens, props.framePublicAPI.dataViews, props.indexPatternService] ); + const hideAddLayerButton = query && isOfAggregateQueryType(query); + return ( {layerIds.map((layerId, layerIndex) => ( @@ -264,16 +267,18 @@ export function LayerPanels( indexPatternService={indexPatternService} /> ))} - { - const layerId = generateId(); - dispatchLens(addLayer({ layerId, layerType })); - setNextFocusedLayerId(layerId); - }} - /> + {!hideAddLayerButton && ( + { + const layerId = generateId(); + dispatchLens(addLayer({ layerId, layerType })); + setNextFocusedLayerId(layerId); + }} + /> + )} ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/clone_layer_action.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/clone_layer_action.tsx index dbaa4aa0fd069..26d4c1f04f41a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/clone_layer_action.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/clone_layer_action.tsx @@ -13,6 +13,7 @@ interface CloneLayerAction { execute: () => void; layerIndex: number; activeVisualization: Visualization; + isTextBasedLanguage?: boolean; } export const getCloneLayerAction = (props: CloneLayerAction): LayerAction => { @@ -23,7 +24,7 @@ export const getCloneLayerAction = (props: CloneLayerAction): LayerAction => { return { execute: props.execute, displayName, - isCompatible: Boolean(props.activeVisualization.cloneLayer), + isCompatible: Boolean(props.activeVisualization.cloneLayer && !props.isTextBasedLanguage), icon: 'copy', 'data-test-subj': `lnsLayerClone--${props.layerIndex}`, }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx index 46abe207637a4..b9ca695882ef2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx @@ -31,6 +31,7 @@ export interface LayerActionsProps { isOnlyLayer: boolean; activeVisualization: Visualization; layerType?: LayerType; + isTextBasedLanguage?: boolean; core: Pick; } @@ -111,6 +112,7 @@ export const LayerActions = (props: LayerActionsProps) => { execute: props.onCloneLayer, layerIndex: props.layerIndex, activeVisualization: props.activeVisualization, + isTextBasedLanguage: props.isTextBasedLanguage, }), getRemoveLayerAction({ execute: props.onRemoveLayer, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index b3552c6fdf2e5..ed6d6b8c0553d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -307,6 +307,8 @@ export function LayerPanel( ); const { dataViews } = props.framePublicAPI; + const [datasource] = Object.values(framePublicAPI.datasourceLayers); + const isTextBasedLanguage = Boolean(datasource?.isTextBasedLanguage()); return ( <> @@ -337,6 +339,7 @@ export function LayerPanel( layerType={activeVisualization.getLayerType(layerId, visualizationState)} onRemoveLayer={onRemoveLayer} onCloneLayer={onCloneLayer} + isTextBasedLanguage={isTextBasedLanguage} core={core} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 0e2b7f88ca6f2..520f52ad3ea32 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -7,9 +7,7 @@ import './data_panel_wrapper.scss'; -import React, { useMemo, memo, useContext, useState, useEffect, useCallback } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { useMemo, memo, useContext, useEffect, useCallback } from 'react'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -24,7 +22,6 @@ import { VisualizationMap, } from '../../types'; import { - switchDatasource, useLensDispatch, updateDatasourceState, useLensSelector, @@ -180,53 +177,9 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { ], }; - const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false); - return ( <> - {Object.keys(props.datasourceMap).length > 1 && ( - setDatasourceSwitcher(true)} - iconType="gear" - /> - } - isOpen={showDatasourceSwitcher} - closePopover={() => setDatasourceSwitcher(false)} - panelPaddingSize="none" - anchorPosition="rightUp" - > - ( - { - setDatasourceSwitcher(false); - dispatchLens(switchDatasource({ newDatasourceId: datasourceId })); - }} - > - {datasourceId} - - ))} - /> - - )} {activeDatasourceId && !datasourceIsLoading && ( { getSourceId: jest.fn(), getFilters: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }; mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI); @@ -498,41 +499,6 @@ describe('editor_frame', () => { instance.unmount(); }); - it('should initialize other datasource on switch', async () => { - await act(async () => { - instance.find('button[data-test-subj="datasource-switch"]').simulate('click'); - }); - await act(async () => { - ( - document.querySelector( - '[data-test-subj="datasource-switch-testDatasource2"]' - ) as HTMLButtonElement - ).click(); - }); - instance.update(); - expect(mockDatasource2.initialize).toHaveBeenCalled(); - }); - - it('should call datasource render with new state on switch', async () => { - const initialState = {}; - mockDatasource2.initialize.mockReturnValue(initialState); - - instance.find('button[data-test-subj="datasource-switch"]').simulate('click'); - - await act(async () => { - ( - document.querySelector( - '[data-test-subj="datasource-switch-testDatasource2"]' - ) as HTMLButtonElement - ).click(); - }); - - expect(mockDatasource2.renderDataPanel).toHaveBeenCalledWith( - expect.any(Element), - expect.objectContaining({ state: initialState }) - ); - }); - it('should initialize other visualization on switch', async () => { switchTo('testVis2'); expect(mockVisualization2.initialize).toHaveBeenCalled(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index f1caa66ede1a2..c9b0358b81a0b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { Ast, fromExpression } from '@kbn/interpreter'; import { DatasourceStates } from '../../state_management'; import { Visualization, DatasourceMap, DatasourceLayers, IndexPatternMap } from '../../types'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index 261a84751d663..a2f36f0754708 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -570,6 +570,7 @@ describe('suggestion helpers', () => { getSourceId: jest.fn(), getFilters: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }, }, { activeId: 'testVis', state: {} }, @@ -607,6 +608,7 @@ describe('suggestion helpers', () => { getSourceId: jest.fn(), getFilters: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }, }; defaultParams[3] = { @@ -669,6 +671,7 @@ describe('suggestion helpers', () => { getSourceId: jest.fn(), getFilters: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }, }; mockVisualization1.getSuggestions.mockReturnValue([]); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 8bf5fae85c73d..0a6d0391c15a1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -196,12 +196,15 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const onRender$ = useCallback(() => { if (renderDeps.current) { const datasourceEvents = Object.values(renderDeps.current.datasourceMap).reduce( - (acc, datasource) => [ - ...acc, - ...(datasource.getRenderEventCounters?.( - renderDeps.current!.datasourceStates[datasource.id].state - ) ?? []), - ], + (acc, datasource) => { + if (!renderDeps.current!.datasourceStates[datasource.id]) return []; + return [ + ...acc, + ...(datasource.getRenderEventCounters?.( + renderDeps.current!.datasourceStates[datasource.id]?.state + ) ?? []), + ]; + }, [] ); let visualizationEvents: string[] = []; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 9acec09776207..1ce184e623e7a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -310,15 +310,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ const currentIndexPattern = indexPatterns[currentIndexPatternId]; const existingFieldsForIndexPattern = existingFields[currentIndexPattern?.title]; const visualizeGeoFieldTrigger = uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER); - const allFields = useMemo( - () => - visualizeGeoFieldTrigger - ? currentIndexPattern.fields - : currentIndexPattern.fields.filter( - ({ type }) => type !== 'geo_point' && type !== 'geo_shape' - ), - [currentIndexPattern.fields, visualizeGeoFieldTrigger] - ); + const allFields = useMemo(() => { + if (!currentIndexPattern) return []; + return visualizeGeoFieldTrigger + ? currentIndexPattern.fields + : currentIndexPattern.fields.filter( + ({ type }) => type !== 'geo_point' && type !== 'geo_shape' + ); + }, [currentIndexPattern, visualizeGeoFieldTrigger]); + const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); const availableFieldTypes = uniq([ ...uniq(allFields.map(getFieldType)).filter((type) => type in fieldTypeNames), @@ -327,13 +327,13 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ ]); const fieldInfoUnavailable = - existenceFetchFailed || existenceFetchTimeout || currentIndexPattern.hasRestrictions; + existenceFetchFailed || existenceFetchTimeout || currentIndexPattern?.hasRestrictions; const editPermission = indexPatternFieldEditor.userPermissions.editIndexPattern(); const unfilteredFieldGroups: FieldGroups = useMemo(() => { const containsData = (field: IndexPatternField) => { - const overallField = currentIndexPattern.getFieldByName(field.name); + const overallField = currentIndexPattern?.getFieldByName(field.name); return ( overallField && existingFieldsForIndexPattern && @@ -503,22 +503,24 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }, []); const refreshFieldList = useCallback(async () => { - const newlyMappedIndexPattern = await indexPatternService.loadIndexPatterns({ - patterns: [currentIndexPattern.id], - cache: {}, - onIndexPatternRefresh, - }); - indexPatternService.updateDataViewsState({ - indexPatterns: { - ...frame.dataViews.indexPatterns, - [currentIndexPattern.id]: newlyMappedIndexPattern[currentIndexPattern.id], - }, - }); + if (currentIndexPattern) { + const newlyMappedIndexPattern = await indexPatternService.loadIndexPatterns({ + patterns: [currentIndexPattern.id], + cache: {}, + onIndexPatternRefresh, + }); + indexPatternService.updateDataViewsState({ + indexPatterns: { + ...frame.dataViews.indexPatterns, + [currentIndexPattern.id]: newlyMappedIndexPattern[currentIndexPattern.id], + }, + }); + } // start a new session so all charts are refreshed data.search.session.start(); }, [ indexPatternService, - currentIndexPattern.id, + currentIndexPattern, onIndexPatternRefresh, frame.dataViews.indexPatterns, data.search.session, @@ -528,7 +530,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ () => editPermission ? async (fieldName?: string, uiAction: 'edit' | 'add' = 'edit') => { - const indexPatternInstance = await dataViews.get(currentIndexPattern.id); + const indexPatternInstance = await dataViews.get(currentIndexPattern?.id); closeFieldEditor.current = indexPatternFieldEditor.openEditor({ ctx: { dataView: indexPatternInstance, @@ -547,7 +549,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ [ editPermission, dataViews, - currentIndexPattern.id, + currentIndexPattern?.id, indexPatternFieldEditor, refreshFieldList, indexPatternService, @@ -558,7 +560,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ () => editPermission ? async (fieldName: string) => { - const indexPatternInstance = await dataViews.get(currentIndexPattern.id); + const indexPatternInstance = await dataViews.get(currentIndexPattern?.id); closeFieldEditor.current = indexPatternFieldEditor.openDeleteModal({ ctx: { dataView: indexPatternInstance, @@ -575,7 +577,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ } : undefined, [ - currentIndexPattern.id, + currentIndexPattern?.id, dataViews, editPermission, indexPatternFieldEditor, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 481701b1e7824..aefb4c7327463 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -1718,6 +1718,15 @@ describe('IndexPattern Data Source', () => { }); }); + describe('#createEmptyLayer', () => { + it('creates state with empty layers', () => { + expect(indexPatternDatasource.createEmptyLayer('index-pattern-id')).toEqual({ + currentIndexPatternId: 'index-pattern-id', + layers: {}, + }); + }); + }); + describe('#getLayers', () => { it('should list the current layers', () => { expect( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 1fcec0b6509bf..20d0df1358be7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -190,6 +190,13 @@ export function getIndexPatternDatasource({ }; }, + createEmptyLayer(indexPatternId: string) { + return { + currentIndexPatternId: indexPatternId, + layers: {}, + }; + }, + cloneLayer(state, layerId, newLayerId, getNewId) { return { ...state, @@ -218,7 +225,7 @@ export function getIndexPatternDatasource({ }, getLayers(state: IndexPatternPrivateState) { - return Object.keys(state.layers); + return Object.keys(state?.layers); }, removeColumn({ prevState, layerId, columnId, indexPatterns }) { @@ -314,7 +321,6 @@ export function getIndexPatternDatasource({ counts[uniqueLabel] = 0; return uniqueLabel; }; - Object.values(layers).forEach((layer) => { if (!layer.columns) { return; @@ -500,7 +506,7 @@ export function getIndexPatternDatasource({ filter: false, }; const operations = flatten( - Object.values(state.layers ?? {}).map((l) => + Object.values(state?.layers ?? {}).map((l) => Object.values(l.columns).map((c) => { if (c.timeShift) { additionalEvents.time_shift = true; @@ -568,6 +574,7 @@ export function getIndexPatternDatasource({ fields: [...new Set(fieldsPerColumn[colId] || [])], })); }, + isTextBasedLanguage: () => false, getOperationForColumnId: (columnId: string) => { if (layer && layer.columns[columnId]) { if (!isReferenced(layer, columnId)) { diff --git a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts index 6939a03300dbb..db7ab00de22e3 100644 --- a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts +++ b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts @@ -129,6 +129,7 @@ export function mockDataPlugin( get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })), }, dataViews: { + getIds: jest.fn().mockImplementation(jest.fn(async () => [])), get: jest.fn().mockImplementation((id) => Promise.resolve({ id, diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index de6f9342fd58b..3d169b643c2ce 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -20,6 +20,7 @@ export function createMockDatasource(id: string): DatasourceMock { getSourceId: jest.fn(), getFilters: jest.fn(), getMaxPossibleNumValues: jest.fn(), + isTextBasedLanguage: jest.fn(() => false), }; return { @@ -52,6 +53,7 @@ export function createMockDatasource(id: string): DatasourceMock { renderDimensionEditor: jest.fn(), getDropProps: jest.fn(), onDrop: jest.fn(), + createEmptyLayer: jest.fn(), // this is an additional property which doesn't exist on real datasources // but can be used to validate whether specific API mock functions are called diff --git a/x-pack/plugins/lens/public/mocks/services_mock.tsx b/x-pack/plugins/lens/public/mocks/services_mock.tsx index c97bfbb9373f0..81aa4e0617931 100644 --- a/x-pack/plugins/lens/public/mocks/services_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/services_mock.tsx @@ -104,9 +104,11 @@ export function makeDefaultServices( const navigationStartMock = navigationPluginMock.createStartContract(); - jest.spyOn(navigationStartMock.ui.TopNavMenu.prototype, 'constructor').mockImplementation(() => { - return
; - }); + jest + .spyOn(navigationStartMock.ui.AggregateQueryTopNavMenu.prototype, 'constructor') + .mockImplementation(() => { + return
; + }); function makeAttributeService(): LensAttributeService { const attributeServiceMock = mockAttributeService< diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 5f55f4ae9c2e0..6019f566e0ba6 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -56,6 +56,8 @@ import type { IndexPatternDatasourceSetupPlugins, FormulaPublicApi, } from './indexpattern_datasource'; +import type { TextBasedLanguagesDatasource as TextBasedLanguagesDatasourceType } from './text_based_languages_datasource'; + import type { XyVisualization as XyVisualizationType, XyVisualizationPluginSetupPlugins, @@ -230,6 +232,7 @@ export class LensPlugin { private editorFrameSetup: EditorFrameSetup | undefined; private queuedVisualizations: Array Promise)> = []; private indexpatternDatasource: IndexPatternDatasourceType | undefined; + private textBasedLanguagesDatasource: TextBasedLanguagesDatasourceType | undefined; private xyVisualization: XyVisualizationType | undefined; private legacyMetricVisualization: LegacyMetricVisualizationType | undefined; private metricVisualization: MetricVisualizationType | undefined; @@ -426,10 +429,12 @@ export class LensPlugin { PieVisualization, HeatmapVisualization, GaugeVisualization, + TextBasedLanguagesDatasource, } = await import('./async_services'); this.datatableVisualization = new DatatableVisualization(); this.editorFrameService = new EditorFrameService(); this.indexpatternDatasource = new IndexPatternDatasource(); + this.textBasedLanguagesDatasource = new TextBasedLanguagesDatasource(); this.xyVisualization = new XyVisualization(); this.legacyMetricVisualization = new LegacyMetricVisualization(); this.metricVisualization = new MetricVisualization(); @@ -453,6 +458,7 @@ export class LensPlugin { eventAnnotation, }; this.indexpatternDatasource.setup(core, dependencies); + this.textBasedLanguagesDatasource.setup(core, dependencies); this.xyVisualization.setup(core, dependencies); this.datatableVisualization.setup(core, dependencies); this.legacyMetricVisualization.setup(core, dependencies); diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx index b66a5f1d76545..35c0215a35c53 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx @@ -15,6 +15,7 @@ import { IndexPatternRef } from '../../types'; export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { label: string; title?: string; + isDisabled?: boolean; }; export function ChangeIndexPattern({ diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index 91481cd58bb0c..725c68e7a1429 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -34,6 +34,7 @@ export const { rollbackSuggestion, submitSuggestion, switchDatasource, + switchAndCleanDatasource, updateIndexPatterns, setToggleFullscreen, initEmpty, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index fba5c9c9abd54..fc536b30ddac6 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -9,6 +9,7 @@ import { EnhancedStore } from '@reduxjs/toolkit'; import type { Query } from '@kbn/es-query'; import { switchDatasource, + switchAndCleanDatasource, switchVisualization, setState, updateState, @@ -210,6 +211,57 @@ describe('lensSlice', () => { ); }); + describe('switching to a new datasource and modify the state', () => { + it('should switch active datasource and initialize new state', () => { + store.dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'testDatasource2', + visualizationId: 'testVis', + currentIndexPatternId: 'testIndexPatternId', + }) + ); + expect(store.getState().lens.activeDatasourceId).toEqual('testDatasource2'); + expect(store.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual(false); + expect(store.getState().lens.visualization.activeId).toEqual('testVis'); + }); + + it('should should switch active datasource and clean the datasource state', () => { + const datasource2State = { + layers: {}, + }; + const { store: customStore } = makeLensStore({ + preloadedState: { + datasourceStates: { + testDatasource: { + state: {}, + isLoading: false, + }, + testDatasource2: { + state: datasource2State, + isLoading: false, + }, + }, + }, + }); + + customStore.dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'testDatasource2', + visualizationId: 'testVis', + currentIndexPatternId: 'testIndexPatternId', + }) + ); + + expect(customStore.getState().lens.activeDatasourceId).toEqual('testDatasource2'); + expect(customStore.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual( + false + ); + expect(customStore.getState().lens.datasourceStates.testDatasource2.state).toStrictEqual( + {} + ); + }); + }); + describe('adding or removing layer', () => { const testDatasource = (datasourceId: string) => { return { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index b73d1f12ca868..725c60bfc22cb 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -57,10 +57,12 @@ export const getPreloadedState = ({ const initialDatasourceId = getInitialDatasourceId(datasourceMap); const datasourceStates: LensAppState['datasourceStates'] = {}; if (initialDatasourceId) { - datasourceStates[initialDatasourceId] = { - state: null, - isLoading: true, - }; + Object.keys(datasourceMap).forEach((datasourceId) => { + datasourceStates[datasourceId] = { + state: null, + isLoading: true, + }; + }); } const state = { @@ -130,6 +132,11 @@ export const submitSuggestion = createAction('lens/submitSuggestion'); export const switchDatasource = createAction<{ newDatasourceId: string; }>('lens/switchDatasource'); +export const switchAndCleanDatasource = createAction<{ + newDatasourceId: string; + visualizationId: string | null; + currentIndexPatternId?: string; +}>('lens/switchAndCleanDatasource'); export const navigateAway = createAction('lens/navigateAway'); export const loadInitial = createAction<{ initialInput?: LensEmbeddableInput; @@ -211,6 +218,7 @@ export const lensActions = { setToggleFullscreen, submitSuggestion, switchDatasource, + switchAndCleanDatasource, navigateAway, loadInitial, initEmpty, @@ -359,9 +367,11 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { const datasource = datasourceMap[datasourceId!]; return { ...datasourceState, - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), + ...(datasourceId === state.activeDatasourceId && { + state: isOnlyLayer + ? datasource.clearLayer(datasourceState.state, layerId) + : datasource.removeLayer(datasourceState.state, layerId), + }), }; } ); @@ -663,6 +673,55 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { activeDatasourceId: payload.newDatasourceId, }; }, + [switchAndCleanDatasource.type]: ( + state, + { + payload, + }: { + payload: { + newDatasourceId: string; + visualizationId?: string; + currentIndexPatternId?: string; + }; + } + ) => { + const activeVisualization = + payload.visualizationId && visualizationMap[payload.visualizationId]; + const visualization = state.visualization; + let newVizState = visualization.state; + const ids: string[] = []; + if (activeVisualization && activeVisualization.getLayerIds) { + const layerIds = activeVisualization.getLayerIds(visualization.state); + ids.push(...Object.values(layerIds)); + newVizState = activeVisualization.initialize(() => ids[0]); + } + const currentVizId = ids[0]; + + const datasourceState = current(state).datasourceStates[payload.newDatasourceId] + ? current(state).datasourceStates[payload.newDatasourceId]?.state + : datasourceMap[payload.newDatasourceId].createEmptyLayer( + payload.currentIndexPatternId ?? '' + ); + const updatedState = datasourceMap[payload.newDatasourceId].insertLayer( + datasourceState, + currentVizId + ); + + return { + ...state, + datasourceStates: { + [payload.newDatasourceId]: { + state: updatedState, + isLoading: false, + }, + }, + activeDatasourceId: payload.newDatasourceId, + visualization: { + ...visualization, + state: newVizState, + }, + }; + }, [navigateAway.type]: (state) => state, [loadInitial.type]: ( state, diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index ceae38967c77d..9399506f5fca1 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -7,7 +7,7 @@ import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import { EmbeddableEditorState } from '@kbn/embeddable-plugin/public'; -import { Filter, Query } from '@kbn/es-query'; +import type { Filter, Query } from '@kbn/es-query'; import { SavedQuery } from '@kbn/data-plugin/public'; import { Document } from '../persistence'; diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.test.tsx new file mode 100644 index 0000000000000..51ba02c1cdc6f --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.test.tsx @@ -0,0 +1,237 @@ +/* + * 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 ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import type { Query } from '@kbn/es-query'; + +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; +import { + dataViewPluginMocks, + Start as DataViewPublicStart, +} from '@kbn/data-views-plugin/public/mocks'; +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; +import { FieldButton } from '@kbn/react-field'; + +import { type TextBasedLanguagesDataPanelProps, TextBasedLanguagesDataPanel } from './datapanel'; + +import { coreMock } from '@kbn/core/public/mocks'; +import type { TextBasedLanguagesPrivateState } from './types'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; + +import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; +import { createIndexPatternServiceMock } from '../mocks/data_views_service_mock'; +import { createMockFramePublicAPI } from '../mocks'; +import { createMockedDragDropContext } from './mocks'; +import { DataViewsState } from '../state_management'; +import { ExistingFieldsMap, IndexPattern } from '../types'; + +const fieldsFromQuery = [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, +] as DatatableColumn[]; + +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'amemory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'unsupported', + displayName: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'client', + displayName: 'client', + type: 'ip', + aggregatable: true, + searchable: true, + }, +]; + +function getExistingFields(indexPatterns: Record) { + const existingFields: ExistingFieldsMap = {}; + for (const { title, fields } of Object.values(indexPatterns)) { + const fieldsMap: Record = {}; + for (const { displayName, name } of fields) { + fieldsMap[displayName ?? name] = true; + } + existingFields[title] = fieldsMap; + } + return existingFields; +} + +const initialState: TextBasedLanguagesPrivateState = { + layers: { + first: { + index: '1', + columns: [], + allColumns: [], + query: { sql: 'SELECT * FROM foo' }, + }, + }, + indexPatternRefs: [ + { id: '1', title: 'my-fake-index-pattern' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ], + fieldList: fieldsFromQuery, +}; + +function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial = {}) { + const frameAPI = createMockFramePublicAPI(); + const defaultIndexPatterns = { + '1': { + id: '1', + title: 'idx1', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: jest.fn(), + isPersisted: true, + spec: {}, + }, + }; + return { + ...frameAPI, + dataViews: { + ...frameAPI.dataViews, + indexPatterns: indexPatterns ?? defaultIndexPatterns, + existingFields: existingFields ?? getExistingFields(indexPatterns ?? defaultIndexPatterns), + isFirstExistenceFetch: false, + ...rest, + }, + }; +} + +// @ts-expect-error Portal mocks are notoriously difficult to type +ReactDOM.createPortal = jest.fn((element) => element); + +describe('TextBased Query Languages Data Panel', () => { + let core: ReturnType; + let dataViews: DataViewPublicStart; + + let defaultProps: TextBasedLanguagesDataPanelProps; + const dataViewsMock = dataViewPluginMocks.createStartContract(); + beforeEach(() => { + core = coreMock.createStart(); + dataViews = dataViewPluginMocks.createStartContract(); + defaultProps = { + data: dataPluginMock.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + dataViews: { + ...dataViewsMock, + getIdsWithTitle: jest.fn().mockReturnValue( + Promise.resolve([ + { id: '1', title: 'my-fake-index-pattern' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ]) + ), + }, + dragDropContext: createMockedDragDropContext(), + core, + dateRange: { + fromDate: 'now-7d', + toDate: 'now', + }, + query: { sql: 'SELECT * FROM my-fake-index-pattern' } as unknown as Query, + filters: [], + showNoDataPopover: jest.fn(), + dropOntoWorkspace: jest.fn(), + hasSuggestionForField: jest.fn(() => false), + uiActions: uiActionsPluginMock.createStartContract(), + indexPatternService: createIndexPatternServiceMock({ core, dataViews }), + frame: getFrameAPIMock(), + state: initialState, + setState: jest.fn(), + onChangeIndexPattern: jest.fn(), + }; + }); + + it('should render a search box', async () => { + const wrapper = mountWithIntl(); + expect(wrapper.find('[data-test-subj="lnsTextBasedLangugesFieldSearch"]').length).toEqual(1); + }); + + it('should list all supported fields in the pattern', async () => { + const wrapper = mountWithIntl(); + expect( + wrapper + .find('[data-test-subj="lnsTextBasedLanguagesPanelFields"]') + .find(FieldButton) + .map((fieldItem) => fieldItem.prop('fieldName')) + ).toEqual(['timestamp', 'bytes', 'memory']); + }); + + it('should list all supported fields in the pattern that match the search input', async () => { + const wrapper = mountWithIntl(); + const searchBox = wrapper.find('[data-test-subj="lnsTextBasedLangugesFieldSearch"]'); + + act(() => { + searchBox.prop('onChange')!({ + target: { value: 'mem' }, + } as React.ChangeEvent); + }); + + wrapper.update(); + expect( + wrapper + .find('[data-test-subj="lnsTextBasedLanguagesPanelFields"]') + .find(FieldButton) + .map((fieldItem) => fieldItem.prop('fieldName')) + ).toEqual(['memory']); + }); +}); diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.tsx new file mode 100644 index 0000000000000..9e15db381549d --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/datapanel.tsx @@ -0,0 +1,167 @@ +/* + * 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, { useState, useEffect, useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFormControlLayout, htmlIdGenerator } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import usePrevious from 'react-use/lib/usePrevious'; +import { isEqual } from 'lodash'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { FieldButton } from '@kbn/react-field'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { DatasourceDataPanelProps, DataType } from '../types'; +import type { TextBasedLanguagesPrivateState } from './types'; +import { getStateFromAggregateQuery } from './utils'; +import { DragDrop } from '../drag_drop'; +import { LensFieldIcon } from '../shared_components'; +import { ChildDragDropProvider } from '../drag_drop'; + +export type TextBasedLanguagesDataPanelProps = + DatasourceDataPanelProps & { + data: DataPublicPluginStart; + expressions: ExpressionsStart; + dataViews: DataViewsPublicPluginStart; + }; +const htmlId = htmlIdGenerator('datapanel-text-based-languages'); +const fieldSearchDescriptionId = htmlId(); + +export function TextBasedLanguagesDataPanel({ + setState, + state, + dragDropContext, + core, + data, + query, + filters, + dateRange, + expressions, + dataViews, +}: TextBasedLanguagesDataPanelProps) { + const prevQuery = usePrevious(query); + const [localState, setLocalState] = useState({ nameFilter: '' }); + const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '' })); + useEffect(() => { + async function fetchData() { + if (query && isOfAggregateQueryType(query) && !isEqual(query, prevQuery)) { + const stateFromQuery = await getStateFromAggregateQuery( + state, + query, + dataViews, + data, + expressions + ); + + setState(stateFromQuery); + } + } + fetchData(); + }, [data, dataViews, expressions, prevQuery, query, setState, state]); + + const { fieldList } = state; + const filteredFields = useMemo(() => { + return fieldList.filter((field) => { + if ( + localState.nameFilter && + !field.name.toLowerCase().includes(localState.nameFilter.toLowerCase()) + ) { + return false; + } + return true; + }); + }, [fieldList, localState.nameFilter]); + + return ( + + + + + { + clearLocalState(); + }, + }} + > + { + setLocalState({ ...localState, nameFilter: e.target.value }); + }} + aria-label={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', { + defaultMessage: 'Search field names', + description: 'Search the list of fields in the data view for the provided text', + })} + aria-describedby={fieldSearchDescriptionId} + /> + + + +
+
+
    + {filteredFields.length > 0 && + filteredFields.map((field, index) => ( +
  • + + {}} + fieldIcon={} + fieldName={field?.name} + /> + +
  • + ))} +
+
+
+
+
+
+
+ ); +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/fetch_data_from_aggregate_query.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/fetch_data_from_aggregate_query.ts new file mode 100644 index 0000000000000..b41289d11e8fb --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/fetch_data_from_aggregate_query.ts @@ -0,0 +1,65 @@ +/* + * 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 { pluck } from 'rxjs/operators'; +import { lastValueFrom } from 'rxjs'; +import { Query, AggregateQuery, Filter } from '@kbn/es-query'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/public'; +import type { DataViewsContract } from '@kbn/data-views-plugin/common'; +import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common'; + +interface TextBasedLanguagesErrorResponse { + error: { + message: string; + }; + type: 'error'; +} + +export function fetchDataFromAggregateQuery( + query: Query | AggregateQuery, + dataViewsService: DataViewsContract, + data: DataPublicPluginStart, + expressions: ExpressionsStart, + filters?: Filter[], + inputQuery?: Query +) { + const timeRange = data.query.timefilter.timefilter.getTime(); + return textBasedQueryStateToAstWithValidation({ + filters, + query, + time: timeRange, + dataViewsService, + inputQuery, + }) + .then((ast) => { + if (ast) { + const execution = expressions.run(ast, null); + let finalData: Datatable; + let error: string | undefined; + execution.pipe(pluck('result')).subscribe((resp) => { + const response = resp as Datatable | TextBasedLanguagesErrorResponse; + if (response.type === 'error') { + error = response.error.message; + } else { + finalData = response; + } + }); + return lastValueFrom(execution).then(() => { + if (error) { + throw new Error(error); + } else { + return finalData; + } + }); + } + return undefined; + }) + .catch((err) => { + throw new Error(err.message); + }); +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.test.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.test.tsx new file mode 100644 index 0000000000000..f1051f3b8f61d --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.test.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; + +import { FieldPicker, FieldOptionValue } from '../shared_components/field_picker'; + +import { FieldSelect, FieldSelectProps } from './field_select'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; + +const fields = [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, +] as DatatableColumn[]; + +describe('Layer Data Panel', () => { + let defaultProps: FieldSelectProps; + + beforeEach(() => { + defaultProps = { + selectedField: { + fieldName: 'bytes', + columnId: 'bytes', + meta: { + type: 'number', + }, + }, + existingFields: fields, + onChoose: jest.fn(), + }; + }); + + it('should display the selected field if given', () => { + const instance = shallow(); + expect(instance.find(FieldPicker).prop('selectedOptions')).toStrictEqual([ + { + label: 'bytes', + value: { + type: 'field', + field: 'bytes', + dataType: 'number', + }, + }, + ]); + }); + + it('should pass the fields with the correct format', () => { + const instance = shallow(); + expect(instance.find(FieldPicker).prop('options')).toStrictEqual([ + { + label: 'Available fields', + options: [ + { + compatible: true, + exists: true, + label: 'timestamp', + value: { + type: 'field' as FieldOptionValue['type'], + field: 'timestamp', + dataType: 'date', + }, + }, + { + compatible: true, + exists: true, + label: 'bytes', + value: { + type: 'field' as FieldOptionValue['type'], + field: 'bytes', + dataType: 'number', + }, + }, + { + compatible: true, + exists: true, + label: 'memory', + value: { + type: 'field' as FieldOptionValue['type'], + field: 'memory', + dataType: 'number', + }, + }, + ], + }, + ]); + }); +}); diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.tsx new file mode 100644 index 0000000000000..63153bc87d59e --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/field_select.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, { useMemo } from 'react'; +import { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; +import { FieldPicker, FieldOptionValue, FieldOption } from '../shared_components/field_picker'; +import type { TextBasedLanguagesLayerColumn } from './types'; +import type { DataType } from '../types'; + +export interface FieldSelectProps extends EuiComboBoxProps { + selectedField?: TextBasedLanguagesLayerColumn; + onChoose: (choice: FieldOptionValue) => void; + existingFields: DatatableColumn[]; +} + +export function FieldSelect({ + selectedField, + onChoose, + existingFields, + ['data-test-subj']: dataTestSub, +}: FieldSelectProps) { + const memoizedFieldOptions = useMemo(() => { + const availableFields = existingFields.map((field) => { + const dataType = field?.meta?.type as DataType; + return { + compatible: true, + exists: true, + label: field.name, + value: { + type: 'field' as FieldOptionValue['type'], + field: field.name, + dataType, + }, + }; + }); + return [ + { + label: i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', { + defaultMessage: 'Available fields', + }), + options: availableFields, + }, + ]; + }, [existingFields]); + + return ( + + selectedOptions={ + selectedField + ? ([ + { + label: selectedField.fieldName, + value: { + type: 'field', + field: selectedField.fieldName, + dataType: selectedField?.meta?.type, + }, + }, + ] as unknown as Array>) + : [] + } + options={memoizedFieldOptions as Array>} + onChoose={(choice) => { + if (choice && choice.field !== selectedField?.fieldName) { + onChoose(choice); + } + }} + fieldIsInvalid={false} + data-test-subj={dataTestSub ?? 'text-based-dimension-field'} + /> + ); +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/index.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/index.ts new file mode 100644 index 0000000000000..b2cffc5659dbf --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup } from '@kbn/core/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { EditorFrameSetup } from '../types'; + +export interface TextBasedLanguageSetupPlugins { + data: DataPublicPluginSetup; + editorFrame: EditorFrameSetup; +} + +export interface TextBasedLanguageStartPlugins { + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + expressions: ExpressionsStart; +} + +export class TextBasedLanguagesDatasource { + constructor() {} + + setup( + core: CoreSetup, + { editorFrame }: TextBasedLanguageSetupPlugins + ) { + editorFrame.registerDatasource(async () => { + const { getTextBasedLanguagesDatasource } = await import('../async_services'); + const [coreStart, { data, dataViews, expressions }] = await core.getStartServices(); + + return getTextBasedLanguagesDatasource({ + core: coreStart, + storage: new Storage(localStorage), + data, + dataViews, + expressions, + }); + }); + } +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.test.tsx new file mode 100644 index 0000000000000..7a3bf25b5e9e6 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.test.tsx @@ -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. + */ + +import React from 'react'; +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; +import { TextBasedLanguagesPrivateState } from './types'; +import type { DataViewsState } from '../state_management/types'; + +import { TextBasedLanguageLayerPanelProps, LayerPanel } from './layerpanel'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; +import { ChangeIndexPattern } from '../shared_components/dataview_picker/dataview_picker'; + +const fields = [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, +] as DatatableColumn[]; + +const initialState: TextBasedLanguagesPrivateState = { + layers: { + first: { + index: '1', + columns: [], + allColumns: [], + query: { sql: 'SELECT * FROM foo' }, + }, + }, + indexPatternRefs: [ + { id: '1', title: 'my-fake-index-pattern' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ], + fieldList: fields, +}; +describe('Layer Data Panel', () => { + let defaultProps: TextBasedLanguageLayerPanelProps; + + beforeEach(() => { + defaultProps = { + layerId: 'first', + state: initialState, + onChangeIndexPattern: jest.fn(), + dataViews: { + indexPatternRefs: [ + { id: '1', title: 'my-fake-index-pattern', name: 'My fake index pattern' }, + { id: '2', title: 'my-fake-restricted-pattern', name: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern', name: 'my-compatible-pattern' }, + ], + existingFields: {}, + isFirstExistenceFetch: false, + indexPatterns: {}, + } as DataViewsState, + }; + }); + + it('should display the selected dataview but disabled', () => { + const instance = shallow(); + expect(instance.find(ChangeIndexPattern).prop('trigger')).toStrictEqual({ + fontWeight: 'normal', + isDisabled: true, + label: 'My fake index pattern', + size: 's', + title: 'my-fake-index-pattern', + }); + }); +}); diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.tsx new file mode 100644 index 0000000000000..8f8e4a91242b1 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/layerpanel.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 from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { DatasourceLayerPanelProps } from '../types'; +import { TextBasedLanguagesPrivateState } from './types'; +import { ChangeIndexPattern } from '../shared_components/dataview_picker/dataview_picker'; + +export interface TextBasedLanguageLayerPanelProps + extends DatasourceLayerPanelProps { + state: TextBasedLanguagesPrivateState; +} + +export function LayerPanel({ state, layerId, dataViews }: TextBasedLanguageLayerPanelProps) { + const layer = state.layers[layerId]; + const dataView = dataViews.indexPatternRefs.find((ref) => ref.id === layer.index); + const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { + defaultMessage: 'Data view not found', + }); + return ( + + {}} + /> + + ); +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/mocks.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/mocks.ts new file mode 100644 index 0000000000000..adca02ab2299d --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/mocks.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 { DragContextState } from '../drag_drop'; + +export function createMockedDragDropContext(): jest.Mocked { + return { + dragging: undefined, + setDragging: jest.fn(), + activeDropTarget: undefined, + setActiveDropTarget: jest.fn(), + keyboardMode: false, + setKeyboardMode: jest.fn(), + setA11yMessage: jest.fn(), + dropTargetsByOrder: undefined, + registerDropTarget: jest.fn(), + }; +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts new file mode 100644 index 0000000000000..c2238bfd9fef1 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts @@ -0,0 +1,595 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; +import { TextBasedLanguagesPersistedState, TextBasedLanguagesPrivateState } from './types'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { getTextBasedLanguagesDatasource } from './text_based_languages'; +import { DatasourcePublicAPI, Datasource } from '../types'; + +jest.mock('../id_generator'); + +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + +const expectedIndexPatterns = { + 1: { + id: '1', + title: 'foo', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: jest.fn(), + spec: {}, + isPersisted: true, + }, +}; + +const indexPatterns = expectedIndexPatterns; + +describe('IndexPattern Data Source', () => { + let baseState: TextBasedLanguagesPrivateState; + let textBasedLanguagesDatasource: Datasource< + TextBasedLanguagesPrivateState, + TextBasedLanguagesPersistedState + >; + + beforeEach(() => { + textBasedLanguagesDatasource = getTextBasedLanguagesDatasource({ + storage: {} as IStorageWrapper, + core: coreMock.createStart(), + data: dataPluginMock.createStartContract(), + dataViews: dataViewPluginMocks.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + }); + + baseState = { + layers: { + a: { + columns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + ], + index: 'foo', + query: { sql: 'SELECT * FROM foo' }, + }, + }, + } as unknown as TextBasedLanguagesPrivateState; + }); + + describe('uniqueLabels', () => { + it('appends a suffix to duplicates', () => { + const map = textBasedLanguagesDatasource.uniqueLabels({ + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Foo', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Foo', + meta: { + type: 'number', + }, + }, + ], + index: 'foo', + }, + }, + } as unknown as TextBasedLanguagesPrivateState); + + expect(map).toMatchInlineSnapshot(` + Object { + "a": "Foo", + "b": "Foo [1]", + } + `); + }); + }); + + describe('#getPersistedState', () => { + it('should persist from saved state', async () => { + expect(textBasedLanguagesDatasource.getPersistableState(baseState)).toEqual({ + state: baseState, + savedObjectReferences: [ + { name: 'textBasedLanguages-datasource-layer-a', type: 'index-pattern', id: 'foo' }, + ], + }); + }); + }); + + describe('#insertLayer', () => { + it('should insert an empty layer into the previous state', () => { + expect(textBasedLanguagesDatasource.insertLayer(baseState, 'newLayer')).toEqual({ + ...baseState, + layers: { + ...baseState.layers, + newLayer: { + index: 'foo', + query: { sql: 'SELECT * FROM foo' }, + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + ], + columns: [], + }, + }, + }); + }); + }); + + describe('#removeLayer', () => { + it('should remove a layer', () => { + expect(textBasedLanguagesDatasource.removeLayer(baseState, 'a')).toEqual({ + ...baseState, + layers: { + a: { + columns: [], + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, + }, + }); + }); + }); + + describe('#createEmptyLayer', () => { + it('creates state with empty layers', () => { + expect(textBasedLanguagesDatasource.createEmptyLayer('index-pattern-id')).toEqual({ + fieldList: [], + layers: {}, + indexPatternRefs: [], + }); + }); + }); + + describe('#getLayers', () => { + it('should list the current layers', () => { + expect( + textBasedLanguagesDatasource.getLayers({ + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, + }, + } as unknown as TextBasedLanguagesPrivateState) + ).toEqual(['a']); + }); + }); + + describe('#getErrorMessages', () => { + it('should use the results of getErrorMessages directly when single layer', () => { + const state = { + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + errors: [new Error('error 1'), new Error('error 2')], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, + }, + } as unknown as TextBasedLanguagesPrivateState; + expect(textBasedLanguagesDatasource.getErrorMessages(state, indexPatterns)).toEqual([ + { longMessage: 'error 1', shortMessage: 'error 1' }, + { longMessage: 'error 2', shortMessage: 'error 2' }, + ]); + }); + }); + + describe('#isTimeBased', () => { + it('should return true if timefield name exists on the dataview', () => { + const state = { + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: '1', + }, + }, + } as unknown as TextBasedLanguagesPrivateState; + expect( + textBasedLanguagesDatasource.isTimeBased(state, { + ...indexPatterns, + }) + ).toEqual(true); + }); + it('should return false if timefield name not exists on the selected dataview', () => { + const state = { + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: '1', + }, + }, + } as unknown as TextBasedLanguagesPrivateState; + expect( + textBasedLanguagesDatasource.isTimeBased(state, { + ...indexPatterns, + '1': { ...indexPatterns['1'], timeFieldName: undefined }, + }) + ).toEqual(false); + }); + }); + + describe('#toExpression', () => { + it('should generate an empty expression when no columns are selected', async () => { + const state = textBasedLanguagesDatasource.initialize(); + expect(textBasedLanguagesDatasource.toExpression(state, 'first', indexPatterns)).toEqual( + null + ); + }); + + it('should generate an expression for an SQL query', async () => { + const queryBaseState = { + layers: { + a: { + columns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + allColumns: [ + { + columnId: 'a', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'b', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: '1', + }, + }, + indexPatternRefs: [ + { id: '1', title: 'foo' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ], + } as unknown as TextBasedLanguagesPrivateState; + + expect(textBasedLanguagesDatasource.toExpression(queryBaseState, 'a', indexPatterns)) + .toMatchInlineSnapshot(` + Object { + "chain": Array [ + Object { + "arguments": Object {}, + "function": "kibana", + "type": "function", + }, + Object { + "arguments": Object {}, + "function": "kibana_context", + "type": "function", + }, + Object { + "arguments": Object { + "query": Array [ + "SELECT * FROM foo", + ], + }, + "function": "essql", + "type": "function", + }, + Object { + "arguments": Object { + "idMap": Array [ + "{\\"Test 1\\":[{\\"id\\":\\"a\\",\\"label\\":\\"Test 1\\"}],\\"Test 2\\":[{\\"id\\":\\"b\\",\\"label\\":\\"Test 2\\"}]}", + ], + }, + "function": "lens_map_to_columns", + "type": "function", + }, + ], + "type": "expression", + } + `); + }); + }); + + describe('#getPublicAPI', () => { + let publicAPI: DatasourcePublicAPI; + + beforeEach(async () => { + publicAPI = textBasedLanguagesDatasource.getPublicAPI({ + state: baseState, + layerId: 'a', + indexPatterns, + }); + }); + + describe('getTableSpec', () => { + it('should include col1', () => { + expect(publicAPI.getTableSpec()).toEqual([expect.objectContaining({ columnId: 'col1' })]); + }); + + it('should include fields prop for each column', () => { + expect(publicAPI.getTableSpec()).toEqual([expect.objectContaining({ fields: ['Test 1'] })]); + }); + + it('should collect all fields ', () => { + const state = { + layers: { + a: { + columns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, + }, + { + columnId: 'col2', + fieldName: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + index: 'foo', + }, + }, + } as unknown as TextBasedLanguagesPrivateState; + + publicAPI = textBasedLanguagesDatasource.getPublicAPI({ + state, + layerId: 'a', + indexPatterns, + }); + expect(publicAPI.getTableSpec()).toEqual([ + { columnId: 'col1', fields: ['Test 1'] }, + { columnId: 'col2', fields: ['Test 2'] }, + ]); + }); + }); + + describe('getOperationForColumnId', () => { + it('should get an operation for col1', () => { + expect(publicAPI.getOperationForColumnId('col1')).toEqual({ + label: 'Test 1', + dataType: 'number', + isBucketed: false, + hasTimeShift: false, + }); + }); + + it('should return null for non-existant columns', () => { + expect(publicAPI.getOperationForColumnId('col2')).toBe(null); + }); + }); + + describe('getSourceId', () => { + it('should basically return the datasource internal id', () => { + expect(publicAPI.getSourceId()).toEqual('foo'); + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx new file mode 100644 index 0000000000000..5a03500c76fbf --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx @@ -0,0 +1,587 @@ +/* + * 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 'react-dom'; +import { I18nProvider } from '@kbn/i18n-react'; +import { CoreStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { AggregateQuery } from '@kbn/es-query'; +import type { SavedObjectReference } from '@kbn/core/public'; +import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + DatasourceDimensionEditorProps, + DatasourceDataPanelProps, + DatasourceLayerPanelProps, + PublicAPIProps, + DataType, + TableChangeType, + DatasourceDimensionTriggerProps, +} from '../types'; +import { generateId } from '../id_generator'; +import { toExpression } from './to_expression'; +import { TextBasedLanguagesDataPanel } from './datapanel'; +import type { + TextBasedLanguagesPrivateState, + TextBasedLanguagesPersistedState, + TextBasedLanguagesLayerColumn, + TextBasedLanguageField, +} from './types'; +import { FieldSelect } from './field_select'; +import { Datasource } from '../types'; +import { LayerPanel } from './layerpanel'; + +function getLayerReferenceName(layerId: string) { + return `textBasedLanguages-datasource-layer-${layerId}`; +} + +export function getTextBasedLanguagesDatasource({ + core, + storage, + data, + expressions, + dataViews, +}: { + core: CoreStart; + storage: IStorageWrapper; + data: DataPublicPluginStart; + expressions: ExpressionsStart; + dataViews: DataViewsPublicPluginStart; +}) { + const getSuggestionsForState = (state: TextBasedLanguagesPrivateState) => { + return Object.entries(state.layers)?.map(([id, layer]) => { + return { + state: { + ...state, + }, + table: { + changeType: 'unchanged' as TableChangeType, + isMultiRow: false, + layerId: id, + columns: + layer.columns?.map((f) => { + return { + columnId: f.columnId, + operation: { + dataType: f?.meta?.type as DataType, + label: f.fieldName, + isBucketed: Boolean(f?.meta?.type !== 'number'), + }, + }; + }) ?? [], + }, + keptLayerIds: [id], + }; + }); + }; + const TextBasedLanguagesDatasource: Datasource< + TextBasedLanguagesPrivateState, + TextBasedLanguagesPersistedState + > = { + id: 'textBasedLanguages', + + checkIntegrity: () => { + return []; + }, + getErrorMessages: (state) => { + const errors: Error[] = []; + + Object.values(state.layers).forEach((layer) => { + if (layer.errors && layer.errors.length > 0) { + errors.push(...layer.errors); + } + }); + return errors.map((err) => { + return { + shortMessage: err.message, + longMessage: err.message, + }; + }); + }, + getUnifiedSearchErrors: (state) => { + const errors: Error[] = []; + + Object.values(state.layers).forEach((layer) => { + if (layer.errors && layer.errors.length > 0) { + errors.push(...layer.errors); + } + }); + return errors; + }, + initialize( + state?: TextBasedLanguagesPersistedState, + savedObjectReferences?, + context?, + indexPatternRefs?, + indexPatterns? + ) { + const patterns = indexPatterns ? Object.values(indexPatterns) : []; + const refs = patterns.map((p) => { + return { + id: p.id, + title: p.title, + timeField: p.timeFieldName, + }; + }); + + const initState = state || { layers: {} }; + return { + ...initState, + fieldList: [], + indexPatternRefs: refs, + }; + }, + onRefreshIndexPattern() {}, + + getUsedDataViews: (state) => { + return Object.values(state.layers).map(({ index }) => index); + }, + + getPersistableState({ layers }: TextBasedLanguagesPrivateState) { + const savedObjectReferences: SavedObjectReference[] = []; + Object.entries(layers).forEach(([layerId, { index, ...persistableLayer }]) => { + if (index) { + savedObjectReferences.push({ + type: 'index-pattern', + id: index, + name: getLayerReferenceName(layerId), + }); + } + }); + return { state: { layers }, savedObjectReferences }; + }, + isValidColumn(state, indexPatterns, layerId, columnId) { + const layer = state.layers[layerId]; + const column = layer.columns.find((c) => c.columnId === columnId); + const indexPattern = indexPatterns[layer.index]; + if (!column || !indexPattern) return false; + return true; + }, + insertLayer(state: TextBasedLanguagesPrivateState, newLayerId: string) { + const layer = Object.values(state?.layers)?.[0]; + const query = layer?.query; + const columns = layer?.allColumns ?? []; + const index = + layer?.index ?? + (JSON.parse(localStorage.getItem('lens-settings') || '{}').indexPatternId || + state.indexPatternRefs[0].id); + return { + ...state, + layers: { + ...state.layers, + [newLayerId]: blankLayer(index, query, columns), + }, + }; + }, + createEmptyLayer() { + return { + indexPatternRefs: [], + layers: {}, + fieldList: [], + }; + }, + + cloneLayer(state, layerId, newLayerId, getNewId) { + return { + ...state, + }; + }, + + removeLayer(state: TextBasedLanguagesPrivateState, layerId: string) { + const newLayers = { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + columns: [], + }, + }; + + return { + ...state, + layers: newLayers, + fieldList: state.fieldList, + }; + }, + + clearLayer(state: TextBasedLanguagesPrivateState, layerId: string) { + return { + ...state, + layers: { + ...state.layers, + [layerId]: { ...state.layers[layerId], columns: [] }, + }, + }; + }, + + getLayers(state: TextBasedLanguagesPrivateState) { + return state && state.layers ? Object.keys(state?.layers) : []; + }, + getCurrentIndexPatternId(state: TextBasedLanguagesPrivateState) { + const layers = Object.values(state.layers); + return layers?.[0]?.index; + }, + isTimeBased: (state, indexPatterns) => { + if (!state) return false; + const { layers } = state; + return ( + Boolean(layers) && + Object.values(layers).some((layer) => { + return Boolean(indexPatterns[layer.index]?.timeFieldName); + }) + ); + }, + getUsedDataView: (state: TextBasedLanguagesPrivateState, layerId: string) => { + return state.layers[layerId].index; + }, + + removeColumn({ prevState, layerId, columnId }) { + return { + ...prevState, + layers: { + ...prevState.layers, + [layerId]: { + ...prevState.layers[layerId], + columns: prevState.layers[layerId].columns.filter((col) => col.columnId !== columnId), + }, + }, + }; + }, + + toExpression: (state, layerId, indexPatterns) => { + return toExpression(state, layerId); + }, + + renderDataPanel( + domElement: Element, + props: DatasourceDataPanelProps + ) { + render( + + + , + domElement + ); + }, + + renderDimensionTrigger: ( + domElement: Element, + props: DatasourceDimensionTriggerProps + ) => { + const columnLabelMap = TextBasedLanguagesDatasource.uniqueLabels(props.state); + const layer = props.state.layers[props.layerId]; + const selectedField = layer?.allColumns?.find((column) => column.columnId === props.columnId); + let customLabel: string | undefined = columnLabelMap[props.columnId]; + if (!customLabel) { + customLabel = selectedField?.fieldName; + } + + const columnExists = props.state.fieldList.some((f) => f.name === selectedField?.fieldName); + + render( + {}}> + {customLabel ?? + i18n.translate('xpack.lens.textBasedLanguages.missingField', { + defaultMessage: 'Missing field', + })} + , + domElement + ); + }, + + getRenderEventCounters(state: TextBasedLanguagesPrivateState): string[] { + return []; + }, + + renderDimensionEditor: ( + domElement: Element, + props: DatasourceDimensionEditorProps + ) => { + const fields = props.state.fieldList; + const selectedField = props.state.layers[props.layerId]?.allColumns?.find( + (column) => column.columnId === props.columnId + ); + render( + + { + const meta = fields.find((f) => f.name === choice.field)?.meta; + const newColumn = { + columnId: props.columnId, + fieldName: choice.field, + meta, + }; + return props.setState( + !selectedField + ? { + ...props.state, + layers: { + ...props.state.layers, + [props.layerId]: { + ...props.state.layers[props.layerId], + columns: [...props.state.layers[props.layerId].columns, newColumn], + allColumns: [...props.state.layers[props.layerId].allColumns, newColumn], + }, + }, + } + : { + ...props.state, + layers: { + ...props.state.layers, + [props.layerId]: { + ...props.state.layers[props.layerId], + columns: props.state.layers[props.layerId].columns.map((col) => + col.columnId !== props.columnId + ? col + : { ...col, fieldName: choice.field } + ), + allColumns: props.state.layers[props.layerId].allColumns.map((col) => + col.columnId !== props.columnId + ? col + : { ...col, fieldName: choice.field } + ), + }, + }, + } + ); + }} + /> + , + domElement + ); + }, + + renderLayerPanel: ( + domElement: Element, + props: DatasourceLayerPanelProps + ) => { + render( + + + , + domElement + ); + }, + + uniqueLabels(state: TextBasedLanguagesPrivateState) { + const layers = state.layers; + const columnLabelMap = {} as Record; + const counts = {} as Record; + + const makeUnique = (label: string) => { + let uniqueLabel = label; + + while (counts[uniqueLabel] >= 0) { + const num = ++counts[uniqueLabel]; + uniqueLabel = i18n.translate('xpack.lens.indexPattern.uniqueLabel', { + defaultMessage: '{label} [{num}]', + values: { label, num }, + }); + } + + counts[uniqueLabel] = 0; + return uniqueLabel; + }; + Object.values(layers).forEach((layer) => { + if (!layer.columns) { + return; + } + Object.values(layer.columns).forEach((column) => { + columnLabelMap[column.columnId] = makeUnique(column.fieldName); + }); + }); + + return columnLabelMap; + }, + + getDropProps: (props) => { + const { source } = props; + if (!source) { + return; + } + const label = source.field as string; + return { dropTypes: ['field_add'], nextLabel: label }; + }, + + onDrop: (props) => { + const { dropType, state, source, target } = props; + const { layers } = state; + + if (dropType === 'field_add') { + Object.keys(layers).forEach((layerId) => { + const currentLayer = layers[layerId]; + const field = currentLayer.allColumns.find((f) => f.columnId === source.id); + const newColumn = { + columnId: target.columnId, + fieldName: field?.fieldName ?? '', + meta: field?.meta, + }; + const columns = currentLayer.columns.filter((c) => c.columnId !== target.columnId); + columns.push(newColumn); + + const allColumns = currentLayer.allColumns.filter((c) => c.columnId !== target.columnId); + allColumns.push(newColumn); + + props.setState({ + ...props.state, + layers: { + ...props.state.layers, + [layerId]: { + ...props.state.layers[layerId], + columns, + allColumns, + }, + }, + }); + }); + return true; + } + return false; + }, + + getPublicAPI({ state, layerId }: PublicAPIProps) { + return { + datasourceId: 'textBasedLanguages', + + getTableSpec: () => { + return ( + state.layers[layerId]?.columns?.map((column) => ({ + columnId: column.columnId, + fields: [column.fieldName], + })) || [] + ); + }, + getOperationForColumnId: (columnId: string) => { + const layer = state.layers[layerId]; + const column = layer?.allColumns?.find((c) => c.columnId === columnId); + const columnLabelMap = TextBasedLanguagesDatasource.uniqueLabels(state); + + if (column) { + return { + dataType: column?.meta?.type as DataType, + label: columnLabelMap[columnId] ?? column?.fieldName, + isBucketed: Boolean(column?.meta?.type !== 'number'), + hasTimeShift: false, + }; + } + return null; + }, + getVisualDefaults: () => ({}), + isTextBasedLanguage: () => true, + getMaxPossibleNumValues: (columnId) => { + return null; + }, + getSourceId: () => { + const layer = state.layers[layerId]; + return layer.index; + }, + getFilters: () => { + return { + enabled: { + kuery: [], + lucene: [], + }, + disabled: { + kuery: [], + lucene: [], + }, + }; + }, + }; + }, + getDatasourceSuggestionsForField(state, draggedField) { + const field = state.fieldList.find( + (f) => f.id === (draggedField as TextBasedLanguageField).id + ); + if (!field) return []; + return Object.entries(state.layers)?.map(([id, layer]) => { + const newId = generateId(); + const newColumn = { + columnId: newId, + fieldName: field?.name ?? '', + meta: field?.meta, + }; + return { + state: { + ...state, + layers: { + ...state.layers, + [id]: { + ...state.layers[id], + columns: [...layer.columns, newColumn], + allColumns: [...layer.allColumns, newColumn], + }, + }, + }, + table: { + changeType: 'initial' as TableChangeType, + isMultiRow: false, + layerId: id, + columns: [ + ...layer.columns?.map((f) => { + return { + columnId: f.columnId, + operation: { + dataType: f?.meta?.type as DataType, + label: f.fieldName, + isBucketed: Boolean(f?.meta?.type !== 'number'), + }, + }; + }), + { + columnId: newId, + operation: { + dataType: field?.meta?.type as DataType, + label: field?.name ?? '', + isBucketed: Boolean(field?.meta?.type !== 'number'), + }, + }, + ], + }, + keptLayerIds: [id], + }; + }); + return []; + }, + getDatasourceSuggestionsForVisualizeField: getSuggestionsForState, + getDatasourceSuggestionsFromCurrentState: getSuggestionsForState, + getDatasourceSuggestionsForVisualizeCharts: getSuggestionsForState, + isEqual: () => true, + }; + + return TextBasedLanguagesDatasource; +} + +function blankLayer( + index: string, + query?: AggregateQuery, + columns?: TextBasedLanguagesLayerColumn[] +) { + return { + index, + query, + columns: [], + allColumns: columns ?? [], + }; +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/to_expression.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/to_expression.ts new file mode 100644 index 0000000000000..aa7a264673a3e --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/to_expression.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Ast } from '@kbn/interpreter'; +import { textBasedQueryStateToExpressionAst } from '@kbn/data-plugin/common'; +import type { OriginalColumn } from '../../common/types'; +import { TextBasedLanguagesPrivateState, TextBasedLanguagesLayer, IndexPatternRef } from './types'; + +function getExpressionForLayer( + layer: TextBasedLanguagesLayer, + refs: IndexPatternRef[] +): Ast | null { + if (!layer.columns || layer.columns?.length === 0) { + return null; + } + + let idMapper: Record = {}; + layer.columns.forEach((col) => { + if (idMapper[col.fieldName]) { + idMapper[col.fieldName].push({ + id: col.columnId, + label: col.fieldName, + } as OriginalColumn); + } else { + idMapper = { + ...idMapper, + [col.fieldName]: [ + { + id: col.columnId, + label: col.fieldName, + } as OriginalColumn, + ], + }; + } + }); + const timeFieldName = refs.find((r) => r.id === layer.index)?.timeField; + const textBasedQueryToAst = textBasedQueryStateToExpressionAst({ + query: layer.query, + timeFieldName, + }); + + textBasedQueryToAst.chain.push({ + type: 'function', + function: 'lens_map_to_columns', + arguments: { + idMap: [JSON.stringify(idMapper)], + }, + }); + return textBasedQueryToAst; +} + +export function toExpression(state: TextBasedLanguagesPrivateState, layerId: string) { + if (state.layers[layerId]) { + return getExpressionForLayer(state.layers[layerId], state.indexPatternRefs); + } + + return null; +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/types.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/types.ts new file mode 100644 index 0000000000000..2ea1692cf37c0 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/types.ts @@ -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 type { DatatableColumn } from '@kbn/expressions-plugin/public'; +import type { AggregateQuery } from '@kbn/es-query'; + +export interface TextBasedLanguagesLayerColumn { + columnId: string; + fieldName: string; + meta?: DatatableColumn['meta']; +} + +export interface TextBasedLanguageField { + id: string; + field: string; +} + +export interface TextBasedLanguagesLayer { + index: string; + query: AggregateQuery | undefined; + columns: TextBasedLanguagesLayerColumn[]; + allColumns: TextBasedLanguagesLayerColumn[]; + timeField?: string; + errors?: Error[]; +} + +export interface TextBasedLanguagesPersistedState { + layers: Record; +} + +export type TextBasedLanguagesPrivateState = TextBasedLanguagesPersistedState & { + indexPatternRefs: IndexPatternRef[]; + fieldList: DatatableColumn[]; +}; + +export interface IndexPatternRef { + id: string; + title: string; + timeField?: string; +} diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts new file mode 100644 index 0000000000000..1cbd830a92fc2 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts @@ -0,0 +1,193 @@ +/* + * 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 { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { mockDataViewsService } from '../data_views_service/mocks'; +import { + getIndexPatternFromTextBasedQuery, + loadIndexPatternRefs, + getStateFromAggregateQuery, +} from './utils'; +import { type AggregateQuery } from '@kbn/es-query'; + +jest.mock('./fetch_data_from_aggregate_query', () => ({ + fetchDataFromAggregateQuery: jest.fn(() => { + return { + columns: [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, + ], + }; + }), +})); + +describe('Text based languages utils', () => { + describe('getIndexPatternFromTextBasedQuery', () => { + it('should return the index pattern for sql query', () => { + const indexPattern = getIndexPatternFromTextBasedQuery({ + sql: 'SELECT bytes, memory from foo', + }); + + expect(indexPattern).toBe('foo'); + }); + + it('should return empty index pattern for non sql query', () => { + const indexPattern = getIndexPatternFromTextBasedQuery({ + lang1: 'SELECT bytes, memory from foo', + } as unknown as AggregateQuery); + + expect(indexPattern).toBe(''); + }); + }); + + describe('loadIndexPatternRefs', () => { + it('should return a list of sorted indexpattern refs', async () => { + const refs = await loadIndexPatternRefs(mockDataViewsService() as DataViewsPublicPluginStart); + expect(refs[0].title < refs[1].title).toBeTruthy(); + }); + }); + + describe('getStateFromAggregateQuery', () => { + it('should return the correct state', async () => { + const state = { + layers: { + first: { + allColumns: [], + columns: [], + query: undefined, + index: '', + }, + }, + }; + const dataViewsMock = dataViewPluginMocks.createStartContract(); + const dataMock = dataPluginMock.createStartContract(); + const expressionsMock = expressionsPluginMock.createStartContract(); + const updatedState = await getStateFromAggregateQuery( + state, + { sql: 'SELECT * FROM my-fake-index-pattern' }, + { + ...dataViewsMock, + getIdsWithTitle: jest.fn().mockReturnValue( + Promise.resolve([ + { id: '1', title: 'my-fake-index-pattern' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ]) + ), + get: jest.fn().mockReturnValue( + Promise.resolve({ + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timeField', + }) + ), + }, + dataMock, + expressionsMock + ); + + expect(updatedState).toStrictEqual({ + fieldList: [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, + ], + indexPatternRefs: [ + { + id: '3', + timeField: 'timeField', + title: 'my-compatible-pattern', + }, + { + id: '1', + timeField: 'timeField', + title: 'my-fake-index-pattern', + }, + { + id: '2', + timeField: 'timeField', + title: 'my-fake-restricted-pattern', + }, + ], + layers: { + first: { + allColumns: [ + { + fieldName: 'timestamp', + columnId: 'timestamp', + meta: { + type: 'date', + }, + }, + { + fieldName: 'bytes', + columnId: 'bytes', + meta: { + type: 'number', + }, + }, + { + fieldName: 'memory', + columnId: 'memory', + meta: { + type: 'number', + }, + }, + ], + columns: [], + errors: [], + index: '1', + query: { + sql: 'SELECT * FROM my-fake-index-pattern', + }, + timeField: 'timeField', + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts new file mode 100644 index 0000000000000..6c17d5206efb0 --- /dev/null +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts @@ -0,0 +1,99 @@ +/* + * 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 { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; + +import { type AggregateQuery, getIndexPatternFromSQLQuery } from '@kbn/es-query'; +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; +import { generateId } from '../id_generator'; +import { fetchDataFromAggregateQuery } from './fetch_data_from_aggregate_query'; + +import type { + IndexPatternRef, + TextBasedLanguagesPersistedState, + TextBasedLanguagesLayerColumn, +} from './types'; + +export async function loadIndexPatternRefs( + indexPatternsService: DataViewsPublicPluginStart +): Promise { + const indexPatterns = await indexPatternsService.getIdsWithTitle(); + + const timefields = await Promise.all( + indexPatterns.map((p) => indexPatternsService.get(p.id).then((pat) => pat.timeFieldName)) + ); + + return indexPatterns + .map((p, i) => ({ ...p, timeField: timefields[i] })) + .sort((a, b) => { + return a.title.localeCompare(b.title); + }); +} + +export async function getStateFromAggregateQuery( + state: TextBasedLanguagesPersistedState, + query: AggregateQuery, + dataViews: DataViewsPublicPluginStart, + data: DataPublicPluginStart, + expressions: ExpressionsStart +) { + const indexPatternRefs: IndexPatternRef[] = await loadIndexPatternRefs(dataViews); + const errors: Error[] = []; + const layerIds = Object.keys(state.layers); + const newLayerId = layerIds.length > 0 ? layerIds[0] : generateId(); + // fetch the pattern from the query + const indexPattern = getIndexPatternFromTextBasedQuery(query); + // get the id of the dataview + const index = indexPatternRefs.find((r) => r.title === indexPattern)?.id ?? ''; + let columnsFromQuery: DatatableColumn[] = []; + let columns: TextBasedLanguagesLayerColumn[] = []; + let timeFieldName; + try { + const table = await fetchDataFromAggregateQuery(query, dataViews, data, expressions); + const dataView = await dataViews.get(index); + timeFieldName = dataView.timeFieldName; + columnsFromQuery = table?.columns ?? []; + const existingColumns = state.layers[newLayerId].allColumns; + columns = [ + ...existingColumns, + ...columnsFromQuery.map((c) => ({ columnId: c.id, fieldName: c.id, meta: c.meta })), + ]; + } catch (e) { + errors.push(e); + } + + const tempState = { + layers: { + [newLayerId]: { + index, + query, + columns: state.layers[newLayerId].columns ?? [], + allColumns: columns, + timeField: timeFieldName, + errors, + }, + }, + }; + + return { + ...tempState, + fieldList: columnsFromQuery ?? [], + indexPatternRefs, + }; +} + +export function getIndexPatternFromTextBasedQuery(query: AggregateQuery): string { + let indexPattern = ''; + // sql queries + if ('sql' in query) { + indexPattern = getIndexPatternFromSQLQuery(query.sql); + } + // other textbased queries.... + + return indexPattern; +} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e581064bcdbf2..af2dcc601c996 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -259,8 +259,10 @@ export interface Datasource { // Given the current state, which parts should be saved? getPersistableState: (state: T) => { state: P; savedObjectReferences: SavedObjectReference[] }; getCurrentIndexPatternId: (state: T) => string; + getUnifiedSearchErrors?: (state: T) => Error[]; insertLayer: (state: T, newLayerId: string) => T; + createEmptyLayer: (indexPatternId: string) => T; removeLayer: (state: T, layerId: string) => T; clearLayer: (state: T, layerId: string) => T; cloneLayer: ( @@ -466,6 +468,10 @@ export interface DatasourcePublicAPI { * Retrieve the specific source id for the current state */ getSourceId: () => string | undefined; + /** + * Returns true if this is a text based language datasource + */ + isTextBasedLanguage: () => boolean; /** * Collect all defined filters from all the operations in the layer. If it returns undefined, this means that filters can't be constructed for the current layer */ diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index 071fa328486c9..72bf5812b7157 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -277,12 +277,12 @@ export const getDatatableVisualization = ({ .filter((c) => !datasource!.getOperationForColumnId(c)?.isBucketed) .map((accessor) => { const columnConfig = columnMap[accessor]; - const stops = columnConfig.palette?.params?.stops; - const hasColoring = Boolean(columnConfig.colorMode !== 'none' && stops); + const stops = columnConfig?.palette?.params?.stops; + const hasColoring = Boolean(columnConfig?.colorMode !== 'none' && stops); return { columnId: accessor, - triggerIcon: columnConfig.hidden + triggerIcon: columnConfig?.hidden ? 'invisible' : hasColoring ? 'colorBy' diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 1e9bf2a737964..7b619c78eaf9f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17119,7 +17119,6 @@ "xpack.lens.configure.invalidReferenceLineDimension": "La ligne de référence est affectée à un axe qui n’existe plus. Vous pouvez déplacer cette ligne de référence vers un autre axe disponible ou la supprimer.", "xpack.lens.confirmModal.cancelButtonLabel": "Annuler", "xpack.lens.customBucketContainer.dragToReorder": "Faire glisser pour réorganiser", - "xpack.lens.dataPanelWrapper.switchDatasource": "Basculer vers la source de données", "xpack.lens.datatable.addLayer": "Visualisation", "xpack.lens.datatable.breakdownColumns": "Colonnes", "xpack.lens.datatable.breakdownColumns.description": "Divisez les colonnes d'indicateurs par champ. Il est recommandé de conserver un faible nombre de colonnes pour éviter le défilement horizontal.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 493f2b2e7a885..214fe1234d7d3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17102,7 +17102,6 @@ "xpack.lens.configure.invalidReferenceLineDimension": "この基準線は存在しない軸に割り当てられています。この基準線を別の使用可能な軸に移動するか、削除することができます。", "xpack.lens.confirmModal.cancelButtonLabel": "キャンセル", "xpack.lens.customBucketContainer.dragToReorder": "ドラッグして並べ替え", - "xpack.lens.dataPanelWrapper.switchDatasource": "データソースに切り替える", "xpack.lens.datatable.addLayer": "ビジュアライゼーション", "xpack.lens.datatable.breakdownColumns": "列", "xpack.lens.datatable.breakdownColumns.description": "フィールドでメトリックを列に分割します。列数を少なくし、横スクロールを避けることをお勧めします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 45552726be4e8..aeaadfcb1faba 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17124,7 +17124,6 @@ "xpack.lens.configure.invalidReferenceLineDimension": "此参考线分配给了不再存在的轴。您可以将此参考线移到其他可用的轴,或将其移除。", "xpack.lens.confirmModal.cancelButtonLabel": "取消", "xpack.lens.customBucketContainer.dragToReorder": "拖动以重新排序", - "xpack.lens.dataPanelWrapper.switchDatasource": "切换到数据源", "xpack.lens.datatable.addLayer": "可视化", "xpack.lens.datatable.breakdownColumns": "列", "xpack.lens.datatable.breakdownColumns.description": "按字段拆分指标列。建议减少列数目以避免水平滚动。", diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts index 717f33c60181e..c01a43ce8c6ab 100644 --- a/x-pack/test/functional/apps/lens/group1/index.ts +++ b/x-pack/test/functional/apps/lens/group1/index.ts @@ -77,6 +77,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./persistent_context')); loadTestFile(require.resolve('./table_dashboard')); loadTestFile(require.resolve('./table')); + loadTestFile(require.resolve('./text_based_languages')); } }); }; diff --git a/x-pack/test/functional/apps/lens/group1/text_based_languages.ts b/x-pack/test/functional/apps/lens/group1/text_based_languages.ts new file mode 100644 index 0000000000000..d4766916d0da8 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/text_based_languages.ts @@ -0,0 +1,139 @@ +/* + * 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 { DebugState } from '@elastic/charts'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'visualize', + 'lens', + 'header', + 'unifiedSearch', + 'dashboard', + 'common', + ]); + const elasticChart = getService('elasticChart'); + const queryBar = getService('queryBar'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const monacoEditor = getService('monacoEditor'); + + function assertMatchesExpectedData(state: DebugState) { + expect(state.axes?.x![0].labels.sort()).to.eql(['css', 'gif', 'jpg', 'php', 'png']); + } + + const defaultSettings = { + 'discover:enableSql': true, + }; + + async function switchToTextBasedLanguage(language: string) { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.switchToTextBasedLanguage(language); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + describe('lens text based language tests', () => { + before(async () => { + await kibanaServer.uiSettings.replace(defaultSettings); + }); + it('should navigate to text based languages mode correctly', async () => { + await switchToTextBasedLanguage('SQL'); + expect(await testSubjects.exists('showQueryBarMenu')).to.be(false); + expect(await testSubjects.exists('addFilter')).to.be(false); + const textBasedQuery = await monacoEditor.getCodeEditorValue(); + expect(textBasedQuery).to.be('SELECT * FROM "log*"'); + }); + + it('should allow adding and using a field', async () => { + await monacoEditor.setCodeEditorValue( + 'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension' + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.switchToVisualization('lnsMetric'); + await PageObjects.lens.configureTextBasedLanguagesDimension({ + dimension: 'lnsMetric_primaryMetricDimensionPanel > lns-empty-dimension', + field: 'average', + }); + + await PageObjects.lens.waitForVisualization('mtrVis'); + const metricData = await PageObjects.lens.getMetricVisualizationData(); + expect(metricData[0].title).to.eql('average'); + }); + + it('should allow switching to another chart', async () => { + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.switchToVisualization('bar'); + await PageObjects.lens.configureTextBasedLanguagesDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + field: 'extension', + }); + + await PageObjects.lens.configureTextBasedLanguagesDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + field: 'average', + }); + + await PageObjects.lens.waitForVisualization('xyVisChart'); + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + assertMatchesExpectedData(data!); + }); + + it('should allow adding an text based languages chart to a dashboard', async () => { + await PageObjects.lens.switchToVisualization('lnsMetric'); + + await PageObjects.lens.waitForVisualization('mtrVis'); + await PageObjects.lens.removeDimension('lnsMetric_breakdownByDimensionPanel'); + await PageObjects.lens.waitForVisualization('mtrVis'); + const metricData = await PageObjects.lens.getMetricVisualizationData(); + expect(metricData[0].value).to.eql('5.7K'); + expect(metricData[0].title).to.eql('average'); + await PageObjects.lens.save('New text based languages viz', false, false, false, 'new'); + + await PageObjects.dashboard.waitForRenderComplete(); + expect(metricData[0].value).to.eql('5.7K'); + + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(1); + }); + + it('should allow saving the text based languages chart into a saved object', async () => { + await switchToTextBasedLanguage('SQL'); + await monacoEditor.setCodeEditorValue( + 'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension' + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.configureTextBasedLanguagesDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + field: 'extension', + }); + + await PageObjects.lens.configureTextBasedLanguagesDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + field: 'average', + }); + await PageObjects.lens.waitForVisualization('xyVisChart'); + await PageObjects.lens.save('Lens with text based language'); + await PageObjects.lens.waitForVisualization('xyVisChart'); + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + assertMatchesExpectedData(data!); + }); + + it('should allow to return to the dataview mode', async () => { + await PageObjects.lens.switchDataPanelIndexPattern('logstash-*', true); + expect(await testSubjects.exists('addFilter')).to.be(true); + expect(await queryBar.getQueryString()).to.be(''); + }); + }); +} diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 82820993e5715..56d5185a6583b 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -880,8 +880,15 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont /** * Changes the index pattern in the data panel */ - async switchDataPanelIndexPattern(dataViewTitle: string) { - await PageObjects.unifiedSearch.switchDataView('lns-dataView-switch-link', dataViewTitle); + async switchDataPanelIndexPattern( + dataViewTitle: string, + transitionFromTextBasedLanguages?: boolean + ) { + await PageObjects.unifiedSearch.switchDataView( + 'lns-dataView-switch-link', + dataViewTitle, + transitionFromTextBasedLanguages + ); await PageObjects.header.waitUntilLoadingHasFinished(); }, @@ -1297,6 +1304,35 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await PageObjects.unifiedSearch.createNewDataView(name, true); }, + async switchToTextBasedLanguage(language: string) { + await testSubjects.click('lns-dataView-switch-link'); + await PageObjects.unifiedSearch.selectTextBasedLanguage(language); + }, + + async configureTextBasedLanguagesDimension(opts: { + dimension: string; + field: string; + keepOpen?: boolean; + palette?: string; + }) { + await retry.try(async () => { + if (!(await testSubjects.exists('lns-indexPattern-dimensionContainerClose'))) { + await testSubjects.click(opts.dimension); + } + await testSubjects.existOrFail('lns-indexPattern-dimensionContainerClose'); + }); + + await this.selectOptionFromComboBox('text-based-dimension-field', opts.field); + + if (opts.palette) { + await this.setPalette(opts.palette); + } + + if (!opts.keepOpen) { + await this.closeDimensionEditor(); + } + }, + /** resets visualization/layer or removes a layer */ async removeLayer(index: number = 0) { await retry.try(async () => { From fa78386746004f5186702a20a121f5238f18e6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Casper=20H=C3=BCbertz?= Date: Tue, 27 Sep 2022 13:47:58 +0200 Subject: [PATCH 058/172] [Observability] Overview: Rename `Guided setup` to `Data assistant` (#141779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Observability] Remove progressbar and update name * [Observability] Update Obs tour step * [Observability] Update button and flyout name Also update the dismissed callout tour contents * Remove unused import * [Observability] Remove unused test * [Observability] Clean up unused functions and imports Co-authored-by: Casper Hübertz Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../observability_status_progress.test.tsx | 10 ------- .../observability_status_progress.tsx | 28 ++----------------- .../components/shared/tour/steps_config.ts | 2 +- .../public/components/shared/tour/tour.tsx | 2 +- .../overview_page/overview_page.tsx | 10 +++---- 5 files changed, 9 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.test.tsx b/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.test.tsx index 6e79c3691402a..38626ad0364cc 100644 --- a/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.test.tsx +++ b/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.test.tsx @@ -30,16 +30,6 @@ describe('ObservabilityStatusProgress', () => { forceUpdate: '', } as HasDataContextValue); }); - it('should render the progress', () => { - render( - - - - ); - const progressBar = screen.getByRole('progressbar') as HTMLProgressElement; - expect(progressBar).toBeInTheDocument(); - expect(progressBar.value).toBe(50); - }); it('should call the onViewDetailsCallback when view details button is clicked', () => { render( diff --git a/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.tsx b/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.tsx index 050da44457969..e08ffe0fb3c4e 100644 --- a/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.tsx +++ b/x-pack/plugins/observability/public/components/app/observability_status/observability_status_progress.tsx @@ -4,10 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { EuiPanel, - EuiProgress, EuiTitle, EuiButtonEmpty, EuiButton, @@ -16,9 +15,7 @@ import { EuiFlexItem, EuiSpacer, } from '@elastic/eui'; -import { reduce } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useHasData } from '../../../hooks/use_has_data'; import { useUiTracker } from '../../../hooks/use_track_metric'; import { useGuidedSetupProgress } from '../../../hooks/use_guided_setup_progress'; @@ -30,28 +27,9 @@ export function ObservabilityStatusProgress({ onViewDetailsClick, onDismissClick, }: ObservabilityStatusProgressProps) { - const { hasDataMap, isAllRequestsComplete } = useHasData(); const trackMetric = useUiTracker({ app: 'observability-overview' }); const { isGuidedSetupProgressDismissed, dismissGuidedSetupProgress } = useGuidedSetupProgress(); - const [progress, setProgress] = useState(0); - - useEffect(() => { - const totalCounts = Object.keys(hasDataMap); - if (isAllRequestsComplete) { - const hasDataCount = reduce( - hasDataMap, - (result, value) => { - return value?.hasData ? result + 1 : result; - }, - 0 - ); - - const percentage = (hasDataCount / totalCounts.length) * 100; - setProgress(isFinite(percentage) ? percentage : 0); - } - }, [isAllRequestsComplete, hasDataMap]); - const dismissGuidedSetup = useCallback(() => { dismissGuidedSetupProgress(); if (onDismissClick) { @@ -68,15 +46,13 @@ export function ObservabilityStatusProgress({ return !isGuidedSetupProgressDismissed ? ( <> - -

diff --git a/x-pack/plugins/observability/public/components/shared/tour/steps_config.ts b/x-pack/plugins/observability/public/components/shared/tour/steps_config.ts index 0c5e101aac9ad..50200d22f0ad5 100644 --- a/x-pack/plugins/observability/public/components/shared/tour/steps_config.ts +++ b/x-pack/plugins/observability/public/components/shared/tour/steps_config.ts @@ -112,7 +112,7 @@ export const tourStepsConfig: TourStep[] = [ }), content: i18n.translate('xpack.observability.tour.guidedSetupStep.tourContent', { defaultMessage: - 'The easiest way to get going with Elastic Observability is to follow the Guided setup.', + 'The easiest way to get going with Elastic Observability is to follow the steps in the data assistant.', }), anchor: '#guidedSetupButton', anchorPosition: 'rightUp', diff --git a/x-pack/plugins/observability/public/components/shared/tour/tour.tsx b/x-pack/plugins/observability/public/components/shared/tour/tour.tsx index 98b6bb7e0a38a..0c9e9ae65b058 100644 --- a/x-pack/plugins/observability/public/components/shared/tour/tour.tsx +++ b/x-pack/plugins/observability/public/components/shared/tour/tour.tsx @@ -239,7 +239,7 @@ export function ObservabilityTour({ }, [activeStep]); useEffect(() => { - // The user must be on the overview page to view the guided setup step in the tour + // The user must be on the overview page to view the data assistant step in the tour if (isTourActive && isOverviewPage === false && activeStep === guidedSetupStep) { navigateToApp(observabilityAppId, { path: overviewPath, diff --git a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx index e8a90cac969c6..16ccd0e1fdc06 100644 --- a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/overview_page.tsx @@ -232,7 +232,7 @@ export function OverviewPage() { >

@@ -292,7 +292,7 @@ function PageHeader({ color="text" iconType="wrench" onClick={() => { - // End the Observability tour if it's visible and the user clicks the guided setup button + // End the Observability tour if it's visible and the user clicks the data assistant button if (isObservabilityTourVisible) { endObservabilityTour(); } @@ -301,7 +301,7 @@ function PageHeader({ > {showTour ? ( @@ -310,13 +310,13 @@ function PageHeader({ anchor={() => buttonRef.current} isStepOpen title={i18n.translate('xpack.observability.overview.guidedSetupTourTitle', { - defaultMessage: 'Guided setup is always available', + defaultMessage: 'Data assistant is always available', })} content={ } From 3c04a1f212821b443e79a71d0567535fec8f62d0 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 27 Sep 2022 06:56:46 -0500 Subject: [PATCH 059/172] [data views] Allow data view rename via rest api (#141869) * allow rename of data view via rest api --- docs/api/data-views/update.asciidoc | 2 + docs/api/index-patterns/update.asciidoc | 2 + .../rest_api_routes/update_data_view.ts | 7 ++++ .../constants.ts | 0 .../create_data_view}/index.ts | 0 .../data_views_crud/create_data_view}/main.ts | 0 .../create_data_view}/validation.ts | 0 .../delete_data_view}/errors.ts | 0 .../delete_data_view}/index.ts | 0 .../data_views_crud/delete_data_view}/main.ts | 0 .../data_views_crud/get_data_view}/errors.ts | 0 .../data_views_crud/get_data_view}/index.ts | 0 .../data_views_crud/get_data_view}/main.ts | 0 .../data_views_crud}/get_data_views/index.ts | 0 .../data_views_crud}/get_data_views/main.ts | 0 .../data_views_crud}/index.ts | 8 ++-- .../update_data_view}/errors.ts | 0 .../update_data_view}/index.ts | 0 .../data_views_crud/update_data_view}/main.ts | 41 +++++++++++++++---- .../default_index_pattern.ts | 0 .../default_index_pattern/index.ts | 0 .../deprecations/index.ts | 0 .../deprecations/scripted_fields.ts | 0 .../es_errors/errors.js | 0 .../es_errors/index.js | 0 .../es_errors/lib/get_es_errors.js | 0 .../es_errors/lib/index.js | 0 .../fields_api/index.ts | 0 .../fields_api/update_fields/errors.ts | 0 .../fields_api/update_fields/index.ts | 0 .../fields_api/update_fields/main.ts | 0 .../fields_for_wildcard_route/conflicts.js | 0 .../fields_for_wildcard_route/filter.ts | 0 .../fields_for_wildcard_route/index.js | 0 .../fields_for_wildcard_route/params.js | 0 .../fields_for_wildcard_route/response.js | 0 .../has_user_index_pattern.ts | 0 .../has_user_index_pattern/index.ts | 0 .../{index_patterns => data_views}/index.js | 2 +- .../integration/index.ts | 0 .../integration/integration.ts | 0 .../create_runtime_field/errors.ts | 0 .../create_runtime_field/index.ts | 0 .../create_runtime_field/main.ts | 0 .../delete_runtime_field/errors.ts | 0 .../delete_runtime_field/index.ts | 0 .../delete_runtime_field/main.ts | 0 .../get_runtime_field/errors.ts | 0 .../get_runtime_field/index.ts | 0 .../get_runtime_field/main.ts | 0 .../runtime_fields_crud/index.ts | 0 .../put_runtime_field/errors.ts | 0 .../put_runtime_field/index.ts | 0 .../put_runtime_field/main.ts | 0 .../update_runtime_field/errors.ts | 0 .../update_runtime_field/index.ts | 0 .../update_runtime_field/main.ts | 0 .../create_scripted_field/errors.ts | 0 .../create_scripted_field/index.ts | 0 .../create_scripted_field/main.ts | 0 .../delete_scripted_field/errors.ts | 0 .../delete_scripted_field/index.ts | 0 .../delete_scripted_field/main.ts | 0 .../get_scripted_field/errors.ts | 0 .../get_scripted_field/index.ts | 0 .../get_scripted_field/main.ts | 0 .../scripted_fields_crud/index.ts | 0 .../put_scripted_field/errors.ts | 0 .../put_scripted_field/index.ts | 0 .../put_scripted_field/main.ts | 0 .../update_scripted_field/errors.ts | 0 .../update_scripted_field/index.ts | 0 .../update_scripted_field/main.ts | 0 test/api_integration/apis/index.ts | 2 +- 74 files changed, 51 insertions(+), 13 deletions(-) rename test/api_integration/apis/{index_patterns => data_views}/constants.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/create_index_pattern => data_views/data_views_crud/create_data_view}/index.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/create_index_pattern => data_views/data_views_crud/create_data_view}/main.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/create_index_pattern => data_views/data_views_crud/create_data_view}/validation.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/delete_index_pattern => data_views/data_views_crud/delete_data_view}/errors.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/delete_index_pattern => data_views/data_views_crud/delete_data_view}/index.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/delete_index_pattern => data_views/data_views_crud/delete_data_view}/main.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/get_index_pattern => data_views/data_views_crud/get_data_view}/errors.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/get_index_pattern => data_views/data_views_crud/get_data_view}/index.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/get_index_pattern => data_views/data_views_crud/get_data_view}/main.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud => data_views/data_views_crud}/get_data_views/index.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud => data_views/data_views_crud}/get_data_views/main.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud => data_views/data_views_crud}/index.ts (71%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/update_index_pattern => data_views/data_views_crud/update_data_view}/errors.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/update_index_pattern => data_views/data_views_crud/update_data_view}/index.ts (100%) rename test/api_integration/apis/{index_patterns/index_pattern_crud/update_index_pattern => data_views/data_views_crud/update_data_view}/main.ts (88%) rename test/api_integration/apis/{index_patterns => data_views}/default_index_pattern/default_index_pattern.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/default_index_pattern/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/deprecations/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/deprecations/scripted_fields.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/es_errors/errors.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/es_errors/index.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/es_errors/lib/get_es_errors.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/es_errors/lib/index.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_api/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_api/update_fields/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_api/update_fields/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_api/update_fields/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_for_wildcard_route/conflicts.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_for_wildcard_route/filter.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_for_wildcard_route/index.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_for_wildcard_route/params.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/fields_for_wildcard_route/response.js (100%) rename test/api_integration/apis/{index_patterns => data_views}/has_user_index_pattern/has_user_index_pattern.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/has_user_index_pattern/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/index.js (94%) rename test/api_integration/apis/{index_patterns => data_views}/integration/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/integration/integration.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/create_runtime_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/create_runtime_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/create_runtime_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/delete_runtime_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/delete_runtime_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/delete_runtime_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/get_runtime_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/get_runtime_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/get_runtime_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/put_runtime_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/put_runtime_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/put_runtime_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/update_runtime_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/update_runtime_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/runtime_fields_crud/update_runtime_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/create_scripted_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/create_scripted_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/create_scripted_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/delete_scripted_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/delete_scripted_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/delete_scripted_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/get_scripted_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/get_scripted_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/get_scripted_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/put_scripted_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/put_scripted_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/put_scripted_field/main.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/update_scripted_field/errors.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/update_scripted_field/index.ts (100%) rename test/api_integration/apis/{index_patterns => data_views}/scripted_fields_crud/update_scripted_field/main.ts (100%) diff --git a/docs/api/data-views/update.asciidoc b/docs/api/data-views/update.asciidoc index c1473b8f7079b..790656e6d6057 100644 --- a/docs/api/data-views/update.asciidoc +++ b/docs/api/data-views/update.asciidoc @@ -39,6 +39,7 @@ the data view is updated. The default is `false`. You can partially update the following fields: * `title` +* `name` * `timeFieldName` * `fields` * `sourceFilters` @@ -93,6 +94,7 @@ $ curl -X POST api/data_views/data-view/my-view { "data_view": { "title": "...", + "name": "...", "timeFieldName": "...", "sourceFilters": [], "fieldFormats": {}, diff --git a/docs/api/index-patterns/update.asciidoc b/docs/api/index-patterns/update.asciidoc index 64479f00ef5c5..809c01500b7f9 100644 --- a/docs/api/index-patterns/update.asciidoc +++ b/docs/api/index-patterns/update.asciidoc @@ -38,6 +38,7 @@ the index pattern is updated. The default is `false`. You can partially update the following fields: * `title` +* `name` * `timeFieldName` * `fields` * `sourceFilters` @@ -90,6 +91,7 @@ $ curl -X POST api/index_patterns/index-pattern/my-pattern { "index_pattern": { "title": "...", + "name": "...", "timeFieldName": "...", "sourceFilters": [], "fieldFormats": {}, diff --git a/src/plugins/data_views/server/rest_api_routes/update_data_view.ts b/src/plugins/data_views/server/rest_api_routes/update_data_view.ts index 22598a8251096..0e9f0983ac64d 100644 --- a/src/plugins/data_views/server/rest_api_routes/update_data_view.ts +++ b/src/plugins/data_views/server/rest_api_routes/update_data_view.ts @@ -37,6 +37,7 @@ const indexPatternUpdateSchema = schema.object({ fields: schema.maybe(schema.recordOf(schema.string(), fieldSpecSchema)), allowNoIndex: schema.maybe(schema.boolean()), runtimeFieldMap: schema.maybe(schema.recordOf(schema.string(), runtimeFieldSchema)), + name: schema.maybe(schema.string()), }); interface UpdateDataViewArgs { @@ -67,6 +68,7 @@ export const updateDataView = async ({ typeMeta, fields, runtimeFieldMap, + name, } = spec; let changeCount = 0; @@ -102,6 +104,11 @@ export const updateDataView = async ({ dataView.typeMeta = typeMeta; } + if (name !== undefined) { + changeCount++; + dataView.name = name; + } + if (fields !== undefined) { changeCount++; doRefreshFields = true; diff --git a/test/api_integration/apis/index_patterns/constants.ts b/test/api_integration/apis/data_views/constants.ts similarity index 100% rename from test/api_integration/apis/index_patterns/constants.ts rename to test/api_integration/apis/data_views/constants.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/index.ts b/test/api_integration/apis/data_views/data_views_crud/create_data_view/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/index.ts rename to test/api_integration/apis/data_views/data_views_crud/create_data_view/index.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts b/test/api_integration/apis/data_views/data_views_crud/create_data_view/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts rename to test/api_integration/apis/data_views/data_views_crud/create_data_view/main.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts b/test/api_integration/apis/data_views/data_views_crud/create_data_view/validation.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts rename to test/api_integration/apis/data_views/data_views_crud/create_data_view/validation.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/errors.ts b/test/api_integration/apis/data_views/data_views_crud/delete_data_view/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/errors.ts rename to test/api_integration/apis/data_views/data_views_crud/delete_data_view/errors.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/index.ts b/test/api_integration/apis/data_views/data_views_crud/delete_data_view/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/index.ts rename to test/api_integration/apis/data_views/data_views_crud/delete_data_view/index.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/main.ts b/test/api_integration/apis/data_views/data_views_crud/delete_data_view/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/delete_index_pattern/main.ts rename to test/api_integration/apis/data_views/data_views_crud/delete_data_view/main.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/errors.ts b/test/api_integration/apis/data_views/data_views_crud/get_data_view/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/errors.ts rename to test/api_integration/apis/data_views/data_views_crud/get_data_view/errors.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/index.ts b/test/api_integration/apis/data_views/data_views_crud/get_data_view/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/index.ts rename to test/api_integration/apis/data_views/data_views_crud/get_data_view/index.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/main.ts b/test/api_integration/apis/data_views/data_views_crud/get_data_view/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/get_index_pattern/main.ts rename to test/api_integration/apis/data_views/data_views_crud/get_data_view/main.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/get_data_views/index.ts b/test/api_integration/apis/data_views/data_views_crud/get_data_views/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/get_data_views/index.ts rename to test/api_integration/apis/data_views/data_views_crud/get_data_views/index.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/get_data_views/main.ts b/test/api_integration/apis/data_views/data_views_crud/get_data_views/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/get_data_views/main.ts rename to test/api_integration/apis/data_views/data_views_crud/get_data_views/main.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/index.ts b/test/api_integration/apis/data_views/data_views_crud/index.ts similarity index 71% rename from test/api_integration/apis/index_patterns/index_pattern_crud/index.ts rename to test/api_integration/apis/data_views/data_views_crud/index.ts index 158fe3087bcbe..241cc0230e869 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/index.ts +++ b/test/api_integration/apis/data_views/data_views_crud/index.ts @@ -10,10 +10,10 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('index_pattern_crud', () => { - loadTestFile(require.resolve('./create_index_pattern')); - loadTestFile(require.resolve('./get_index_pattern')); - loadTestFile(require.resolve('./delete_index_pattern')); - loadTestFile(require.resolve('./update_index_pattern')); + loadTestFile(require.resolve('./create_data_view')); + loadTestFile(require.resolve('./get_data_view')); + loadTestFile(require.resolve('./delete_data_view')); + loadTestFile(require.resolve('./update_data_view')); loadTestFile(require.resolve('./get_data_views')); }); } diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/errors.ts b/test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/errors.ts rename to test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/index.ts b/test/api_integration/apis/data_views/data_views_crud/update_data_view/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/index.ts rename to test/api_integration/apis/data_views/data_views_crud/update_data_view/index.ts diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts b/test/api_integration/apis/data_views/data_views_crud/update_data_view/main.ts similarity index 88% rename from test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts rename to test/api_integration/apis/data_views/data_views_crud/update_data_view/main.ts index 7548d09580f45..aaeef54750864 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts +++ b/test/api_integration/apis/data_views/data_views_crud/update_data_view/main.ts @@ -16,7 +16,7 @@ export default function ({ getService }: FtrProviderContext) { describe('main', () => { configArray.forEach((config) => { describe(config.name, () => { - it('can update index_pattern title', async () => { + it('can update data view title', async () => { const title1 = `foo-${Date.now()}-${Math.random()}*`; const title2 = `bar-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ @@ -41,7 +41,34 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body[config.serviceKey].title).to.be(title2); }); - it('can update index_pattern timeFieldName', async () => { + it('can update data view name', async () => { + const title = `foo-${Date.now()}-${Math.random()}*`; + const name1 = 'good data view name'; + const name2 = 'better data view name'; + const response1 = await supertest.post(config.path).send({ + [config.serviceKey]: { + title, + name: name1, + }, + }); + + expect(response1.body[config.serviceKey].name).to.be(name1); + + const id = response1.body[config.serviceKey].id; + const response2 = await supertest.post(`${config.path}/${id}`).send({ + [config.serviceKey]: { + name: name2, + }, + }); + + expect(response2.body[config.serviceKey].name).to.be(name2); + + const response3 = await supertest.get(`${config.path}/${id}`); + + expect(response3.body[config.serviceKey].name).to.be(name2); + }); + + it('can update data view timeFieldName', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -66,7 +93,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body[config.serviceKey].timeFieldName).to.be('timeFieldName2'); }); - it('can update index_pattern sourceFilters', async () => { + it('can update data view sourceFilters', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -120,7 +147,7 @@ export default function ({ getService }: FtrProviderContext) { ]); }); - it('can update index_pattern fieldFormats', async () => { + it('can update data view fieldFormats', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -180,7 +207,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('can update index_pattern type', async () => { + it('can update data view type', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -205,7 +232,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body[config.serviceKey].type).to.be('bar'); }); - it('can update index_pattern typeMeta', async () => { + it('can update data view typeMeta', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -230,7 +257,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body[config.serviceKey].typeMeta).to.eql({ foo: 'baz' }); }); - it('can update multiple index pattern fields at once', async () => { + it('can update multiple data view fields at once', async () => { const title = `foo-${Date.now()}-${Math.random()}*`; const response1 = await supertest.post(config.path).send({ [config.serviceKey]: { diff --git a/test/api_integration/apis/index_patterns/default_index_pattern/default_index_pattern.ts b/test/api_integration/apis/data_views/default_index_pattern/default_index_pattern.ts similarity index 100% rename from test/api_integration/apis/index_patterns/default_index_pattern/default_index_pattern.ts rename to test/api_integration/apis/data_views/default_index_pattern/default_index_pattern.ts diff --git a/test/api_integration/apis/index_patterns/default_index_pattern/index.ts b/test/api_integration/apis/data_views/default_index_pattern/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/default_index_pattern/index.ts rename to test/api_integration/apis/data_views/default_index_pattern/index.ts diff --git a/test/api_integration/apis/index_patterns/deprecations/index.ts b/test/api_integration/apis/data_views/deprecations/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/deprecations/index.ts rename to test/api_integration/apis/data_views/deprecations/index.ts diff --git a/test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts b/test/api_integration/apis/data_views/deprecations/scripted_fields.ts similarity index 100% rename from test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts rename to test/api_integration/apis/data_views/deprecations/scripted_fields.ts diff --git a/test/api_integration/apis/index_patterns/es_errors/errors.js b/test/api_integration/apis/data_views/es_errors/errors.js similarity index 100% rename from test/api_integration/apis/index_patterns/es_errors/errors.js rename to test/api_integration/apis/data_views/es_errors/errors.js diff --git a/test/api_integration/apis/index_patterns/es_errors/index.js b/test/api_integration/apis/data_views/es_errors/index.js similarity index 100% rename from test/api_integration/apis/index_patterns/es_errors/index.js rename to test/api_integration/apis/data_views/es_errors/index.js diff --git a/test/api_integration/apis/index_patterns/es_errors/lib/get_es_errors.js b/test/api_integration/apis/data_views/es_errors/lib/get_es_errors.js similarity index 100% rename from test/api_integration/apis/index_patterns/es_errors/lib/get_es_errors.js rename to test/api_integration/apis/data_views/es_errors/lib/get_es_errors.js diff --git a/test/api_integration/apis/index_patterns/es_errors/lib/index.js b/test/api_integration/apis/data_views/es_errors/lib/index.js similarity index 100% rename from test/api_integration/apis/index_patterns/es_errors/lib/index.js rename to test/api_integration/apis/data_views/es_errors/lib/index.js diff --git a/test/api_integration/apis/index_patterns/fields_api/index.ts b/test/api_integration/apis/data_views/fields_api/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/fields_api/index.ts rename to test/api_integration/apis/data_views/fields_api/index.ts diff --git a/test/api_integration/apis/index_patterns/fields_api/update_fields/errors.ts b/test/api_integration/apis/data_views/fields_api/update_fields/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/fields_api/update_fields/errors.ts rename to test/api_integration/apis/data_views/fields_api/update_fields/errors.ts diff --git a/test/api_integration/apis/index_patterns/fields_api/update_fields/index.ts b/test/api_integration/apis/data_views/fields_api/update_fields/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/fields_api/update_fields/index.ts rename to test/api_integration/apis/data_views/fields_api/update_fields/index.ts diff --git a/test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts b/test/api_integration/apis/data_views/fields_api/update_fields/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts rename to test/api_integration/apis/data_views/fields_api/update_fields/main.ts diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/conflicts.js b/test/api_integration/apis/data_views/fields_for_wildcard_route/conflicts.js similarity index 100% rename from test/api_integration/apis/index_patterns/fields_for_wildcard_route/conflicts.js rename to test/api_integration/apis/data_views/fields_for_wildcard_route/conflicts.js diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/filter.ts b/test/api_integration/apis/data_views/fields_for_wildcard_route/filter.ts similarity index 100% rename from test/api_integration/apis/index_patterns/fields_for_wildcard_route/filter.ts rename to test/api_integration/apis/data_views/fields_for_wildcard_route/filter.ts diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/index.js b/test/api_integration/apis/data_views/fields_for_wildcard_route/index.js similarity index 100% rename from test/api_integration/apis/index_patterns/fields_for_wildcard_route/index.js rename to test/api_integration/apis/data_views/fields_for_wildcard_route/index.js diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js b/test/api_integration/apis/data_views/fields_for_wildcard_route/params.js similarity index 100% rename from test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js rename to test/api_integration/apis/data_views/fields_for_wildcard_route/params.js diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/data_views/fields_for_wildcard_route/response.js similarity index 100% rename from test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js rename to test/api_integration/apis/data_views/fields_for_wildcard_route/response.js diff --git a/test/api_integration/apis/index_patterns/has_user_index_pattern/has_user_index_pattern.ts b/test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts similarity index 100% rename from test/api_integration/apis/index_patterns/has_user_index_pattern/has_user_index_pattern.ts rename to test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts diff --git a/test/api_integration/apis/index_patterns/has_user_index_pattern/index.ts b/test/api_integration/apis/data_views/has_user_index_pattern/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/has_user_index_pattern/index.ts rename to test/api_integration/apis/data_views/has_user_index_pattern/index.ts diff --git a/test/api_integration/apis/index_patterns/index.js b/test/api_integration/apis/data_views/index.js similarity index 94% rename from test/api_integration/apis/index_patterns/index.js rename to test/api_integration/apis/data_views/index.js index 9472f12a85159..c874bbc7d5b28 100644 --- a/test/api_integration/apis/index_patterns/index.js +++ b/test/api_integration/apis/data_views/index.js @@ -10,7 +10,7 @@ export default function ({ loadTestFile }) { describe('index_patterns', () => { loadTestFile(require.resolve('./es_errors')); loadTestFile(require.resolve('./fields_for_wildcard_route')); - loadTestFile(require.resolve('./index_pattern_crud')); + loadTestFile(require.resolve('./data_views_crud')); loadTestFile(require.resolve('./scripted_fields_crud')); loadTestFile(require.resolve('./fields_api')); loadTestFile(require.resolve('./default_index_pattern')); diff --git a/test/api_integration/apis/index_patterns/integration/index.ts b/test/api_integration/apis/data_views/integration/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/integration/index.ts rename to test/api_integration/apis/data_views/integration/index.ts diff --git a/test/api_integration/apis/index_patterns/integration/integration.ts b/test/api_integration/apis/data_views/integration/integration.ts similarity index 100% rename from test/api_integration/apis/index_patterns/integration/integration.ts rename to test/api_integration/apis/data_views/integration/integration.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts b/test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts b/test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/create_runtime_field/main.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts b/test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts b/test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/delete_runtime_field/main.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts b/test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts b/test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/get_runtime_field/main.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts b/test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts b/test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/put_runtime_field/main.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts b/test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts b/test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/index.ts diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts b/test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts rename to test/api_integration/apis/data_views/runtime_fields_crud/update_runtime_field/main.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/errors.ts b/test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/errors.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts b/test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/create_scripted_field/main.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/errors.ts b/test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/errors.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts b/test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/delete_scripted_field/main.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/errors.ts b/test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/errors.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts b/test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/get_scripted_field/main.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/errors.ts b/test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/errors.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts b/test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/put_scripted_field/main.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/errors.ts b/test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/errors.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/errors.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/errors.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/index.ts b/test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/index.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/index.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/index.ts diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts b/test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/main.ts similarity index 100% rename from test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts rename to test/api_integration/apis/data_views/scripted_fields_crud/update_scripted_field/main.ts diff --git a/test/api_integration/apis/index.ts b/test/api_integration/apis/index.ts index 3b8a182308e77..b3a2f6e8d6eaf 100644 --- a/test/api_integration/apis/index.ts +++ b/test/api_integration/apis/index.ts @@ -16,7 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./general')); loadTestFile(require.resolve('./home')); loadTestFile(require.resolve('./data_view_field_editor')); - loadTestFile(require.resolve('./index_patterns')); + loadTestFile(require.resolve('./data_views')); loadTestFile(require.resolve('./kql_telemetry')); loadTestFile(require.resolve('./saved_objects_management')); loadTestFile(require.resolve('./saved_objects')); From c1a270ab3c3f1b2dcf3549d7eedd9708d93db7bb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 27 Sep 2022 14:12:37 +0200 Subject: [PATCH 060/172] [Files] Use mime type on `Blob` object (#141913) * do not use mime type on frontend * added type to file --- .../files/public/components/upload_file/upload_state.test.ts | 4 ++-- .../files/public/components/upload_file/upload_state.ts | 3 ++- .../components/upload_file/util/parse_file_name.test.ts | 4 ---- .../public/components/upload_file/util/parse_file_name.ts | 4 ---- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts index 829e1f1031bfc..8fc0c02e0f60e 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts @@ -35,7 +35,7 @@ describe('UploadState', () => { it('calls file client with expected arguments', async () => { testScheduler.run(({ expectObservable, cold, flush }) => { - const file1 = { name: 'test.png', size: 1 } as File; + const file1 = { name: 'test.png', size: 1, type: 'image/png' } as File; uploadState.setFiles([file1]); @@ -113,7 +113,7 @@ describe('UploadState', () => { filesClient.delete.mockReturnValue(of(undefined) as any); const file1 = { name: 'test' } as File; - const file2 = { name: 'test 2.png' } as File; + const file2 = { name: 'test 2.png', type: 'image/png' } as File; uploadState.setFiles([file1, file2]); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.ts index 7a25e407daebe..d5fbc04512fdc 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.ts @@ -169,7 +169,8 @@ export class UploadState { file$.setState({ status: 'uploading', error: undefined }); - const { name, mime } = parseFileName(file.name); + const { name } = parseFileName(file.name); + const mime = file.type || undefined; return from( this.client.create({ diff --git a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts index f03b019f6aca3..bfe27b50a9f43 100644 --- a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts +++ b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts @@ -11,28 +11,24 @@ describe('parseFileName', () => { test('file.png', () => { expect(parseFileName('file.png')).toEqual({ name: 'file', - mime: 'image/png', }); }); test(' Something_* really -=- strange.abc.wav', () => { expect(parseFileName(' Something_* really -=- strange.abc.wav')).toEqual({ name: 'Something__ really ___ strange_abc', - mime: 'audio/wave', }); }); test('!@#$%^&*()', () => { expect(parseFileName('!@#$%^&*()')).toEqual({ name: '__________', - mime: undefined, }); }); test('reallylong.repeat(100).dmg', () => { expect(parseFileName('reallylong'.repeat(100) + '.dmg')).toEqual({ name: 'reallylong'.repeat(100).slice(0, 256), - mime: 'application/x-apple-diskimage', }); }); }); diff --git a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts index 22e6833851825..485b3013631dd 100644 --- a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts +++ b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts @@ -5,11 +5,8 @@ * 2.0. */ -import mime from 'mime-types'; - interface Result { name: string; - mime?: string; } export function parseFileName(fileName: string): Result { @@ -19,6 +16,5 @@ export function parseFileName(fileName: string): Result { .trim() .slice(0, 256) .replace(/[^a-z0-9\s]/gi, '_'), // replace invalid chars - mime: mime.lookup(fileName) || undefined, }; } From 3154b2aa47ad6bef30aec1a535a2879bc4218c6e Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 27 Sep 2022 14:22:47 +0200 Subject: [PATCH 061/172] Update the risk score documentation links to use the official documentation (#141792) --- .../security_solution/common/constants.ts | 4 ---- .../cti_details/host_risk_summary.tsx | 1 - .../cti_details/user_risk_summary.tsx | 1 - .../host_risk_score_disabled.tsx | 2 +- .../user_risk_score.disabled.tsx | 2 +- .../risk_score_doc_link.tsx | 17 ++--------------- .../risk_score_upgrade_button.tsx | 1 - .../use_risk_score_toast_content.tsx | 2 +- .../components/host_risk_information/index.tsx | 1 - .../public/hosts/components/kpi_hosts/index.tsx | 1 - .../entity_analytics/host_risk_score/index.tsx | 4 ++-- .../entity_analytics/user_risk_score/index.tsx | 4 ++-- .../public/users/components/kpi_users/index.tsx | 1 - .../components/user_risk_information/index.tsx | 1 - 14 files changed, 9 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 967e16d323874..09599d4a289b2 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -462,10 +462,6 @@ export enum BulkActionsDryRunErrCode { MACHINE_LEARNING_INDEX_PATTERN = 'MACHINE_LEARNING_INDEX_PATTERN', } -export const RISKY_HOSTS_EXTERNAL_DOC_LINK = - 'https://www.github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/host-risk-score.md'; -export const RISKY_USERS_EXTERNAL_DOC_LINK = - 'https://www.github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/user-risk-score.md'; export const RISKY_HOSTS_DOC_LINK = 'https://www.elastic.co/guide/en/security/current/host-risk-score.html'; export const RISKY_USERS_DOC_LINK = diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx index 369122aef39d8..68d2d9a65157e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx @@ -41,7 +41,6 @@ const HostRiskSummaryComponent: React.FC<{ values={{ hostRiskScoreDocumentationLink: ( {i18n.ENABLE_HOST_RISK_SCORE_DESCRIPTION}{' '} - + } actions={ diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_disabled/user_risk_score.disabled.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_disabled/user_risk_score.disabled.tsx index 769be20fc362c..5649c9698e361 100644 --- a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_disabled/user_risk_score.disabled.tsx +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_disabled/user_risk_score.disabled.tsx @@ -38,7 +38,7 @@ const EntityAnalyticsUserRiskScoreDisableComponent = ({ body={ <> {i18n.ENABLE_USER_RISK_SCORE_DESCRIPTION}{' '} - + } actions={ diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_doc_link.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_doc_link.tsx index 78d01879a780f..340f63e71bc74 100644 --- a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_doc_link.tsx +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_doc_link.tsx @@ -7,35 +7,22 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { - RISKY_HOSTS_DOC_LINK, - RISKY_HOSTS_EXTERNAL_DOC_LINK, - RISKY_USERS_DOC_LINK, - RISKY_USERS_EXTERNAL_DOC_LINK, -} from '../../../../../common/constants'; +import { RISKY_HOSTS_DOC_LINK, RISKY_USERS_DOC_LINK } from '../../../../../common/constants'; import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { LEARN_MORE } from '../../../../overview/components/entity_analytics/host_risk_score/translations'; const RiskScoreDocLinkComponent = ({ - external, riskScoreEntity, title, }: { - external: boolean; riskScoreEntity: RiskScoreEntity; title?: string | React.ReactNode; }) => { - const externalLink = - riskScoreEntity === RiskScoreEntity.user - ? RISKY_USERS_EXTERNAL_DOC_LINK - : RISKY_HOSTS_EXTERNAL_DOC_LINK; - const docLink = riskScoreEntity === RiskScoreEntity.user ? RISKY_USERS_DOC_LINK : RISKY_HOSTS_DOC_LINK; - const link = external ? externalLink : docLink; return ( - + {title ? title : LEARN_MORE} ); diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_upgrade_button.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_upgrade_button.tsx index 6f79950a65040..6ed805850ce23 100644 --- a/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_upgrade_button.tsx +++ b/x-pack/plugins/security_solution/public/common/components/risk_score/risk_score_onboarding/risk_score_upgrade_button.tsx @@ -94,7 +94,6 @@ const RiskScoreUpgradeButtonComponent = ({ onConfirm={upgradeRiskScore} cancelButtonText={ { const renderDocLink = useCallback( (message: string) => ( <> - {message} + {message} ), [riskScoreEntity] diff --git a/x-pack/plugins/security_solution/public/hosts/components/host_risk_information/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/host_risk_information/index.tsx index 9ef78fe893aa7..361d982df180f 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/host_risk_information/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/host_risk_information/index.tsx @@ -131,7 +131,6 @@ const HostRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => voi values={{ HostRiskScoreDocumentationLink: ( ( <> {i18n.LEARN_MORE}{' '} diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx index 1db3fd6e5d873..794b33c6b33ef 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx @@ -30,7 +30,7 @@ import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { hostsActions } from '../../../../hosts/store'; import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { BasicTableWithoutBorderBottom } from '../common/basic_table_without_border_bottom'; -import { RISKY_HOSTS_EXTERNAL_DOC_LINK } from '../../../../../common/constants'; +import { RISKY_HOSTS_DOC_LINK } from '../../../../../common/constants'; import { EntityAnalyticsHostRiskScoreDisable } from '../../../../common/components/risk_score/risk_score_disabled/host_risk_score_disabled'; import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; @@ -165,7 +165,7 @@ const EntityAnalyticsHostRiskScoresComponent = () => { {i18n.LEARN_MORE} diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx index 35c865243cc26..3b3905d4604a6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx @@ -30,7 +30,7 @@ import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirec import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { BasicTableWithoutBorderBottom } from '../common/basic_table_without_border_bottom'; -import { RISKY_USERS_EXTERNAL_DOC_LINK } from '../../../../../common/constants'; +import { RISKY_USERS_DOC_LINK } from '../../../../../common/constants'; import { EntityAnalyticsUserRiskScoreDisable } from '../../../../common/components/risk_score/risk_score_disabled/user_risk_score.disabled'; import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; @@ -165,7 +165,7 @@ const EntityAnalyticsUserRiskScoresComponent = () => { {i18n.LEARN_MORE} diff --git a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx index 4344c52e10d4b..831ca2bdc76f6 100644 --- a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx @@ -44,7 +44,6 @@ export const UsersKpiComponent = React.memo( <> {i18n.LEARN_MORE}{' '} diff --git a/x-pack/plugins/security_solution/public/users/components/user_risk_information/index.tsx b/x-pack/plugins/security_solution/public/users/components/user_risk_information/index.tsx index b759e8751e7e7..4004299b7e450 100644 --- a/x-pack/plugins/security_solution/public/users/components/user_risk_information/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/user_risk_information/index.tsx @@ -108,7 +108,6 @@ const UserRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => voi values={{ UserRiskScoreDocumentationLink: ( Date: Tue, 27 Sep 2022 08:25:00 -0400 Subject: [PATCH 062/172] [ResponseOps][Alerting] Fixing flaky tests in task_management.ts (#141441) * Fixing flaky test * Updating tests * Fixing tests again * Update x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts Co-authored-by: Ying Mao * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Updating for pr comments * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Missing lint * Updating check * Adding type * Remove runAt Co-authored-by: Ying Mao Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../task_manager/task_management.ts | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index e857636d8f67a..123259cadf0c7 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -54,8 +54,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const testHistoryIndex = '.kibana_task_manager_test_result'; - // Failing: See https://github.com/elastic/kibana/issues/141002 - describe.skip('scheduling and running tasks', () => { + describe('scheduling and running tasks', () => { beforeEach(async () => { // clean up before each test return await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); @@ -646,36 +645,36 @@ export default function ({ getService }: FtrProviderContext) { const historyItem = random(1, 100); const scheduledTask = await scheduleTask({ taskType: 'sampleTask', - schedule: { interval: '30m' }, + schedule: { interval: '1h' }, params: { historyItem }, }); await retry.try(async () => { expect((await historyDocs()).length).to.eql(1); - const tasks = (await currentTasks()).docs; + const task = await currentTask(scheduledTask.id); - expect(getTaskById(tasks, scheduledTask.id).enabled).to.eql(true); + expect(task.enabled).to.eql(true); }); // disable the task await bulkDisable([scheduledTask.id]); await retry.try(async () => { - const tasks = (await currentTasks()).docs; - - expect(getTaskById(tasks, scheduledTask.id).enabled).to.eql(false); + const task = await currentTask(scheduledTask.id); + expect(task.enabled).to.eql(false); }); // re-enable the task await bulkEnable([scheduledTask.id], true); await retry.try(async () => { - const tasks = (await currentTasks()).docs; - - expect(getTaskById(tasks, scheduledTask.id).enabled).to.eql(true); + const task = await currentTask(scheduledTask.id); - // should get a new document even tho original schedule interval was 30m - expect((await historyDocs()).length).to.eql(2); + expect(task.enabled).to.eql(true); + expect(Date.parse(task.scheduledAt)).to.be.greaterThan( + Date.parse(scheduledTask.scheduledAt) + ); + expect(Date.parse(task.runAt)).to.be.greaterThan(Date.parse(scheduledTask.runAt)); }); }); @@ -683,40 +682,33 @@ export default function ({ getService }: FtrProviderContext) { const historyItem = random(1, 100); const scheduledTask = await scheduleTask({ taskType: 'sampleTask', - schedule: { interval: '30m' }, + schedule: { interval: '1h' }, params: { historyItem }, }); await retry.try(async () => { expect((await historyDocs()).length).to.eql(1); - const tasks = (await currentTasks()).docs; - expect(getTaskById(tasks, scheduledTask.id).enabled).to.eql(true); + const task = await currentTask(scheduledTask.id); + expect(task.enabled).to.eql(true); }); // disable the task await bulkDisable([scheduledTask.id]); await retry.try(async () => { - const tasks = (await currentTasks()).docs; - - expect(getTaskById(tasks, scheduledTask.id).enabled).to.eql(false); + const task = await currentTask(scheduledTask.id); + expect(task.enabled).to.eql(false); }); // re-enable the task await bulkEnable([scheduledTask.id], false); await retry.try(async () => { - const tasks = (await currentTasks()).docs; - - const task = getTaskById(tasks, scheduledTask.id); + const task = await currentTask(scheduledTask.id); expect(task.enabled).to.eql(true); - - // task runAt should be set in the future by greater than 20 minutes - // this assumes it takes less than 10 minutes to disable and renable the task - // since the schedule interval is 30 minutes - expect(Date.parse(task.runAt) - Date.now()).to.be.greaterThan(10 * 60 * 1000); + expect(Date.parse(task.scheduledAt)).to.eql(Date.parse(scheduledTask.scheduledAt)); }); }); From f336734196000e37ee60b58056b04202b928a051 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Tue, 27 Sep 2022 08:33:01 -0400 Subject: [PATCH 063/172] [Security Solution] Update Action history text to be Response actions history (#141805) --- .../security_solution/common/constants.ts | 7 +++--- .../public/app/deep_links/index.ts | 10 ++++----- .../public/app/home/home_navigations.ts | 10 ++++----- .../public/app/translations.ts | 9 +++++--- .../common/components/navigation/types.ts | 2 +- .../__snapshots__/index.test.tsx.snap | 10 ++++----- .../use_navigation_items.tsx | 2 +- .../public/management/common/breadcrumbs.ts | 8 +++++-- .../public/management/common/constants.ts | 2 +- .../endpoint_responder/action_log_button.tsx | 4 ++-- .../components/actions_log_empty_state.tsx | 8 +++---- .../response_actions_log.test.tsx | 2 +- .../response_actions_log.tsx | 4 ++-- .../translations.tsx | 2 +- .../public/management/links.ts | 12 +++++----- .../details/components/actions_menu.test.tsx | 2 +- .../view/hooks/use_endpoint_action_items.tsx | 4 ++-- .../pages/endpoint_hosts/view/index.test.tsx | 2 +- .../pages/endpoint_hosts/view/translations.ts | 4 ++-- .../public/management/pages/index.tsx | 11 ++++++---- .../pages/response_actions/index.tsx | 4 ++-- .../view/response_actions_list_page.test.tsx | 22 ++++++++++--------- .../view/response_actions_list_page.tsx | 4 ++-- .../public/management/types.ts | 2 +- .../translations/translations/fr-FR.json | 3 --- .../translations/translations/ja-JP.json | 3 --- .../translations/translations/zh-CN.json | 3 --- 27 files changed, 80 insertions(+), 76 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 09599d4a289b2..7358fbb33b4b2 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -113,7 +113,7 @@ export enum SecurityPageName { noPage = '', overview = 'overview', policies = 'policy', - actionHistory = 'action_history', + responseActionsHistory = 'response_actions_history', rules = 'rules', rulesCreate = 'rules-create', sessions = 'sessions', @@ -159,7 +159,7 @@ export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters` as const; export const HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/host_isolation_exceptions` as const; export const BLOCKLIST_PATH = `${MANAGEMENT_PATH}/blocklist` as const; -export const ACTION_HISTORY_PATH = `${MANAGEMENT_PATH}/action_history` as const; +export const RESPONSE_ACTIONS_HISTORY_PATH = `${MANAGEMENT_PATH}/response_actions_history` as const; export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const; export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const; export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const; @@ -183,7 +183,8 @@ export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}` as cons export const APP_HOST_ISOLATION_EXCEPTIONS_PATH = `${APP_PATH}${HOST_ISOLATION_EXCEPTIONS_PATH}` as const; export const APP_BLOCKLIST_PATH = `${APP_PATH}${BLOCKLIST_PATH}` as const; -export const APP_ACTION_HISTORY_PATH = `${APP_PATH}${ACTION_HISTORY_PATH}` as const; +export const APP_RESPONSE_ACTIONS_HISTORY_PATH = + `${APP_PATH}${RESPONSE_ACTIONS_HISTORY_PATH}` as const; export const APP_ENTITY_ANALYTICS_PATH = `${APP_PATH}${ENTITY_ANALYTICS_PATH}` as const; // cloud logs to exclude from default index pattern diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 99db764285ab7..170e06742dcb0 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -42,7 +42,7 @@ import { NETWORK, OVERVIEW, POLICIES, - ACTION_HISTORY, + RESPONSE_ACTIONS_HISTORY, ENTITY_ANALYTICS, RULES, TIMELINES, @@ -65,7 +65,7 @@ import { NETWORK_PATH, OVERVIEW_PATH, POLICIES_PATH, - ACTION_HISTORY_PATH, + RESPONSE_ACTIONS_HISTORY_PATH, ENTITY_ANALYTICS_PATH, RULES_CREATE_PATH, RULES_PATH, @@ -511,9 +511,9 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ path: BLOCKLIST_PATH, }, { - id: SecurityPageName.actionHistory, - title: ACTION_HISTORY, - path: ACTION_HISTORY_PATH, + id: SecurityPageName.responseActionsHistory, + title: RESPONSE_ACTIONS_HISTORY, + path: RESPONSE_ACTIONS_HISTORY_PATH, }, { ...getSecuritySolutionLink('benchmarks'), diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts index ae7c15c73a4d2..392e89fbbec0b 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -30,7 +30,7 @@ import { APP_USERS_PATH, APP_KUBERNETES_PATH, APP_LANDING_PATH, - APP_ACTION_HISTORY_PATH, + APP_RESPONSE_ACTIONS_HISTORY_PATH, APP_ENTITY_ANALYTICS_PATH, APP_PATH, } from '../../../common/constants'; @@ -162,10 +162,10 @@ export const navTabs: SecurityNav = { disabled: false, urlKey: 'administration', }, - [SecurityPageName.actionHistory]: { - id: SecurityPageName.actionHistory, - name: i18n.ACTION_HISTORY, - href: APP_ACTION_HISTORY_PATH, + [SecurityPageName.responseActionsHistory]: { + id: SecurityPageName.responseActionsHistory, + name: i18n.RESPONSE_ACTIONS_HISTORY, + href: APP_RESPONSE_ACTIONS_HISTORY_PATH, disabled: false, urlKey: 'administration', }, diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 154127f469c96..0e74f701eefdf 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -120,9 +120,12 @@ export const BLOCKLIST = i18n.translate('xpack.securitySolution.navigation.block defaultMessage: 'Blocklist', }); -export const ACTION_HISTORY = i18n.translate('xpack.securitySolution.navigation.actionHistory', { - defaultMessage: 'Action history', -}); +export const RESPONSE_ACTIONS_HISTORY = i18n.translate( + 'xpack.securitySolution.navigation.responseActionsHistory', + { + defaultMessage: 'Response actions history', + } +); export const CREATE_NEW_RULE = i18n.translate('xpack.securitySolution.navigation.newRuleTitle', { defaultMessage: 'Create new rule', diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 84341f5321169..ebfae21d5a5e5 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -68,7 +68,7 @@ export interface NavTab { } export const securityNavKeys = [ SecurityPageName.alerts, - SecurityPageName.actionHistory, + SecurityPageName.responseActionsHistory, SecurityPageName.blocklist, SecurityPageName.detectionAndResponse, SecurityPageName.case, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap index 613171d5da894..ec824632e1aee 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap @@ -251,13 +251,13 @@ Object { "onClick": [Function], }, Object { - "data-href": "securitySolutionUI/action_history", - "data-test-subj": "navigation-action_history", + "data-href": "securitySolutionUI/response_actions_history", + "data-test-subj": "navigation-response_actions_history", "disabled": false, - "href": "securitySolutionUI/action_history", - "id": "action_history", + "href": "securitySolutionUI/response_actions_history", + "id": "response_actions_history", "isSelected": false, - "name": "Action history", + "name": "Response actions history", "onClick": [Function], }, Object { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index dfef46f35a237..dc15e371ba630 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -138,7 +138,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { ? [navTabs[SecurityPageName.hostIsolationExceptions]] : []), navTabs[SecurityPageName.blocklist], - navTabs[SecurityPageName.actionHistory], + navTabs[SecurityPageName.responseActionsHistory], navTabs[SecurityPageName.cloudSecurityPostureBenchmarks], ], }, diff --git a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts index 12dfa0f28208a..8bb985df9610d 100644 --- a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts @@ -9,7 +9,11 @@ import type { ChromeBreadcrumb } from '@kbn/core/public'; import { AdministrationSubTab } from '../types'; import { ENDPOINTS_TAB, EVENT_FILTERS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from './translations'; import type { AdministrationRouteSpyState } from '../../common/utils/route/types'; -import { HOST_ISOLATION_EXCEPTIONS, BLOCKLIST, ACTION_HISTORY } from '../../app/translations'; +import { + HOST_ISOLATION_EXCEPTIONS, + BLOCKLIST, + RESPONSE_ACTIONS_HISTORY, +} from '../../app/translations'; const TabNameMappedToI18nKey: Record = { [AdministrationSubTab.endpoints]: ENDPOINTS_TAB, @@ -18,7 +22,7 @@ const TabNameMappedToI18nKey: Record = { [AdministrationSubTab.eventFilters]: EVENT_FILTERS_TAB, [AdministrationSubTab.hostIsolationExceptions]: HOST_ISOLATION_EXCEPTIONS, [AdministrationSubTab.blocklist]: BLOCKLIST, - [AdministrationSubTab.actionHistory]: ACTION_HISTORY, + [AdministrationSubTab.responseActionsHistory]: RESPONSE_ACTIONS_HISTORY, }; export function getTrailingBreadcrumbs(params: AdministrationRouteSpyState): ChromeBreadcrumb[] { diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index afad5b78e9f4e..edaaa2d21a409 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -23,7 +23,7 @@ export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`; export const MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.hostIsolationExceptions})`; export const MANAGEMENT_ROUTING_BLOCKLIST_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.blocklist})`; -export const MANAGEMENT_ROUTING_ACTION_HISTORY_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.actionHistory})`; +export const MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.responseActionsHistory})`; // --[ STORE ]--------------------------------------------------------------------------- /** The SIEM global store namespace where the management state will be mounted */ diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_log_button.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_log_button.tsx index 85a5f2655dd68..655bc0a6c8911 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_log_button.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_log_button.tsx @@ -29,8 +29,8 @@ export const ActionLogButton = memo((p data-test-subj="responderShowActionLogButton" > {showActionLogFlyout && ( diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx index a5a6f15f2de9d..c5838570eeae0 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx @@ -26,14 +26,14 @@ export const ActionsLogEmptyState = memo( iconType="editorUnorderedList" title={

- {i18n.translate('xpack.securitySolution.actions_log.empty.title', { - defaultMessage: 'Actions history is empty', + {i18n.translate('xpack.securitySolution.responseActionsHistory.empty.title', { + defaultMessage: 'Response actions history is empty', })}

} body={
- {i18n.translate('xpack.securitySolution.actions_log.empty.content', { + {i18n.translate('xpack.securitySolution.responseActionsHistory.empty.content', { defaultMessage: 'No response actions performed', })}
@@ -44,7 +44,7 @@ export const ActionsLogEmptyState = memo( href={docLinks?.links.securitySolution.responseActions} target="_blank" > - {i18n.translate('xpack.securitySolution.actions_log.empty.link', { + {i18n.translate('xpack.securitySolution.responseActionsHistory.empty.link', { defaultMessage: 'Read more about response actions', })}
, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index 549b03fedfbf1..30148c9643baa 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -114,7 +114,7 @@ jest.mock('../../hooks/endpoint/use_get_endpoints_list'); const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; -describe('Response Actions Log', () => { +describe('Response actions history', () => { const testPrefix = 'response-actions-list'; let render: ( diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index bea5a3eb07aaf..2a9362830c76d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -649,7 +649,7 @@ export const ResponseActionsLog = memo<

} @@ -657,7 +657,7 @@ export const ResponseActionsLog = memo<

} diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index 542f0d72bf0be..a90f12bc2d246 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -92,7 +92,7 @@ export const TABLE_COLUMN_NAMES = Object.freeze({ export const UX_MESSAGES = Object.freeze({ flyoutTitle: (hostname: string) => i18n.translate('xpack.securitySolution.responseActionsList.flyout.title', { - defaultMessage: `Actions log : {hostname}`, + defaultMessage: `Response actions history : {hostname}`, values: { hostname }, }), pageSubTitle: i18n.translate('xpack.securitySolution.responseActionsList.list.pageSubTitle', { diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 54c729e41911d..03cfee736def3 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -20,7 +20,7 @@ import { HOST_ISOLATION_EXCEPTIONS_PATH, MANAGE_PATH, POLICIES_PATH, - ACTION_HISTORY_PATH, + RESPONSE_ACTIONS_HISTORY_PATH, RULES_CREATE_PATH, RULES_PATH, SecurityPageName, @@ -36,7 +36,7 @@ import { HOST_ISOLATION_EXCEPTIONS, MANAGE, POLICIES, - ACTION_HISTORY, + RESPONSE_ACTIONS_HISTORY, RULES, TRUSTED_APPLICATIONS, } from '../app/translations'; @@ -77,7 +77,7 @@ const categories = [ SecurityPageName.eventFilters, SecurityPageName.hostIsolationExceptions, SecurityPageName.blocklist, - SecurityPageName.actionHistory, + SecurityPageName.responseActionsHistory, ], }, ...cloudSecurityPostureCategories, @@ -212,13 +212,13 @@ export const links: LinkItem = { hideTimeline: true, }, { - id: SecurityPageName.actionHistory, - title: ACTION_HISTORY, + id: SecurityPageName.responseActionsHistory, + title: RESPONSE_ACTIONS_HISTORY, description: i18n.translate('xpack.securitySolution.appLinks.actionHistoryDescription', { defaultMessage: 'View the history of response actions performed on hosts.', }), landingIcon: IconActionHistory, - path: ACTION_HISTORY_PATH, + path: RESPONSE_ACTIONS_HISTORY_PATH, skipUrlState: true, hideTimeline: true, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 911d3b12931a6..6fc8a99ee7320 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -80,7 +80,7 @@ describe('When using the Endpoint Details Actions Menu', () => { }; }); - it('should not show the actions log link', async () => { + it('should not show the response actions history link', async () => { await render(); expect(renderResult.queryByTestId('actionsLink')).toBeNull(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index b3e1ce76480f3..40fd81c4ab587 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -156,8 +156,8 @@ export const useEndpointActionItems = ( href: getAppUrl({ path: endpointActionsPath }), children: ( ), }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index bbee0e695a8dd..b85ad2cc7f6a5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -1116,7 +1116,7 @@ describe('when on the endpoint list page', () => { expect(responderButton).not.toHaveAttribute('disabled'); }); - it('navigates to the Actions log flyout', async () => { + it('navigates to the Response actions history flyout', async () => { const actionsLink = await renderResult.findByTestId('actionsLink'); expect(actionsLink.getAttribute('href')).toEqual( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts index 30001f41c3636..9ec5b0eee65cf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts @@ -12,8 +12,8 @@ export const OVERVIEW = i18n.translate('xpack.securitySolution.endpointDetails.o }); export const ACTIVITY_LOG = { - tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.activityLog', { - defaultMessage: 'Actions Log', + tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.responseActionsHistory', { + defaultMessage: 'Response actions history', }), LogEntry: { endOfLog: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 2a54557b0095b..dd06a838a26cb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -17,7 +17,7 @@ import { MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_BLOCKLIST_PATH, - MANAGEMENT_ROUTING_ACTION_HISTORY_PATH, + MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH, } from '../common/constants'; import { NotFoundPage } from '../../app/404'; import { EndpointsContainer } from './endpoint_hosts'; @@ -69,9 +69,9 @@ const HostIsolationExceptionsTelemetry = () => ( ); const ResponseActionsTelemetry = () => ( - + - + ); @@ -103,7 +103,10 @@ export const ManagementContainer = memo(() => { component={HostIsolationExceptionsTelemetry} /> - + diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/index.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/index.tsx index 0d3f029cc34ce..25d4fa117da6f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/index.tsx @@ -7,7 +7,7 @@ import { Switch } from 'react-router-dom'; import { Route } from '@kbn/kibana-react-plugin/public'; import React, { memo } from 'react'; -import { MANAGEMENT_ROUTING_ACTION_HISTORY_PATH } from '../../common/constants'; +import { MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; import { ResponseActionsListPage } from './view/response_actions_list_page'; @@ -15,7 +15,7 @@ export const ResponseActionsContainer = memo(() => { return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx index 734db973826c9..7b0132a4b4be4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx @@ -112,7 +112,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { jest.mock('../../../hooks/endpoint/use_get_endpoints_list'); const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; -describe('Action history page', () => { +describe('Response actions history page', () => { const testPrefix = 'response-actions-list'; let render: () => ReturnType; @@ -164,7 +164,7 @@ describe('Action history page', () => { describe('Hide/Show header', () => { it('should show header when data is in', () => { reactTestingLibrary.act(() => { - history.push('/administration/action_history?page=3&pageSize=20'); + history.push('/administration/response_actions_history?page=3&pageSize=20'); }); render(); const { getByTestId } = renderResult; @@ -173,7 +173,7 @@ describe('Action history page', () => { it('should not show header when there is no actions index', () => { reactTestingLibrary.act(() => { - history.push('/administration/action_history?page=3&pageSize=20'); + history.push('/administration/response_actions_history?page=3&pageSize=20'); }); mockUseGetEndpointActionList = { ...baseMockedActionList, @@ -190,7 +190,7 @@ describe('Action history page', () => { describe('Read from URL params', () => { it('should read and set paging values from URL params', () => { reactTestingLibrary.act(() => { - history.push('/administration/action_history?page=3&pageSize=20'); + history.push('/administration/response_actions_history?page=3&pageSize=20'); }); render(); const { getByTestId } = renderResult; @@ -203,7 +203,7 @@ describe('Action history page', () => { it('should read and set command filter values from URL params', () => { const filterPrefix = 'actions-filter'; reactTestingLibrary.act(() => { - history.push('/administration/action_history?commands=release,processes'); + history.push('/administration/response_actions_history?commands=release,processes'); }); render(); @@ -240,7 +240,7 @@ describe('Action history page', () => { const filterPrefix = 'hosts-filter'; reactTestingLibrary.act(() => { history.push( - '/administration/action_history?hosts=agent-id-1,agent-id-2,agent-id-4,agent-id-5' + '/administration/response_actions_history?hosts=agent-id-1,agent-id-2,agent-id-4,agent-id-5' ); }); @@ -270,7 +270,7 @@ describe('Action history page', () => { it('should read and set status filter values from URL params', () => { const filterPrefix = 'statuses-filter'; reactTestingLibrary.act(() => { - history.push('/administration/action_history?statuses=pending,failed'); + history.push('/administration/response_actions_history?statuses=pending,failed'); }); render(); @@ -293,7 +293,7 @@ describe('Action history page', () => { it('should set selected users search input strings to URL params ', () => { const filterPrefix = 'users-filter'; reactTestingLibrary.act(() => { - history.push('/administration/action_history?users=userX,userY'); + history.push('/administration/response_actions_history?users=userX,userY'); }); render(); @@ -305,7 +305,7 @@ describe('Action history page', () => { it('should read and set relative date ranges filter values from URL params', () => { reactTestingLibrary.act(() => { - history.push('/administration/action_history?startDate=now-23m&endDate=now-1m'); + history.push('/administration/response_actions_history?startDate=now-23m&endDate=now-1m'); }); render(); @@ -324,7 +324,9 @@ describe('Action history page', () => { const startDate = '2022-09-12T11:00:00.000Z'; const endDate = '2022-09-12T11:30:33.000Z'; reactTestingLibrary.act(() => { - history.push(`/administration/action_history?startDate=${startDate}&endDate=${endDate}`); + history.push( + `/administration/response_actions_history?startDate=${startDate}&endDate=${endDate}` + ); }); const { getByTestId } = render(); diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx index e1f3705b4489c..d1ae96099387e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback } from 'react'; -import { ACTION_HISTORY } from '../../../../app/translations'; +import { RESPONSE_ACTIONS_HISTORY } from '../../../../app/translations'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { ResponseActionsLog } from '../../../components/endpoint_response_actions_list/response_actions_log'; import { UX_MESSAGES } from '../../../components/endpoint_response_actions_list/translations'; @@ -19,7 +19,7 @@ export const ResponseActionsListPage = () => { return ( diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index 96c1983c8f254..4ca2c34dfcc19 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -31,7 +31,7 @@ export enum AdministrationSubTab { eventFilters = 'event_filters', hostIsolationExceptions = 'host_isolation_exceptions', blocklist = 'blocklist', - actionHistory = 'action_history', + responseActionsHistory = 'response_actions_history', } /** diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 7b619c78eaf9f..04f32940b161a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -25489,7 +25489,6 @@ "xpack.securitySolution.usersTable.rows": "{numRows} {numRows, plural, =0 {ligne} =1 {ligne} other {lignes}}", "xpack.securitySolution.usersTable.unit": "{totalCount, plural, =1 {utilisateur} other {utilisateurs}}", "xpack.securitySolution.accessibility.tooltipWithKeyboardShortcut.pressTooltipLabel": "Appuyer", - "xpack.securitySolution.actionLogButton.label": "Log des actions", "xpack.securitySolution.actionsContextMenu.label": "Ouvrir", "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", @@ -27264,7 +27263,6 @@ "xpack.securitySolution.effectedPolicySelect.assignmentSectionTitle": "Affectation", "xpack.securitySolution.effectedPolicySelect.viewPolicyLinkLabel": "Afficher la politique", "xpack.securitySolution.emptyString.emptyStringDescription": "Chaîne vide", - "xpack.securitySolution.endpoint.actions.actionsLog": "Afficher le log d’actions", "xpack.securitySolution.endpoint.actions.agentDetails": "Afficher les détails de l'agent", "xpack.securitySolution.endpoint.actions.agentPolicy": "Afficher la politique de l'agent", "xpack.securitySolution.endpoint.actions.agentPolicyReassign": "Réaffecter la politique de l'agent", @@ -27732,7 +27730,6 @@ "xpack.securitySolution.endpointConsoleCommands.suspendProcess.commandArgAbout": "Commentaire qui doit accompagner l'action", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.entityId.arg.comment": "Un ID d’entité représentant le processus à suspendre", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.pid.arg.comment": "Un PID représentant le processus à suspendre", - "xpack.securitySolution.endpointDetails.activityLog": "Log d’actions", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog": "Pas d'autre élément à afficher", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointIsolateAction": "n'a pas pu envoyer la requête : Isoler l'hôte", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointReleaseAction": "n'a pas pu envoyer la requête : Libérer l'hôte", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 214fe1234d7d3..672dcbd3d5ccd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -25464,7 +25464,6 @@ "xpack.securitySolution.usersTable.rows": "{numRows} {numRows, plural, other {行}}", "xpack.securitySolution.usersTable.unit": "{totalCount, plural, other {ユーザー}}", "xpack.securitySolution.accessibility.tooltipWithKeyboardShortcut.pressTooltipLabel": "プレス", - "xpack.securitySolution.actionLogButton.label": "アクションログ", "xpack.securitySolution.actionsContextMenu.label": "開く", "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", @@ -27239,7 +27238,6 @@ "xpack.securitySolution.effectedPolicySelect.assignmentSectionTitle": "割り当て", "xpack.securitySolution.effectedPolicySelect.viewPolicyLinkLabel": "ポリシーを表示", "xpack.securitySolution.emptyString.emptyStringDescription": "空の文字列", - "xpack.securitySolution.endpoint.actions.actionsLog": "アクションログを表示", "xpack.securitySolution.endpoint.actions.agentDetails": "エージェント詳細を表示", "xpack.securitySolution.endpoint.actions.agentPolicy": "エージェントポリシーを表示", "xpack.securitySolution.endpoint.actions.agentPolicyReassign": "エージェントポリシーを再割り当て", @@ -27707,7 +27705,6 @@ "xpack.securitySolution.endpointConsoleCommands.suspendProcess.commandArgAbout": "アクションに関するコメント", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.entityId.arg.comment": "一時停止するプロセスを表すエンティティID", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.pid.arg.comment": "一時停止するプロセスを表すPID", - "xpack.securitySolution.endpointDetails.activityLog": "アクションログ", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog": "表示する情報がありません", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointIsolateAction": "要求を送信できませんでした。ホストの分離", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointReleaseAction": "要求を送信できませんでした。ホストのリリース", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index aeaadfcb1faba..0f15a16ae14ac 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25495,7 +25495,6 @@ "xpack.securitySolution.usersTable.rows": "{numRows} {numRows, plural, other {行}}", "xpack.securitySolution.usersTable.unit": "{totalCount, plural, other {个用户}}", "xpack.securitySolution.accessibility.tooltipWithKeyboardShortcut.pressTooltipLabel": "按", - "xpack.securitySolution.actionLogButton.label": "操作日志", "xpack.securitySolution.actionsContextMenu.label": "打开", "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", @@ -27270,7 +27269,6 @@ "xpack.securitySolution.effectedPolicySelect.assignmentSectionTitle": "分配", "xpack.securitySolution.effectedPolicySelect.viewPolicyLinkLabel": "查看策略", "xpack.securitySolution.emptyString.emptyStringDescription": "空字符串", - "xpack.securitySolution.endpoint.actions.actionsLog": "查看操作日志", "xpack.securitySolution.endpoint.actions.agentDetails": "查看代理详情", "xpack.securitySolution.endpoint.actions.agentPolicy": "查看代理策略", "xpack.securitySolution.endpoint.actions.agentPolicyReassign": "重新分配代理策略", @@ -27738,7 +27736,6 @@ "xpack.securitySolution.endpointConsoleCommands.suspendProcess.commandArgAbout": "此操作将伴随一条注释", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.entityId.arg.comment": "表示要挂起的进程的实体 ID", "xpack.securitySolution.endpointConsoleCommands.suspendProcess.pid.arg.comment": "表示要挂起的进程的 PID", - "xpack.securitySolution.endpointDetails.activityLog": "操作日志", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog": "没有更多要显示的内容", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointIsolateAction": "无法提交请求:隔离主机", "xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointReleaseAction": "无法提交请求:释放主机", From 7688fe3b58a05603919e65257dbbf6b0ba1241a2 Mon Sep 17 00:00:00 2001 From: Luke Gmys Date: Tue, 27 Sep 2022 14:58:33 +0200 Subject: [PATCH 064/172] [TIP] Loading state changes and react-query integration (#141102) --- .../public/common/mocks/story_providers.tsx | 21 +- .../public/common/mocks/test_providers.tsx | 31 ++- .../public/common/utils/barchart.test.ts | 2 +- .../public/common/utils/barchart.ts | 4 +- .../indicators_barchart.stories.tsx | 2 +- .../indicators_barchart.test.tsx | 2 +- .../indicators_barchart.tsx | 2 +- .../indicators_barchart_wrapper.stories.tsx | 2 +- .../indicators_table/cell_actions.tsx | 2 +- .../indicators_table.stories.tsx | 4 +- .../indicators_table.test.tsx | 6 +- .../indicators_table/indicators_table.tsx | 13 +- .../hooks/use_aggregated_indicators.test.tsx | 176 +++++------- .../hooks/use_aggregated_indicators.ts | 197 +++---------- .../indicators/hooks/use_indicators.test.tsx | 260 ++++-------------- .../indicators/hooks/use_indicators.ts | 186 ++++--------- .../hooks/use_indicators_total_count.tsx | 2 +- .../indicators/indicators_page.test.tsx | 3 +- .../modules/indicators/indicators_page.tsx | 70 ++--- .../fetch_aggregated_indicators.test.ts | 117 ++++++++ .../services/fetch_aggregated_indicators.ts | 125 +++++++++ .../services/fetch_indicators.test.ts | 105 +++++++ .../indicators/services/fetch_indicators.ts | 84 ++++++ .../utils/get_indicator_query_params.ts | 70 +++++ .../indicators/utils/get_indicators_query.ts | 50 ---- .../public/modules/indicators/utils/search.ts | 75 +++++ .../components/query_bar/query_bar.tsx | 13 +- .../scripts/generate_indicators.js | 4 +- 28 files changed, 894 insertions(+), 734 deletions(-) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.test.ts create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.test.ts create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicator_query_params.ts delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/utils/search.ts diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx index 78c064f72b449..eea2596327fb7 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx @@ -11,6 +11,8 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; import { TimelinesUIStart } from '@kbn/timelines-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; import { SecuritySolutionContext } from '../../containers/security_solution_context'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -20,6 +22,7 @@ import { generateFieldTypeMap } from './mock_field_type_map'; import { mockUiSettingsService } from './mock_kibana_ui_settings_service'; import { mockKibanaTimelinesService } from './mock_kibana_timelines_service'; import { mockTriggersActionsUiService } from './mock_kibana_triggers_actions_ui_service'; +import { InspectorContext } from '../../containers/inspector'; export interface KibanaContextMock { /** @@ -81,13 +84,17 @@ export const StoryProvidersComponent: VFC = ({ return ( - - - - {children} - - - + + + + + + {children} + + + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index 7e046e214b547..a93b6bfe30469 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -17,6 +17,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { KibanaContext } from '../../hooks/use_kibana'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -128,27 +129,31 @@ export const mockedServices = { export const TestProvidersComponent: FC = ({ children }) => ( - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + ); export type MockedSearch = jest.Mocked; export type MockedTimefilter = jest.Mocked; export type MockedTriggersActionsUi = jest.Mocked; +export type MockedQueryService = jest.Mocked; export const mockedSearchService = mockedServices.data.search as MockedSearch; +export const mockedQueryService = mockedServices.data.query as MockedQueryService; export const mockedTimefilterService = mockedServices.data.query.timefilter as MockedTimefilter; export const mockedTriggersActionsUiService = mockedServices.triggersActionsUi as MockedTriggersActionsUi; diff --git a/x-pack/plugins/threat_intelligence/public/common/utils/barchart.test.ts b/x-pack/plugins/threat_intelligence/public/common/utils/barchart.test.ts index 1acf8d0213341..004071059a739 100644 --- a/x-pack/plugins/threat_intelligence/public/common/utils/barchart.test.ts +++ b/x-pack/plugins/threat_intelligence/public/common/utils/barchart.test.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { Aggregation } from '../../modules/indicators/services/fetch_aggregated_indicators'; import { convertAggregationToChartSeries } from './barchart'; -import { Aggregation } from '../../modules/indicators/hooks/use_aggregated_indicators'; const aggregation1: Aggregation = { events: { diff --git a/x-pack/plugins/threat_intelligence/public/common/utils/barchart.ts b/x-pack/plugins/threat_intelligence/public/common/utils/barchart.ts index 93f6b4ce6fd62..c994e7e9f3a3f 100644 --- a/x-pack/plugins/threat_intelligence/public/common/utils/barchart.ts +++ b/x-pack/plugins/threat_intelligence/public/common/utils/barchart.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { +import type { Aggregation, AggregationValue, ChartSeries, -} from '../../modules/indicators/hooks/use_aggregated_indicators'; +} from '../../modules/indicators/services/fetch_aggregated_indicators'; /** * Converts data received from an Elastic search with date_histogram aggregation enabled to something usable in the "@elastic/chart" BarChart component diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.stories.tsx index 465ef3bd1ab78..bb0a1b1205b52 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.stories.tsx @@ -11,8 +11,8 @@ import { Story } from '@storybook/react'; import { TimeRangeBounds } from '@kbn/data-plugin/common'; import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; -import { ChartSeries } from '../../hooks/use_aggregated_indicators'; import { IndicatorsBarChart } from './indicators_barchart'; +import { ChartSeries } from '../../services/fetch_aggregated_indicators'; const mockIndicators: ChartSeries[] = [ { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.test.tsx index 38de7df2b3d4e..19542bdf200a5 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TimeRangeBounds } from '@kbn/data-plugin/common'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { ChartSeries } from '../../hooks/use_aggregated_indicators'; import { IndicatorsBarChart } from './indicators_barchart'; +import { ChartSeries } from '../../services/fetch_aggregated_indicators'; moment.suppressDeprecationWarnings = true; moment.tz.setDefault('UTC'); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.tsx index 75d731b23c3b5..d5535f53a862f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart/indicators_barchart.tsx @@ -11,7 +11,7 @@ import { EuiThemeProvider } from '@elastic/eui'; import { TimeRangeBounds } from '@kbn/data-plugin/common'; import { IndicatorBarchartLegendAction } from '../indicator_barchart_legend_action/indicator_barchart_legend_action'; import { barChartTimeAxisLabelFormatter } from '../../../../common/utils/dates'; -import { ChartSeries } from '../../hooks/use_aggregated_indicators'; +import type { ChartSeries } from '../../services/fetch_aggregated_indicators'; const ID = 'tiIndicator'; const DEFAULT_CHART_HEIGHT = '200px'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx index a9eec2afcf196..213c750c5d1ed 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx @@ -16,9 +16,9 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core/public'; import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; -import { Aggregation, AGGREGATION_NAME } from '../../hooks/use_aggregated_indicators'; import { DEFAULT_TIME_RANGE } from '../../../query_bar/hooks/use_filters/utils'; import { IndicatorsBarChartWrapper } from './indicators_barchart_wrapper'; +import { Aggregation, AGGREGATION_NAME } from '../../services/fetch_aggregated_indicators'; export default { component: IndicatorsBarChartWrapper, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx index 38f22fe34556e..d10ba709bfa2f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx @@ -9,11 +9,11 @@ import React, { VFC } from 'react'; import { EuiDataGridColumnCellActionProps } from '@elastic/eui/src/components/datagrid/data_grid_types'; import { ComponentType } from '../../../../../common/types/component_type'; import { Indicator } from '../../../../../common/types/indicator'; -import { Pagination } from '../../hooks/use_indicators'; import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../utils/field_value'; import { FilterIn } from '../../../query_bar/components/filter_in'; import { FilterOut } from '../../../query_bar/components/filter_out'; +import type { Pagination } from '../../services/fetch_indicators'; export const CELL_TIMELINE_BUTTON_TEST_ID = 'tiIndicatorsTableCellTimelineButton'; export const CELL_FILTER_IN_BUTTON_TEST_ID = 'tiIndicatorsTableCellFilterInButton'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx index 1d563141952e3..034fc13630433 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx @@ -44,7 +44,7 @@ export function WithIndicators() { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx index 71786878bdae3..b110c0f91c319 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx @@ -22,7 +22,7 @@ const tableProps: IndicatorsTableProps = { indicators: [], pagination: { pageSize: 10, pageIndex: 0, pageSizeOptions: [10] }, indicatorCount: 0, - loading: false, + isLoading: false, browserFields: {}, indexPattern: { fields: [], title: '' } as SecuritySolutionDataViewBase, columnSettings: { @@ -60,7 +60,7 @@ describe('', () => { await act(async () => { render( - + ); }); @@ -74,7 +74,7 @@ describe('', () => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx index 3e3035c8c43e4..f12e080b000b9 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx @@ -24,11 +24,11 @@ import { cellRendererFactory } from './cell_renderer'; import { EmptyState } from '../../../../components/empty_state'; import { IndicatorsTableContext, IndicatorsTableContextValue } from './context'; import { IndicatorsFlyout } from '../indicators_flyout/indicators_flyout'; -import { Pagination } from '../../hooks/use_indicators'; import { useToolbarOptions } from './hooks/use_toolbar_options'; import { ColumnSettingsValue } from './hooks/use_column_settings'; import { useFieldTypes } from '../../../../hooks/use_field_types'; import { getFieldSchema } from '../../utils/get_field_schema'; +import { Pagination } from '../../services/fetch_indicators'; export interface IndicatorsTableProps { indicators: Indicator[]; @@ -36,7 +36,10 @@ export interface IndicatorsTableProps { pagination: Pagination; onChangeItemsPerPage: (value: number) => void; onChangePage: (value: number) => void; - loading: boolean; + /** + * If true, no data is available yet + */ + isLoading: boolean; indexPattern: SecuritySolutionDataViewBase; browserFields: BrowserFields; columnSettings: ColumnSettingsValue; @@ -57,7 +60,7 @@ export const IndicatorsTable: VFC = ({ onChangePage, onChangeItemsPerPage, pagination, - loading, + isLoading, browserFields, columnSettings: { columns, columnVisibility, handleResetColumns, handleToggleColumn, sorting }, }) => { @@ -137,7 +140,7 @@ export const IndicatorsTable: VFC = ({ ); const gridFragment = useMemo(() => { - if (loading) { + if (isLoading) { return ( @@ -177,7 +180,7 @@ export const IndicatorsTable: VFC = ({ mappedColumns, indicatorCount, leadingControlColumns, - loading, + isLoading, onChangeItemsPerPage, onChangePage, pagination, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx index be8ea27374a1b..6b3feb7406906 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx @@ -5,34 +5,18 @@ * 2.0. */ -import moment from 'moment'; -import { BehaviorSubject, throwError } from 'rxjs'; -import { renderHook } from '@testing-library/react-hooks'; -import { IKibanaSearchResponse, TimeRangeBounds } from '@kbn/data-plugin/common'; -import { - AGGREGATION_NAME, - RawAggregatedIndicatorsResponse, - useAggregatedIndicators, - UseAggregatedIndicatorsParam, -} from './use_aggregated_indicators'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useAggregatedIndicators, UseAggregatedIndicatorsParam } from './use_aggregated_indicators'; import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils'; import { - TestProvidersComponent, - mockedSearchService, mockedTimefilterService, + TestProvidersComponent, } from '../../../common/mocks/test_providers'; import { useFilters } from '../../query_bar/hooks/use_filters'; +import { createFetchAggregatedIndicators } from '../services/fetch_aggregated_indicators'; -jest.mock('../../query_bar/hooks/use_filters/use_filters'); - -const aggregationResponse = { - rawResponse: { aggregations: { [AGGREGATION_NAME]: { buckets: [] } } }, -}; - -const calculateBoundsResponse: TimeRangeBounds = { - min: moment('1 Jan 2022 06:00:00 GMT'), - max: moment('1 Jan 2022 12:00:00 GMT'), -}; +jest.mock('../services/fetch_aggregated_indicators'); +jest.mock('../../query_bar/hooks/use_filters'); const useAggregatedIndicatorsParams: UseAggregatedIndicatorsParam = { timeRange: DEFAULT_TIME_RANGE, @@ -40,108 +24,78 @@ const useAggregatedIndicatorsParams: UseAggregatedIndicatorsParam = { const stub = () => {}; +const renderUseAggregatedIndicators = () => + renderHook((props) => useAggregatedIndicators(props), { + initialProps: useAggregatedIndicatorsParams, + wrapper: TestProvidersComponent, + }); + +const initialFiltersValue = { + filters: [], + filterQuery: { language: 'kuery', query: '' }, + filterManager: {} as any, + handleSavedQuery: stub, + handleSubmitQuery: stub, + handleSubmitTimeRange: stub, +}; + describe('useAggregatedIndicators()', () => { beforeEach(jest.clearAllMocks); - beforeEach(() => { - mockedSearchService.search.mockReturnValue(new BehaviorSubject(aggregationResponse)); - mockedTimefilterService.timefilter.calculateBounds.mockReturnValue(calculateBoundsResponse); - }); + type MockedCreateFetchAggregatedIndicators = jest.MockedFunction< + typeof createFetchAggregatedIndicators + >; + let aggregatedIndicatorsQuery: jest.MockedFunction< + ReturnType + >; - describe('when mounted', () => { - beforeEach(() => { - (useFilters as jest.MockedFunction).mockReturnValue({ - filters: [], - filterQuery: { language: 'kuery', query: '' }, - filterManager: {} as any, - handleSavedQuery: stub, - handleSubmitQuery: stub, - handleSubmitTimeRange: stub, - }); - - renderHook(() => useAggregatedIndicators(useAggregatedIndicatorsParams), { - wrapper: TestProvidersComponent, - }); - }); + beforeEach(jest.clearAllMocks); - it('should query the database for threat indicators', async () => { - expect(mockedSearchService.search).toHaveBeenCalledTimes(1); - }); + beforeEach(() => { + aggregatedIndicatorsQuery = jest.fn(); + (createFetchAggregatedIndicators as MockedCreateFetchAggregatedIndicators).mockReturnValue( + aggregatedIndicatorsQuery + ); - it('should use the calculateBounds to convert TimeRange to TimeRangeBounds', () => { - expect(mockedTimefilterService.timefilter.calculateBounds).toHaveBeenCalledTimes(1); - }); + (useFilters as jest.MockedFunction).mockReturnValue(initialFiltersValue); }); - describe('when query fails', () => { - beforeEach(async () => { - mockedSearchService.search.mockReturnValue(throwError(() => new Error('some random error'))); - mockedTimefilterService.timefilter.calculateBounds.mockReturnValue(calculateBoundsResponse); - }); + it('should create and call the aggregatedIndicatorsQuery correctly', async () => { + aggregatedIndicatorsQuery.mockResolvedValue([]); - beforeEach(() => { - renderHook(() => useAggregatedIndicators(useAggregatedIndicatorsParams), { - wrapper: TestProvidersComponent, - }); - }); + const { rerender } = renderUseAggregatedIndicators(); - it('should show an error', async () => { - expect(mockedSearchService.showError).toHaveBeenCalledTimes(1); - - expect(mockedSearchService.search).toHaveBeenCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - aggregations: expect.any(Object), - query: expect.any(Object), - size: expect.any(Number), - fields: expect.any(Array), - }), - }), - }), - expect.objectContaining({ - abortSignal: expect.any(AbortSignal), - }) - ); - }); - }); + // indicators service and the query should be called just once + expect( + createFetchAggregatedIndicators as MockedCreateFetchAggregatedIndicators + ).toHaveBeenCalledTimes(1); + expect(aggregatedIndicatorsQuery).toHaveBeenCalledTimes(1); - describe('when query is successful', () => { - beforeEach(async () => { - mockedSearchService.search.mockReturnValue( - new BehaviorSubject>({ - rawResponse: { - aggregations: { - [AGGREGATION_NAME]: { - buckets: [ - { - doc_count: 1, - key: '[Filebeat] AbuseCH Malware', - events: { - buckets: [ - { - doc_count: 0, - key: 1641016800000, - key_as_string: '1 Jan 2022 06:00:00 GMT', - }, - ], - }, - }, - ], - }, - }, - }, - }) - ); - mockedTimefilterService.timefilter.calculateBounds.mockReturnValue(calculateBoundsResponse); + // Ensure the timefilter service is called + expect(mockedTimefilterService.timefilter.calculateBounds).toHaveBeenCalled(); + // Call the query function + expect(aggregatedIndicatorsQuery).toHaveBeenLastCalledWith( + expect.objectContaining({ + filterQuery: { language: 'kuery', query: '' }, + }), + expect.any(AbortSignal) + ); + + // After filter values change, the hook will be re-rendered and should call the query function again, with + // updated values + (useFilters as jest.MockedFunction).mockReturnValue({ + ...initialFiltersValue, + filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" }, }); - it('should call mapping function on every hit', async () => { - const { result } = renderHook(() => useAggregatedIndicators(useAggregatedIndicatorsParams), { - wrapper: TestProvidersComponent, - }); + await act(async () => rerender()); - expect(result.current.indicators.length).toEqual(1); - }); + expect(aggregatedIndicatorsQuery).toHaveBeenCalledTimes(2); + expect(aggregatedIndicatorsQuery).toHaveBeenLastCalledWith( + expect.objectContaining({ + filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" }, + }), + expect.any(AbortSignal) + ); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts index ab583fb0ed95a..2609dbda5eb11 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts @@ -6,25 +6,20 @@ */ import { TimeRange } from '@kbn/es-query'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Subscription } from 'rxjs'; -import { - IEsSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - isErrorResponse, - TimeRangeBounds, -} from '@kbn/data-plugin/common'; +import { useMemo, useState } from 'react'; +import { TimeRangeBounds } from '@kbn/data-plugin/common'; +import { useQuery } from '@tanstack/react-query'; import { useInspector } from '../../../hooks/use_inspector'; import { useFilters } from '../../query_bar/hooks/use_filters'; -import { convertAggregationToChartSeries } from '../../../common/utils/barchart'; import { RawIndicatorFieldId } from '../../../../common/types/indicator'; -import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates'; import { useKibana } from '../../../hooks/use_kibana'; import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { getRuntimeMappings } from '../utils/get_runtime_mappings'; -import { getIndicatorsQuery } from '../utils/get_indicators_query'; +import { + ChartSeries, + createFetchAggregatedIndicators, + FetchAggregatedIndicatorsParams, +} from '../services/fetch_aggregated_indicators'; export interface UseAggregatedIndicatorsParam { /** @@ -55,37 +50,7 @@ export interface UseAggregatedIndicatorsValue { selectedField: string; } -export interface Aggregation { - doc_count: number; - key: string; - events: { - buckets: AggregationValue[]; - }; -} - -export interface AggregationValue { - doc_count: number; - key: number; - key_as_string: string; -} - -export interface ChartSeries { - x: string; - y: number; - g: string; -} - -const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp; const DEFAULT_FIELD = RawIndicatorFieldId.Feed; -export const AGGREGATION_NAME = 'barchartAggregation'; - -export interface RawAggregatedIndicatorsResponse { - aggregations: { - [AGGREGATION_NAME]: { - buckets: Aggregation[]; - }; - }; -} export const useAggregatedIndicators = ({ timeRange = DEFAULT_TIME_RANGE, @@ -100,132 +65,48 @@ export const useAggregatedIndicators = ({ const { inspectorAdapters } = useInspector(); - const searchSubscription$ = useRef(new Subscription()); - const abortController = useRef(new AbortController()); - - const [indicators, setIndicators] = useState([]); const [field, setField] = useState(DEFAULT_FIELD); const { filters, filterQuery } = useFilters(); - const dateRange: TimeRangeBounds = useMemo( - () => queryService.timefilter.timefilter.calculateBounds(timeRange), - [queryService, timeRange] + const aggregatedIndicatorsQuery = useMemo( + () => + createFetchAggregatedIndicators({ + queryService, + searchService, + inspectorAdapter: inspectorAdapters.requests, + }), + [inspectorAdapters, queryService, searchService] ); - const queryToExecute = useMemo(() => { - return getIndicatorsQuery({ timeRange, filters, filterQuery }); - }, [filterQuery, filters, timeRange]); - - const loadData = useCallback(async () => { - const dateFrom: number = (dateRange.min as moment.Moment).toDate().getTime(); - const dateTo: number = (dateRange.max as moment.Moment).toDate().getTime(); - const interval = calculateBarchartColumnTimeInterval(dateFrom, dateTo); - - const request = inspectorAdapters.requests.start('Indicator barchart', {}); - - request.stats({ - indexPattern: { - label: 'Index patterns', - value: selectedPatterns, + const { data } = useQuery( + [ + 'indicatorsBarchart', + { + filters, + field, + filterQuery, + selectedPatterns, + timeRange, }, - }); - - abortController.current = new AbortController(); - - const requestBody = { - aggregations: { - [AGGREGATION_NAME]: { - terms: { - field, - }, - aggs: { - events: { - date_histogram: { - field: TIMESTAMP_FIELD, - fixed_interval: interval, - min_doc_count: 0, - extended_bounds: { - min: dateFrom, - max: dateTo, - }, - }, - }, - }, - }, - }, - fields: [TIMESTAMP_FIELD, field], - size: 0, - query: queryToExecute, - runtime_mappings: getRuntimeMappings(), - }; - - searchSubscription$.current = searchService - .search>( - { - params: { - index: selectedPatterns, - body: requestBody, - }, - }, - { - abortSignal: abortController.current.signal, - } - ) - .subscribe({ - next: (response) => { - if (isCompleteResponse(response)) { - const aggregations: Aggregation[] = - response.rawResponse.aggregations[AGGREGATION_NAME]?.buckets; - const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations); - setIndicators(chartSeries); - searchSubscription$.current.unsubscribe(); - - request.stats({}).ok({ json: response }); - request.json(requestBody); - } else if (isErrorResponse(response)) { - request.error({ json: response }); - searchSubscription$.current.unsubscribe(); - } - }, - error: (requestError) => { - searchService.showError(requestError); - searchSubscription$.current.unsubscribe(); - - if (requestError instanceof Error && requestError.name.includes('Abort')) { - inspectorAdapters.requests.reset(); - } else { - request.error({ json: requestError }); - } - }, - }); - }, [ - dateRange.max, - dateRange.min, - field, - inspectorAdapters.requests, - queryToExecute, - searchService, - selectedPatterns, - ]); - - const onFieldChange = useCallback( - async (f: string) => { - setField(f); - loadData(); - }, - [loadData, setField] + ], + ({ + signal, + queryKey: [_key, queryParams], + }: { + signal?: AbortSignal; + queryKey: [string, FetchAggregatedIndicatorsParams]; + }) => aggregatedIndicatorsQuery(queryParams, signal) ); - useEffect(() => { - loadData(); - - return () => abortController.current.abort(); - }, [loadData]); + const dateRange = useMemo( + () => queryService.timefilter.timefilter.calculateBounds(timeRange), + [queryService.timefilter.timefilter, timeRange] + ); return { dateRange, - indicators, - onFieldChange, + indicators: data || [], + onFieldChange: setField, selectedField: field, }; }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx index 1b1762eb8b67d..0b06208130780 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx @@ -6,17 +6,11 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { - useIndicators, - RawIndicatorsResponse, - UseIndicatorsParams, - UseIndicatorsValue, -} from './use_indicators'; -import { BehaviorSubject, throwError } from 'rxjs'; -import { TestProvidersComponent, mockedSearchService } from '../../../common/mocks/test_providers'; -import { IKibanaSearchResponse } from '@kbn/data-plugin/public'; - -const indicatorsResponse = { rawResponse: { hits: { hits: [], total: 0 } } }; +import { useIndicators, UseIndicatorsParams, UseIndicatorsValue } from './use_indicators'; +import { TestProvidersComponent } from '../../../common/mocks/test_providers'; +import { createFetchIndicators } from '../services/fetch_indicators'; + +jest.mock('../services/fetch_indicators'); const useIndicatorsParams: UseIndicatorsParams = { filters: [], @@ -24,66 +18,71 @@ const useIndicatorsParams: UseIndicatorsParams = { sorting: [], }; +const indicatorsQueryResult = { indicators: [], total: 0 }; + +const renderUseIndicators = (initialProps = useIndicatorsParams) => + renderHook((props) => useIndicators(props), { + initialProps, + wrapper: TestProvidersComponent, + }); + describe('useIndicators()', () => { + type MockedCreateFetchIndicators = jest.MockedFunction; + let indicatorsQuery: jest.MockedFunction>; + beforeEach(jest.clearAllMocks); + beforeEach(() => { + indicatorsQuery = jest.fn(); + (createFetchIndicators as MockedCreateFetchIndicators).mockReturnValue(indicatorsQuery); + }); + describe('when mounted', () => { - beforeEach(() => { - mockedSearchService.search.mockReturnValue(new BehaviorSubject(indicatorsResponse)); - }); + it('should create and call the indicatorsQuery', async () => { + indicatorsQuery.mockResolvedValue(indicatorsQueryResult); - beforeEach(async () => { - renderHook( - () => useIndicators(useIndicatorsParams), - { - wrapper: TestProvidersComponent, - } - ); - }); + const hookResult = renderUseIndicators(); - it('should query the database for threat indicators', async () => { - expect(mockedSearchService.search).toHaveBeenCalledTimes(1); - }); - }); + // isLoading should be true + expect(hookResult.result.current.isLoading).toEqual(true); + + // indicators service and the query should be called just once + expect(createFetchIndicators as MockedCreateFetchIndicators).toHaveBeenCalledTimes(1); + expect(indicatorsQuery).toHaveBeenCalledTimes(1); - describe('when filters change', () => { - beforeEach(() => { - mockedSearchService.search.mockReturnValue(new BehaviorSubject(indicatorsResponse)); + // isLoading should turn to false eventually + await hookResult.waitFor(() => !hookResult.result.current.isLoading); + expect(hookResult.result.current.isLoading).toEqual(false); }); + }); + describe('when inputs change', () => { it('should query the database again and reset page to 0', async () => { - const hookResult = renderHook( - (props) => useIndicators(props), - { - initialProps: useIndicatorsParams, - wrapper: TestProvidersComponent, - } - ); + const hookResult = renderUseIndicators(); + expect(indicatorsQuery).toHaveBeenCalledTimes(1); + + // Change page + await act(async () => hookResult.result.current.onChangePage(42)); - expect(mockedSearchService.search).toHaveBeenCalledTimes(1); - expect(mockedSearchService.search).toHaveBeenLastCalledWith( + expect(indicatorsQuery).toHaveBeenCalledTimes(2); + expect(indicatorsQuery).toHaveBeenLastCalledWith( expect.objectContaining({ - params: expect.objectContaining({ body: expect.objectContaining({ from: 0 }) }), + pagination: expect.objectContaining({ pageIndex: 42 }), }), - expect.objectContaining({ - abortSignal: expect.any(AbortSignal), - }) + expect.any(AbortSignal) ); - // Change page - await act(async () => hookResult.result.current.onChangePage(42)); + // Change page size + await act(async () => hookResult.result.current.onChangeItemsPerPage(50)); - expect(mockedSearchService.search).toHaveBeenLastCalledWith( + expect(indicatorsQuery).toHaveBeenCalledTimes(3); + expect(indicatorsQuery).toHaveBeenLastCalledWith( expect.objectContaining({ - params: expect.objectContaining({ body: expect.objectContaining({ from: 42 * 25 }) }), + pagination: expect.objectContaining({ pageIndex: 0, pageSize: 50 }), }), - expect.objectContaining({ - abortSignal: expect.any(AbortSignal), - }) + expect.any(AbortSignal) ); - expect(mockedSearchService.search).toHaveBeenCalledTimes(2); - // Change filters act(() => hookResult.rerender({ @@ -92,164 +91,13 @@ describe('useIndicators()', () => { }) ); - // From range should be reset to 0 - expect(mockedSearchService.search).toHaveBeenCalledTimes(3); - expect(mockedSearchService.search).toHaveBeenLastCalledWith( + expect(indicatorsQuery).toHaveBeenLastCalledWith( expect.objectContaining({ - params: expect.objectContaining({ body: expect.objectContaining({ from: 0 }) }), - }), - expect.objectContaining({ - abortSignal: expect.any(AbortSignal), - }) - ); - }); - }); - - describe('when query fails', () => { - beforeEach(async () => { - mockedSearchService.search.mockReturnValue(throwError(() => new Error('some random error'))); - - renderHook((props) => useIndicators(props), { - initialProps: useIndicatorsParams, - wrapper: TestProvidersComponent, - }); - }); - - it('should show an error', async () => { - expect(mockedSearchService.showError).toHaveBeenCalledTimes(1); - - expect(mockedSearchService.search).toHaveBeenCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - query: expect.any(Object), - from: expect.any(Number), - size: expect.any(Number), - fields: expect.any(Array), - }), - }), + pagination: expect.objectContaining({ pageIndex: 0 }), + filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" }, }), - expect.objectContaining({ - abortSignal: expect.any(AbortSignal), - }) + expect.any(AbortSignal) ); }); }); - - describe('when query is successful', () => { - beforeEach(async () => { - mockedSearchService.search.mockReturnValue( - new BehaviorSubject>({ - rawResponse: { hits: { hits: [{ fields: {} }], total: 1 } }, - }) - ); - }); - - it('should call mapping function on every hit', async () => { - const { result } = renderHook( - (props) => useIndicators(props), - { - initialProps: useIndicatorsParams, - wrapper: TestProvidersComponent, - } - ); - expect(result.current.indicatorCount).toEqual(1); - }); - }); - - describe('pagination', () => { - beforeEach(async () => { - mockedSearchService.search.mockReturnValue( - new BehaviorSubject>({ - rawResponse: { hits: { hits: [{ fields: {} }], total: 1 } }, - }) - ); - }); - - describe('when page changes', () => { - it('should run the query again with pagination parameters', async () => { - const { result } = renderHook( - () => useIndicators(useIndicatorsParams), - { - wrapper: TestProvidersComponent, - } - ); - - await act(async () => { - result.current.onChangePage(42); - }); - - expect(mockedSearchService.search).toHaveBeenCalledTimes(2); - - expect(mockedSearchService.search).toHaveBeenCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - size: 25, - from: 0, - }), - }), - }), - expect.anything() - ); - - expect(mockedSearchService.search).toHaveBeenLastCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - size: 25, - from: 42 * 25, - }), - }), - }), - expect.anything() - ); - - expect(result.current.pagination.pageIndex).toEqual(42); - }); - - describe('when page size changes', () => { - it('should fetch the first page and update internal page size', async () => { - const { result } = renderHook( - () => useIndicators(useIndicatorsParams), - { - wrapper: TestProvidersComponent, - } - ); - - await act(async () => { - result.current.onChangeItemsPerPage(50); - }); - - expect(mockedSearchService.search).toHaveBeenCalledTimes(3); - - expect(mockedSearchService.search).toHaveBeenCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - size: 25, - from: 0, - }), - }), - }), - expect.anything() - ); - - expect(mockedSearchService.search).toHaveBeenLastCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - body: expect.objectContaining({ - size: 50, - from: 0, - }), - }), - }), - expect.anything() - ); - - expect(result.current.pagination.pageIndex).toEqual(0); - }); - }); - }); - }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts index f06fb03b27d5c..5303b5dae06cb 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts @@ -5,21 +5,14 @@ * 2.0. */ -import { - IEsSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - isErrorResponse, -} from '@kbn/data-plugin/common'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import type { Subscription } from 'rxjs'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { useQuery } from '@tanstack/react-query'; import { useInspector } from '../../../hooks/use_inspector'; import { Indicator } from '../../../../common/types/indicator'; import { useKibana } from '../../../hooks/use_kibana'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { getRuntimeMappings } from '../utils/get_runtime_mappings'; -import { getIndicatorsQuery } from '../utils/get_indicators_query'; +import { createFetchIndicators, FetchParams, Pagination } from '../services/fetch_indicators'; const PAGE_SIZES = [10, 25, 50]; @@ -39,20 +32,16 @@ export interface UseIndicatorsValue { pagination: Pagination; onChangeItemsPerPage: (value: number) => void; onChangePage: (value: number) => void; - loading: boolean; -} -export interface RawIndicatorsResponse { - hits: { - hits: any[]; - total: number; - }; -} + /** + * No data loaded yet + */ + isLoading: boolean; -export interface Pagination { - pageSize: number; - pageIndex: number; - pageSizeOptions: number[]; + /** + * Data loading is in progress (see docs on `isFetching` here: https://tanstack.com/query/v4/docs/guides/queries) + */ + isFetching: boolean; } export const useIndicators = ({ @@ -70,12 +59,20 @@ export const useIndicators = ({ const { inspectorAdapters } = useInspector(); - const searchSubscription$ = useRef(); - const abortController = useRef(new AbortController()); + const onChangeItemsPerPage = useCallback( + (pageSize) => + setPagination((currentPagination) => ({ + ...currentPagination, + pageSize, + pageIndex: 0, + })), + [] + ); - const [indicators, setIndicators] = useState([]); - const [indicatorCount, setIndicatorCount] = useState(0); - const [loading, setLoading] = useState(true); + const onChangePage = useCallback( + (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })), + [] + ); const [pagination, setPagination] = useState({ pageIndex: 0, @@ -83,119 +80,52 @@ export const useIndicators = ({ pageSizeOptions: PAGE_SIZES, }); - const query = useMemo( - () => getIndicatorsQuery({ filters, timeRange, filterQuery }), - [filterQuery, filters, timeRange] - ); - - const loadData = useCallback( - async (from: number, size: number) => { - abortController.current = new AbortController(); - - setLoading(true); - - const request = inspectorAdapters.requests.start('Indicator search', {}); - - request.stats({ - indexPattern: { - label: 'Index patterns', - value: selectedPatterns, - }, - }); - - const requestBody = { - query, - runtime_mappings: getRuntimeMappings(), - fields: [{ field: '*', include_unmapped: true }], - size, - from, - sort: sorting.map(({ id, direction }) => ({ [id]: direction })), - }; - - searchSubscription$.current = searchService - .search>( - { - params: { - index: selectedPatterns, - body: requestBody, - }, - }, - { - abortSignal: abortController.current.signal, - } - ) - .subscribe({ - next: (response) => { - setIndicators(response.rawResponse.hits.hits); - setIndicatorCount(response.rawResponse.hits.total || 0); - - if (isCompleteResponse(response)) { - setLoading(false); - searchSubscription$.current?.unsubscribe(); - request.stats({}).ok({ json: response }); - request.json(requestBody); - } else if (isErrorResponse(response)) { - setLoading(false); - request.error({ json: response }); - searchSubscription$.current?.unsubscribe(); - } - }, - error: (requestError) => { - searchService.showError(requestError); - searchSubscription$.current?.unsubscribe(); - - if (requestError instanceof Error && requestError.name.includes('Abort')) { - inspectorAdapters.requests.reset(); - } else { - request.error({ json: requestError }); - } - - setLoading(false); - }, - }); - }, - [inspectorAdapters.requests, query, searchService, selectedPatterns, sorting] - ); - - const onChangeItemsPerPage = useCallback( - async (pageSize) => { - setPagination((currentPagination) => ({ - ...currentPagination, - pageSize, - pageIndex: 0, - })); + // Go to first page after filters are changed + useEffect(() => { + onChangePage(0); + }, [filters, filterQuery, timeRange, sorting, onChangePage]); - loadData(0, pageSize); - }, - [loadData] + const fetchIndicators = useMemo( + () => createFetchIndicators({ searchService, inspectorAdapter: inspectorAdapters.requests }), + [inspectorAdapters, searchService] ); - const onChangePage = useCallback( - async (pageIndex) => { - setPagination((currentPagination) => ({ ...currentPagination, pageIndex })); - loadData(pageIndex * pagination.pageSize, pagination.pageSize); - }, - [loadData, pagination.pageSize] + const { isLoading, isFetching, data, refetch } = useQuery( + [ + 'indicatorsTable', + { + timeRange, + filterQuery, + filters, + selectedPatterns, + sorting, + pagination, + }, + ], + ({ signal, queryKey: [_key, queryParams] }) => + fetchIndicators(queryParams as FetchParams, signal), + { + /** + * See https://tanstack.com/query/v4/docs/guides/paginated-queries + * This is essential for our ux + */ + keepPreviousData: true, + } ); const handleRefresh = useCallback(() => { onChangePage(0); - }, [onChangePage]); - - // Initial data load (on mount) - useEffect(() => { - handleRefresh(); - - return () => abortController.current.abort(); - }, [handleRefresh]); + refetch(); + }, [onChangePage, refetch]); return { - indicators, - indicatorCount, + indicators: data?.indicators || [], + indicatorCount: data?.total || 0, pagination, onChangePage, onChangeItemsPerPage, - loading, + isLoading, + isFetching, handleRefresh, }; }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx index 9c49ad8126a21..d99d7c0fc4b01 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx @@ -12,8 +12,8 @@ import { isCompleteResponse, } from '@kbn/data-plugin/common'; import { useKibana } from '../../../hooks/use_kibana'; -import type { RawIndicatorsResponse } from './use_indicators'; import { useSourcererDataView } from './use_sourcerer_data_view'; +import type { RawIndicatorsResponse } from '../services/fetch_indicators'; export const useIndicatorsTotalCount = () => { const { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx index 371f917c27746..11bdb8fc8e6ed 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx @@ -35,7 +35,8 @@ describe('', () => { (useIndicators as jest.MockedFunction).mockReturnValue({ indicators: [{ fields: {} }], indicatorCount: 1, - loading: false, + isLoading: false, + isFetching: false, pagination: { pageIndex: 0, pageSize: 10, pageSizeOptions: [10] }, onChangeItemsPerPage: stub, onChangePage: stub, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index 8c138ffec502b..f51e062e1c3cb 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -6,6 +6,7 @@ */ import React, { FC, VFC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { IndicatorsFilters } from './containers/indicators_filters/indicators_filters'; import { IndicatorsBarChartWrapper } from './components/indicators_barchart_wrapper/indicators_barchart_wrapper'; import { IndicatorsTable } from './components/indicators_table/indicators_table'; @@ -19,10 +20,14 @@ import { FieldTypesProvider } from '../../containers/field_types_provider'; import { InspectorProvider } from '../../containers/inspector'; import { useColumnSettings } from './components/indicators_table/hooks/use_column_settings'; +const queryClient = new QueryClient(); + const IndicatorsPageProviders: FC = ({ children }) => ( - - {children} - + + + {children} + + ); const IndicatorsPageContent: VFC = () => { @@ -49,36 +54,35 @@ const IndicatorsPageContent: VFC = () => { }); return ( - - - - - - - - - - - + + + + + + + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.test.ts new file mode 100644 index 0000000000000..c5503f1b32a0c --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.test.ts @@ -0,0 +1,117 @@ +/* + * 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 { mockedQueryService, mockedSearchService } from '../../../common/mocks/test_providers'; +import { BehaviorSubject, throwError } from 'rxjs'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { AGGREGATION_NAME, createFetchAggregatedIndicators } from './fetch_aggregated_indicators'; + +const aggregationResponse = { + rawResponse: { aggregations: { [AGGREGATION_NAME]: { buckets: [] } } }, +}; + +describe('FetchAggregatedIndicatorsService', () => { + beforeEach(jest.clearAllMocks); + + describe('aggregatedIndicatorsQuery()', () => { + describe('when query is successful', () => { + beforeEach(() => { + mockedSearchService.search.mockReturnValue(new BehaviorSubject(aggregationResponse)); + }); + + it('should pass the query down to searchService', async () => { + const aggregatedIndicatorsQuery = createFetchAggregatedIndicators({ + searchService: mockedSearchService, + queryService: mockedQueryService as any, + inspectorAdapter: new RequestAdapter(), + }); + + const result = await aggregatedIndicatorsQuery({ + selectedPatterns: [], + filterQuery: { language: 'kuery', query: '' }, + filters: [], + field: 'myField', + timeRange: { + from: '', + to: '', + }, + }); + + expect(mockedSearchService.search).toHaveBeenCalled(); + expect(mockedSearchService.search).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + body: expect.objectContaining({ + size: 0, + query: expect.objectContaining({ bool: expect.anything() }), + runtime_mappings: { + 'threat.indicator.name': { script: expect.anything(), type: 'keyword' }, + 'threat.indicator.name_origin': { script: expect.anything(), type: 'keyword' }, + }, + aggregations: { + [AGGREGATION_NAME]: { + terms: { + field: 'myField', + }, + aggs: { + events: { + date_histogram: { + field: '@timestamp', + fixed_interval: expect.anything(), + min_doc_count: 0, + extended_bounds: expect.anything(), + }, + }, + }, + }, + }, + fields: ['@timestamp', 'myField'], + }), + index: [], + }), + }), + expect.anything() + ); + + expect(result).toMatchInlineSnapshot(`Array []`); + }); + }); + + describe('when query fails', () => { + beforeEach(() => { + mockedSearchService.search.mockReturnValue( + throwError(() => new Error('some random exception')) + ); + }); + + it('should throw an error', async () => { + const aggregatedIndicatorsQuery = createFetchAggregatedIndicators({ + searchService: mockedSearchService, + queryService: mockedQueryService as any, + inspectorAdapter: new RequestAdapter(), + }); + + try { + await aggregatedIndicatorsQuery({ + selectedPatterns: [], + filterQuery: { language: 'kuery', query: '' }, + filters: [], + field: 'myField', + timeRange: { + from: '', + to: '', + }, + }); + } catch (error) { + expect(error).toMatchInlineSnapshot(`[Error: some random exception]`); + } + + expect.assertions(1); + }); + }); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts new file mode 100644 index 0000000000000..6cf0fea18b2c3 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts @@ -0,0 +1,125 @@ +/* + * 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 { TimeRangeBounds } from '@kbn/data-plugin/common'; +import type { ISearchStart, QueryStart } from '@kbn/data-plugin/public'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { convertAggregationToChartSeries } from '../../../common/utils/barchart'; +import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates'; +import { RawIndicatorFieldId } from '../../../../common/types/indicator'; +import { getIndicatorQueryParams } from '../utils/get_indicator_query_params'; +import { search } from '../utils/search'; + +const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp; + +export const AGGREGATION_NAME = 'barchartAggregation'; + +export interface AggregationValue { + doc_count: number; + key: number; + key_as_string: string; +} + +export interface Aggregation { + doc_count: number; + key: string; + events: { + buckets: AggregationValue[]; + }; +} + +export interface RawAggregatedIndicatorsResponse { + aggregations: { + [AGGREGATION_NAME]: { + buckets: Aggregation[]; + }; + }; +} + +export interface ChartSeries { + x: string; + y: number; + g: string; +} + +export interface FetchAggregatedIndicatorsParams { + selectedPatterns: string[]; + filters: Filter[]; + filterQuery: Query; + timeRange: TimeRange; + field: string; +} + +export const createFetchAggregatedIndicators = + ({ + inspectorAdapter, + searchService, + queryService, + }: { + inspectorAdapter: RequestAdapter; + searchService: ISearchStart; + queryService: QueryStart; + }) => + async ( + { selectedPatterns, timeRange, field, filterQuery, filters }: FetchAggregatedIndicatorsParams, + signal?: AbortSignal + ): Promise => { + const dateRange: TimeRangeBounds = + queryService.timefilter.timefilter.calculateBounds(timeRange); + + const dateFrom: number = (dateRange.min as moment.Moment).toDate().getTime(); + const dateTo: number = (dateRange.max as moment.Moment).toDate().getTime(); + const interval = calculateBarchartColumnTimeInterval(dateFrom, dateTo); + + const sharedParams = getIndicatorQueryParams({ timeRange, filters, filterQuery }); + + const searchRequestBody = { + aggregations: { + [AGGREGATION_NAME]: { + terms: { + field, + }, + aggs: { + events: { + date_histogram: { + field: TIMESTAMP_FIELD, + fixed_interval: interval, + min_doc_count: 0, + extended_bounds: { + min: dateFrom, + max: dateTo, + }, + }, + }, + }, + }, + }, + fields: [TIMESTAMP_FIELD, field], + size: 0, + ...sharedParams, + }; + + const { + aggregations: { [AGGREGATION_NAME]: aggregation }, + } = await search( + searchService, + { + params: { + index: selectedPatterns, + body: searchRequestBody, + }, + }, + { signal, inspectorAdapter, requestName: 'Indicators barchart' } + ); + + const aggregations: Aggregation[] = aggregation?.buckets; + + const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations); + + return chartSeries; + }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.test.ts new file mode 100644 index 0000000000000..388b1c9c9e7c5 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { mockedSearchService } from '../../../common/mocks/test_providers'; +import { BehaviorSubject, throwError } from 'rxjs'; +import { createFetchIndicators } from './fetch_indicators'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; + +const indicatorsResponse = { rawResponse: { hits: { hits: [], total: 0 } } }; + +describe('FetchIndicatorsService', () => { + beforeEach(jest.clearAllMocks); + + describe('indicatorsQuery()', () => { + describe('when query is successful', () => { + beforeEach(() => { + mockedSearchService.search.mockReturnValue(new BehaviorSubject(indicatorsResponse)); + }); + + it('should pass the query down to searchService', async () => { + const indicatorsQuery = createFetchIndicators({ + searchService: mockedSearchService, + inspectorAdapter: new RequestAdapter(), + }); + + const result = await indicatorsQuery({ + pagination: { + pageIndex: 0, + pageSize: 25, + pageSizeOptions: [1, 2, 3], + }, + selectedPatterns: [], + sorting: [], + filterQuery: { language: 'kuery', query: '' }, + filters: [], + }); + + expect(mockedSearchService.search).toHaveBeenCalled(); + expect(mockedSearchService.search).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + body: { + fields: [{ field: '*', include_unmapped: true }], + from: 0, + query: expect.objectContaining({ bool: expect.anything() }), + runtime_mappings: { + 'threat.indicator.name': { script: expect.anything(), type: 'keyword' }, + 'threat.indicator.name_origin': { script: expect.anything(), type: 'keyword' }, + }, + size: 25, + sort: [], + }, + index: [], + }, + }), + expect.anything() + ); + + expect(result).toMatchInlineSnapshot(` + Object { + "indicators": Array [], + "total": 0, + } + `); + }); + }); + + describe('when query fails', () => { + beforeEach(() => { + mockedSearchService.search.mockReturnValue( + throwError(() => new Error('some random exception')) + ); + }); + + it('should throw an error', async () => { + const indicatorsQuery = createFetchIndicators({ + searchService: mockedSearchService, + inspectorAdapter: new RequestAdapter(), + }); + + try { + await indicatorsQuery({ + pagination: { + pageIndex: 0, + pageSize: 25, + pageSizeOptions: [1, 2, 3], + }, + selectedPatterns: [], + sorting: [], + filterQuery: { language: 'kuery', query: '' }, + filters: [], + }); + } catch (error) { + expect(error).toMatchInlineSnapshot(`[Error: some random exception]`); + } + + expect.assertions(1); + }); + }); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts new file mode 100644 index 0000000000000..f06038c320116 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts @@ -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 { ISearchStart } from '@kbn/data-plugin/public'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { Indicator } from '../../../../common/types/indicator'; +import { getIndicatorQueryParams } from '../utils/get_indicator_query_params'; +import { search } from '../utils/search'; + +export interface RawIndicatorsResponse { + hits: { + hits: any[]; + total: number; + }; +} + +export interface Pagination { + pageSize: number; + pageIndex: number; + pageSizeOptions: number[]; +} + +interface FetchIndicatorsDependencies { + searchService: ISearchStart; + inspectorAdapter: RequestAdapter; +} + +export interface FetchParams { + pagination: Pagination; + selectedPatterns: string[]; + sorting: any[]; + filters: Filter[]; + timeRange?: TimeRange; + filterQuery: Query; +} + +type ReactQueryKey = [string, FetchParams]; + +export interface IndicatorsQueryParams { + signal?: AbortSignal; + queryKey: ReactQueryKey; +} + +export interface IndicatorsResponse { + indicators: Indicator[]; + total: number; +} + +export const createFetchIndicators = + ({ searchService, inspectorAdapter }: FetchIndicatorsDependencies) => + async ( + { pagination, selectedPatterns, timeRange, filterQuery, filters, sorting }: FetchParams, + signal?: AbortSignal + ): Promise => { + const sharedParams = getIndicatorQueryParams({ timeRange, filters, filterQuery }); + + const searchRequestBody = { + size: pagination.pageSize, + from: pagination.pageIndex, + fields: [{ field: '*', include_unmapped: true } as const], + sort: sorting.map(({ id, direction }) => ({ [id]: direction })), + ...sharedParams, + }; + + const { + hits: { hits: indicators, total }, + } = await search( + searchService, + { + params: { + index: selectedPatterns, + body: searchRequestBody, + }, + }, + { inspectorAdapter, requestName: 'Indicators table', signal } + ); + + return { indicators, total }; + }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicator_query_params.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicator_query_params.ts new file mode 100644 index 0000000000000..bcaf304fdfd70 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicator_query_params.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { THREAT_QUERY_BASE } from '../../../../common/constants'; +import { RawIndicatorFieldId } from '../../../../common/types/indicator'; +import { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from './display_name'; + +const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp; + +/** + * Prepare shared `runtime_mappings` and `query` fields used within indicator search request + */ +export const getIndicatorQueryParams = ({ + filters, + filterQuery, + timeRange, +}: { + filters: Filter[]; + filterQuery: Query; + timeRange?: TimeRange; +}) => { + return { + runtime_mappings: { + [RawIndicatorFieldId.Name]: { + type: 'keyword', + script: { + source: threatIndicatorNamesScript(), + }, + }, + [RawIndicatorFieldId.NameOrigin]: { + type: 'keyword', + script: { + source: threatIndicatorNamesOriginScript(), + }, + }, + } as const, + query: buildEsQuery( + undefined, + [ + { + query: THREAT_QUERY_BASE, + language: 'kuery', + }, + { + query: filterQuery.query as string, + language: 'kuery', + }, + ], + [ + ...filters, + { + query: { + range: { + [TIMESTAMP_FIELD]: { + gte: timeRange?.from, + lte: timeRange?.to, + }, + }, + }, + meta: {}, + }, + ] + ), + }; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts deleted file mode 100644 index 160fa22db7632..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/get_indicators_query.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; -import { THREAT_QUERY_BASE } from '../../../../common/constants'; -import { RawIndicatorFieldId } from '../../../../common/types/indicator'; - -const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp; - -export const getIndicatorsQuery = ({ - filters, - filterQuery, - timeRange, -}: { - filters: Filter[]; - filterQuery: Query; - timeRange?: TimeRange; -}) => { - return buildEsQuery( - undefined, - [ - { - query: THREAT_QUERY_BASE, - language: 'kuery', - }, - { - query: filterQuery.query as string, - language: 'kuery', - }, - ], - [ - ...filters, - { - query: { - range: { - [TIMESTAMP_FIELD]: { - gte: timeRange?.from, - lte: timeRange?.to, - }, - }, - }, - meta: {}, - }, - ] - ); -}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/search.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/search.ts new file mode 100644 index 0000000000000..49cd371680ce0 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/search.ts @@ -0,0 +1,75 @@ +/* + * 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 { + IEsSearchRequest, + IKibanaSearchResponse, + isCompleteResponse, + isErrorResponse, +} from '@kbn/data-plugin/common'; +import { ISearchStart } from '@kbn/data-plugin/public'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; + +interface SearchOptions { + /** + * Inspector adapter, available in the context + */ + inspectorAdapter: RequestAdapter; + + /** + * Request name registered in the inspector panel + */ + requestName: string; + + /** + * Abort signal + */ + signal?: AbortSignal; +} + +/** + * This is a searchService wrapper that will instrument your query with `inspector` and turn it into a Promise, + * resolved when complete result set is returned or rejected on any error, other than Abort. + */ +export const search = async ( + searchService: ISearchStart, + searchRequest: IEsSearchRequest, + { inspectorAdapter, requestName, signal }: SearchOptions +): Promise => { + const requestId = `${Date.now()}`; + const request = inspectorAdapter.start(requestName, { id: requestId }); + + return new Promise((resolve, reject) => { + searchService + .search>(searchRequest, { + abortSignal: signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + request.stats({}).ok({ json: response }); + request.json(searchRequest.params?.body || {}); + + resolve(response.rawResponse); + } else if (isErrorResponse(response)) { + request.error({ json: response }); + reject(response); + } + }, + error: (requestError) => { + if (requestError instanceof Error && requestError.name.includes('Abort')) { + inspectorAdapter.resetRequest(requestId); + } else { + request.error({ json: requestError }); + } + + searchService.showError(requestError); + reject(requestError); + }, + }); + }); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.tsx index d2739476d7d6b..e564c898f894a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.tsx @@ -82,15 +82,17 @@ export const QueryBar = memo( }) => { const onQuerySubmit = useCallback( ({ query, dateRange }: QueryPayload) => { - if (isQuery(query) && !deepEqual(query, filterQuery)) { - onSubmitQuery(query); - } - if (dateRange != null) { onSubmitDateRange(dateRange); } + + if (isQuery(query) && !deepEqual(query, filterQuery)) { + onSubmitQuery(query); + } else { + onRefresh(); + } }, - [filterQuery, onSubmitDateRange, onSubmitQuery] + [filterQuery, onRefresh, onSubmitDateRange, onSubmitQuery] ); const onQueryChange = useCallback( @@ -169,7 +171,6 @@ export const QueryBar = memo( dataTestSubj={dataTestSubj} savedQuery={savedQuery} displayStyle={displayStyle} - onRefresh={onRefresh} /> ); } diff --git a/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js b/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js index 44e0ad166353c..0d5c170965fe7 100644 --- a/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js +++ b/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js @@ -50,7 +50,7 @@ const main = async () => { 'threat.feed.name': { type: 'keyword', }, - 'threat.indicator.url.original': { + 'threat.indicator.url.full': { type: 'keyword', }, 'threat.indicator.first_seen': { @@ -92,7 +92,7 @@ const main = async () => { 'threat.indicator.first_seen': timestamp, 'threat.feed.name': FEED_NAMES[Math.ceil(Math.random() * FEED_NAMES.length) - 1], 'threat.indicator.type': 'url', - 'threat.indicator.url.original': faker.internet.url(), + 'threat.indicator.url.full': faker.internet.url(), 'event.type': 'indicator', 'event.category': 'threat', }, From cc9827e396b166fbdad536381420272d614c0b98 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 27 Sep 2022 15:20:19 +0200 Subject: [PATCH 065/172] [APM] Generate Service metrics with synthrace (#141739) --- .../src/cli/run_synthtrace.ts | 4 +- .../src/cli/utils/synthtrace_worker.ts | 4 +- ...gator.ts => service_metrics_aggregator.ts} | 68 ++++++++++++++----- 3 files changed, 55 insertions(+), 21 deletions(-) rename packages/kbn-apm-synthtrace/src/lib/apm/aggregators/{service_latency_aggregator.ts => service_metrics_aggregator.ts} (78%) diff --git a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts index ecfbd4a387f34..517d2d61d799f 100644 --- a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts +++ b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts @@ -15,7 +15,7 @@ import { parseRunCliFlags } from './utils/parse_run_cli_flags'; import { getCommonServices } from './utils/get_common_services'; import { ApmSynthtraceKibanaClient } from '../lib/apm/client/apm_synthtrace_kibana_client'; import { StreamAggregator } from '../lib/stream_aggregator'; -import { ServiceLatencyAggregator } from '../lib/apm/aggregators/service_latency_aggregator'; +import { ServicMetricsAggregator } from '../lib/apm/aggregators/service_metrics_aggregator'; function options(y: Argv) { return y @@ -207,7 +207,7 @@ export function runSynthtrace() { } const aggregators: StreamAggregator[] = []; const registry = new Map StreamAggregator[]>([ - ['service', () => [new ServiceLatencyAggregator()]], + ['service', () => [new ServicMetricsAggregator()]], ]); if (runOptions.streamProcessors && runOptions.streamProcessors.length > 0) { for (const processorName of runOptions.streamProcessors) { diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts b/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts index 720b1b0527e80..54ce5b1b2e328 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts @@ -16,7 +16,7 @@ import { StreamProcessor } from '../../lib/stream_processor'; import { Scenario } from '../scenario'; import { EntityIterable, Fields } from '../../..'; import { StreamAggregator } from '../../lib/stream_aggregator'; -import { ServiceLatencyAggregator } from '../../lib/apm/aggregators/service_latency_aggregator'; +import { ServicMetricsAggregator } from '../../lib/apm/aggregators/service_metrics_aggregator'; // logging proxy to main thread, ensures we see real time logging const l = { @@ -63,7 +63,7 @@ async function setup() { parentPort?.postMessage({ workerIndex, lastTimestamp: item['@timestamp'] }); } }; - const aggregators: StreamAggregator[] = [new ServiceLatencyAggregator()]; + const aggregators: StreamAggregator[] = [new ServicMetricsAggregator()]; // If we are sending data to apm-server we do not have to create any aggregates in the stream processor streamProcessor = new StreamProcessor({ version, diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_latency_aggregator.ts b/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts similarity index 78% rename from packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_latency_aggregator.ts rename to packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts index e28ba234b2a49..618c9e52b9f2c 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_latency_aggregator.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts @@ -12,12 +12,14 @@ import { ApmFields } from '../apm_fields'; import { Fields } from '../../entity'; import { StreamAggregator } from '../../stream_aggregator'; -type LatencyState = { +type AggregationState = { count: number; min: number; max: number; sum: number; timestamp: number; + failure_count: number; + success_count: number; } & Pick; export type ServiceFields = Fields & @@ -35,15 +37,22 @@ export type ServiceFields = Fields & | 'transaction.type' > & Partial<{ - 'transaction.duration.aggregate': { - min: number; - max: number; - sum: number; - value_count: number; + _doc_count: number; + transaction: { + duration: { + summary: { + min: number; + max: number; + sum: number; + value_count: number; + }; + }; + failure_count: number; + success_count: number; }; }>; -export class ServiceLatencyAggregator implements StreamAggregator { +export class ServicMetricsAggregator implements StreamAggregator { public readonly name; constructor() { @@ -68,7 +77,7 @@ export class ServiceLatencyAggregator implements StreamAggregator { duration: { type: 'object', properties: { - aggregate: { + summary: { type: 'aggregate_metric_double', metrics: ['min', 'max', 'sum', 'value_count'], default_metric: 'sum', @@ -76,6 +85,12 @@ export class ServiceLatencyAggregator implements StreamAggregator { }, }, }, + failure_count: { + type: { type: 'long' }, + }, + success_count: { + type: { type: 'long' }, + }, }, }, service: { @@ -99,7 +114,7 @@ export class ServiceLatencyAggregator implements StreamAggregator { return null; } - private state: Record = {}; + private state: Record = {}; private processedComponent: number = 0; @@ -120,13 +135,25 @@ export class ServiceLatencyAggregator implements StreamAggregator { 'service.name': service, 'service.environment': environment, 'transaction.type': transactionType, + failure_count: 0, + success_count: 0, }; } + + const state = this.state[key]; + state.count++; + + switch (event['event.outcome']) { + case 'failure': + state.failure_count++; + break; + case 'success': + state.success_count++; + break; + } + const duration = Number(event['transaction.duration.us']); if (duration >= 0) { - const state = this.state[key]; - - state.count++; state.sum += duration; if (duration > state.max) state.max = duration; if (duration < state.min) state.min = Math.min(0, duration); @@ -164,17 +191,24 @@ export class ServiceLatencyAggregator implements StreamAggregator { const component = Date.now() % 100; const state = this.state[key]; return { + _doc_count: state.count, '@timestamp': state.timestamp + random(0, 100) + component + this.processedComponent, 'metricset.name': 'service', 'processor.event': 'metric', 'service.name': state['service.name'], 'service.environment': state['service.environment'], 'transaction.type': state['transaction.type'], - 'transaction.duration.aggregate': { - min: state.min, - max: state.max, - sum: state.sum, - value_count: state.count, + transaction: { + duration: { + summary: { + min: state.min, + max: state.max, + sum: state.sum, + value_count: state.count, + }, + }, + failure_count: state.failure_count, + success_count: state.success_count, }, }; } From f2937926ffd79714bf18af587d4b4b6c8fc14b6f Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Tue, 27 Sep 2022 16:27:43 +0300 Subject: [PATCH 066/172] fix: don't show process ancestry insights in some cases (#141751) --- .../event_details/insights/insights.test.tsx | 33 +++++++++++++++++-- .../event_details/insights/insights.tsx | 21 ++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.test.tsx index 747b2ec939f71..b31cf1d1252bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.test.tsx @@ -61,7 +61,7 @@ jest.mock('../../../hooks/use_experimental_features', () => ({ })); const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; -const data: TimelineEventsDetailsItem[] = [ +const dataWithoutAgentType: TimelineEventsDetailsItem[] = [ { category: 'process', field: 'process.entity_id', @@ -82,6 +82,16 @@ const data: TimelineEventsDetailsItem[] = [ }, ]; +const data: TimelineEventsDetailsItem[] = [ + ...dataWithoutAgentType, + { + category: 'agent', + field: 'agent.type', + isObjectArray: false, + values: ['endpoint'], + }, +]; + describe('Insights', () => { beforeEach(() => { mockUseGetUserCasesPermissions.mockReturnValue(noCasesPermissions()); @@ -123,9 +133,11 @@ describe('Insights', () => { describe('with feature flag enabled', () => { describe('with platinum license', () => { - it('should show insights for related alerts by process ancestry', () => { + beforeAll(() => { licenseServiceMock.isPlatinumPlus.mockReturnValue(true); + }); + it('should show insights for related alerts by process ancestry', () => { render( @@ -137,6 +149,23 @@ describe('Insights', () => { screen.queryByRole('link', { name: new RegExp(i18n.ALERT_UPSELL) }) ).not.toBeInTheDocument(); }); + + describe('without process ancestry info', () => { + it('should not show the related alerts by process ancestry insights module', () => { + render( + + + + ); + + expect(screen.queryByTestId('related-alerts-by-ancestry')).not.toBeInTheDocument(); + }); + }); }); describe('without platinum license', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.tsx index 358b2d8833a63..520f30fb9c31d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/insights.tsx @@ -40,7 +40,6 @@ export const Insights = React.memo( 'insightsRelatedAlertsByProcessAncestry' ); const hasAtLeastPlatinum = useLicense().isPlatinumPlus(); - const processEntityField = find({ category: 'process', field: 'process.entity_id' }, data); const originalDocumentId = find( { category: 'kibana', field: 'kibana.alert.ancestors.id' }, data @@ -49,7 +48,12 @@ export const Insights = React.memo( { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, data ); - const hasProcessEntityInfo = hasData(processEntityField); + const agentTypeField = find({ category: 'agent', field: 'agent.type' }, data); + const eventModuleField = find({ category: 'event', field: 'event.module' }, data); + const processEntityField = find({ category: 'process', field: 'process.entity_id' }, data); + const hasProcessEntityInfo = + hasData(processEntityField) && + hasCorrectAgentTypeAndEventModule(agentTypeField, eventModuleField); const processSessionField = find( { category: 'process', field: 'process.entry_leader.entity_id' }, @@ -147,4 +151,17 @@ export const Insights = React.memo( } ); +function hasCorrectAgentTypeAndEventModule( + agentTypeField?: TimelineEventsDetailsItem, + eventModuleField?: TimelineEventsDetailsItem +): boolean { + return ( + hasData(agentTypeField) && + (agentTypeField.values[0] === 'endpoint' || + (agentTypeField.values[0] === 'winlogbeat' && + hasData(eventModuleField) && + eventModuleField.values[0] === 'sysmon')) + ); +} + Insights.displayName = 'Insights'; From fb136431c5a47fe8bb9fab4672d88a80f1d8b760 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 27 Sep 2022 09:30:17 -0400 Subject: [PATCH 067/172] [Security Solution][Timeline][Fix] - Use dynamic language for search bar (#137606) * use search bar language in place of hard coded kuery * update tests * update snapshot * add cypress --- .../e2e/timelines/search_or_filter.cy.ts | 15 ++++++++++++++- .../cypress/screens/timeline.ts | 10 ++++++++++ .../cypress/tasks/timeline.ts | 19 +++++++++++++++++++ .../__snapshots__/index.test.tsx.snap | 1 + .../timeline/query_tab_content/index.test.tsx | 1 + .../timeline/query_tab_content/index.tsx | 11 +++++++++-- .../timeline/epic_local_storage.test.tsx | 1 + 7 files changed, 55 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts index d5a084f65fac8..39420b27e3866 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts @@ -16,7 +16,11 @@ import { cleanKibana } from '../../tasks/common'; import { login, visit, visitWithoutDateRange } from '../../tasks/login'; import { openTimelineUsingToggle } from '../../tasks/security_main'; -import { executeTimelineKQL } from '../../tasks/timeline'; +import { + changeTimelineQueryLanguage, + executeTimelineKQL, + executeTimelineSearch, +} from '../../tasks/timeline'; import { waitForTimelinesPanelToBeLoaded } from '../../tasks/timelines'; import { HOSTS_URL, TIMELINES_URL } from '../../urls/navigation'; @@ -38,6 +42,15 @@ describe('Timeline search and filters', () => { cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.be.gt(0)); }); + + it('executes a Lucene query', () => { + const messageProcessQuery = 'message:Process\\ zsh*'; + openTimelineUsingToggle(); + changeTimelineQueryLanguage('lucene'); + executeTimelineSearch(messageProcessQuery); + + cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.be.gt(0)); + }); }); describe('Update kqlMode for timeline', () => { diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 51ce3b38d4d6f..87d70a73dbd1f 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -215,6 +215,16 @@ export const TIMELINE_KQLMODE_SEARCH = '[data-test-subj="kqlModePopoverSearch"]' export const TIMELINE_KQLMODE_FILTER = '[data-test-subj="kqlModePopoverFilter"]'; +export const QUERYBAR_MENU_POPOVER = '[data-test-subj="queryBarMenuPopover"]'; + +export const TIMELINE_SHOWQUERYBARMENU_BUTTON = `${TIMELINE_FLYOUT} [data-test-subj="showQueryBarMenu"]`; + +export const TIMELINE_SWITCHQUERYLANGUAGE_BUTTON = '[data-test-subj="switchQueryLanguageButton"]'; + +export const TIMELINE_LUCENELANGUAGE_BUTTON = '[data-test-subj="luceneLanguageMenuItem"]'; + +export const TIMELINE_KQLLANGUAGE_BUTTON = '[data-test-subj="kqlLanguageMenuItem"]'; + export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; export const TIMELINE_TITLE_INPUT = '[data-test-subj="save-timeline-title"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index fda2ea08769ef..a83e1157e98d7 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -74,6 +74,11 @@ import { EMPTY_DROPPABLE_DATA_PROVIDER_GROUP, GET_TIMELINE_GRID_CELL, HOVER_ACTIONS, + TIMELINE_SWITCHQUERYLANGUAGE_BUTTON, + TIMELINE_SHOWQUERYBARMENU_BUTTON, + TIMELINE_LUCENELANGUAGE_BUTTON, + TIMELINE_KQLLANGUAGE_BUTTON, + TIMELINE_QUERY, } from '../screens/timeline'; import { REFRESH_BUTTON, TIMELINE } from '../screens/timelines'; import { drag, drop } from './common'; @@ -170,6 +175,16 @@ export const addFilter = (filter: TimelineFilter): Cypress.Chainable { + cy.get(TIMELINE_SHOWQUERYBARMENU_BUTTON).click(); + cy.get(TIMELINE_SWITCHQUERYLANGUAGE_BUTTON).click(); + if (language === 'lucene') { + cy.get(TIMELINE_LUCENELANGUAGE_BUTTON).click(); + } else { + cy.get(TIMELINE_KQLLANGUAGE_BUTTON).click(); + } +}; + export const addDataProvider = (filter: TimelineFilter): Cypress.Chainable> => { cy.get(TIMELINE_ADD_FIELD_BUTTON).click(); cy.get(LOADING_INDICATOR).should('not.exist'); @@ -280,6 +295,10 @@ export const executeTimelineKQL = (query: string) => { cy.get(`${SEARCH_OR_FILTER_CONTAINER} textarea`).type(`${query} {enter}`); }; +export const executeTimelineSearch = (query: string) => { + cy.get(TIMELINE_QUERY).type(`${query} {enter}`, { force: true }); +}; + export const expandFirstTimelineEventDetails = () => { cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap index 5b0757ee775f7..3205a30d35c04 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap @@ -305,6 +305,7 @@ In other use cases the message field can be used to concatenate different values } kqlMode="search" kqlQueryExpression=" " + kqlQueryLanguage="kuery" onEventClosed={[MockFunction]} renderCellValue={[Function]} rowRenderers={ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index b610adbe6da50..cc48a58717eee 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -137,6 +137,7 @@ describe('Timeline', () => { itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as QueryTabContentComponentProps['kqlMode'], kqlQueryExpression: ' ', + kqlQueryLanguage: 'kuery', onEventClosed: jest.fn(), renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index c38304d798418..414604109e580 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -179,6 +179,7 @@ export const QueryTabContentComponent: React.FC = ({ itemsPerPageOptions, kqlMode, kqlQueryExpression, + kqlQueryLanguage, onEventClosed, renderCellValue, rowRenderers, @@ -223,8 +224,8 @@ export const QueryTabContentComponent: React.FC = ({ query: string; language: KueryFilterQueryKind; } = useMemo( - () => ({ query: kqlQueryExpression.trim(), language: 'kuery' }), - [kqlQueryExpression] + () => ({ query: kqlQueryExpression.trim(), language: kqlQueryLanguage }), + [kqlQueryExpression, kqlQueryLanguage] ); const combinedQueries = combineQueries({ @@ -493,6 +494,11 @@ const makeMapStateToProps = () => { ? ' ' : kqlQueryTimeline?.expression ?? ''; + const kqlQueryLanguage = + isEmpty(dataProviders) && timelineType === 'template' + ? 'kuery' + : kqlQueryTimeline?.kind ?? 'kuery'; + return { activeTab, columns, @@ -506,6 +512,7 @@ const makeMapStateToProps = () => { itemsPerPageOptions, kqlMode, kqlQueryExpression, + kqlQueryLanguage, showCallOutUnauthorizedMsg: getShowCallOutUnauthorizedMsg(state), show, showExpandedDetails: diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index 42a7460a129db..e68530e4579cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -78,6 +78,7 @@ describe('epicLocalStorage', () => { itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as QueryTabContentComponentProps['kqlMode'], kqlQueryExpression: '', + kqlQueryLanguage: 'kuery', onEventClosed: jest.fn(), renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, From ab00a759375efdeb7207595d934043d2d81d1ca8 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Tue, 27 Sep 2022 15:43:09 +0200 Subject: [PATCH 068/172] [Fleet] Fix error occurring when multiple package policies are upgraded (#141625) * [Fleet] Fix error occurring when multiple package policies are upgraded * Add integration test * Fix test * Improve testing Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/services/package_policy.test.ts | 152 ++++++++++++++++++ .../fleet/server/services/package_policy.ts | 33 ++-- .../server/services/package_policy_service.ts | 2 +- 3 files changed, 172 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 9bbeb8fd67232..10a076d734f89 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -666,6 +666,158 @@ describe('Package policy service', () => { ).rejects.toThrow('Saved object [abc/123] conflict'); }); + it('should fail to update if the name already exists on another policy', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: 'existing-package-policy', + type: 'ingest-package-policies', + score: 1, + references: [], + version: '1.0.0', + attributes: { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: 'policy-id-1', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + }, + }, + ], + }); + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type: 'abcd', + references: [], + version: 'test', + attributes: {}, + }); + savedObjectsClient.update.mockImplementation( + async ( + type: string, + id: string, + attrs: any + ): Promise> => { + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type, + references: [], + version: 'test', + attributes: attrs, + }); + return attrs; + } + ); + const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await expect( + packagePolicyService.update( + savedObjectsClient, + elasticsearchClient, + 'the-package-policy-id', + { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: '93c46720-c217-11ea-9906-b5b8a21b268e', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + } + ) + ).rejects.toThrow( + 'An integration policy with the name endpoint-1 already exists. Please rename it or choose a different name.' + ); + }); + + it('should not fail to update if skipUniqueNameVerification when the name already exists on another policy', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: 'existing-package-policy', + type: 'ingest-package-policies', + score: 1, + references: [], + version: '1.0.0', + attributes: { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: 'policy-id-1', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + }, + }, + ], + }); + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type: 'abcd', + references: [], + version: 'test', + attributes: {}, + }); + savedObjectsClient.update.mockImplementation( + async ( + type: string, + id: string, + attrs: any + ): Promise> => { + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type, + references: [], + version: 'test', + attributes: attrs, + }); + return attrs; + } + ); + const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const result = await packagePolicyService.update( + savedObjectsClient, + elasticsearchClient, + 'the-package-policy-id', + { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: '93c46720-c217-11ea-9906-b5b8a21b268e', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + }, + { skipUniqueNameVerification: true } + ); + expect(result.name).toEqual('endpoint-1'); + }); + it('should throw if the user try to update input vars that are frozen', async () => { const savedObjectsClient = savedObjectsClientMock.create(); const mockPackagePolicy = createPackagePolicyMock(); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 7f294ca89d65f..f52316dd4452b 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -469,7 +469,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { esClient: ElasticsearchClient, id: string, packagePolicyUpdate: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser; force?: boolean }, + options?: { user?: AuthenticatedUser; force?: boolean; skipUniqueNameVerification?: boolean }, currentVersion?: string ): Promise { const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() }; @@ -479,22 +479,22 @@ class PackagePolicyClientImpl implements PackagePolicyClient { if (packagePolicyUpdate.is_managed && !options?.force) { throw new PackagePolicyRestrictionRelatedError(`Cannot update package policy ${id}`); } - if (!oldPackagePolicy) { throw new Error('Package policy not found'); } - // Check that the name does not exist already but exclude the current package policy - const existingPoliciesWithName = await this.list(soClient, { - perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}"`, - }); - const filtered = (existingPoliciesWithName?.items || []).filter((p) => p.id !== id); - - if (filtered.length > 0) { - throw new FleetError( - `An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.` - ); + if (!options?.skipUniqueNameVerification) { + // Check that the name does not exist already but exclude the current package policy + const existingPoliciesWithName = await this.list(soClient, { + perPage: SO_SEARCH_LIMIT, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}"`, + }); + const filtered = (existingPoliciesWithName?.items || []).filter((p) => p.id !== id); + if (filtered.length > 0) { + throw new FleetError( + `An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.` + ); + } } let inputs = restOfPackagePolicy.inputs.map((input) => @@ -929,12 +929,17 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ); updatePackagePolicy.elasticsearch = packageInfo.elasticsearch; + const updateOptions = { + skipUniqueNameVerification: true, + ...options, + }; + await this.update( soClient, esClient, id, updatePackagePolicy, - options, + updateOptions, packagePolicy.package!.version ); diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts index 4dd36a8c4fdf4..0a16a01c3dc4c 100644 --- a/x-pack/plugins/fleet/server/services/package_policy_service.ts +++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts @@ -97,7 +97,7 @@ export interface PackagePolicyClient { esClient: ElasticsearchClient, id: string, packagePolicyUpdate: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser; force?: boolean }, + options?: { user?: AuthenticatedUser; force?: boolean; skipUniqueNameVerification?: boolean }, currentVersion?: string ): Promise; From 5adfb8f0704d63ed9ea8bdf78ebff96a32791ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 27 Sep 2022 15:53:29 +0200 Subject: [PATCH 069/172] New extension point for multi step onboarding process. Also hides advanced options for endpoint package (#141748) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/dev_docs/fleet_ui_extensions.md | 6 ++ .../components/page_steps/add_integration.tsx | 79 +++++++++++++------ x-pack/plugins/fleet/public/index.ts | 3 + .../fleet/public/types/ui_extensions.ts | 22 +++++- ...int_policy_create_multi_step_extension.tsx | 18 +++++ ...int_policy_create_multi_step_extension.tsx | 19 +++++ .../security_solution/public/plugin.tsx | 7 ++ 7 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_multi_step_extension.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_multi_step_extension.tsx diff --git a/x-pack/plugins/fleet/dev_docs/fleet_ui_extensions.md b/x-pack/plugins/fleet/dev_docs/fleet_ui_extensions.md index 9dcb7112f7ef0..46788ecac5da0 100644 --- a/x-pack/plugins/fleet/dev_docs/fleet_ui_extensions.md +++ b/x-pack/plugins/fleet/dev_docs/fleet_ui_extensions.md @@ -33,6 +33,12 @@ export class Plugin { component: LazyEndpointPolicyCreateExtension, }); + registerExtension({ + package: 'endpoint', + view: 'package-policy-create-multi-step', + component: LazyEndpointPolicyCreateMultiStepExtension, + }); + registerExtension({ package: 'endpoint', view: 'package-detail-custom', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx index bce4d71b04259..c5ccd2916e465 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { safeLoad } from 'js-yaml'; @@ -19,9 +19,9 @@ import { isVerificationError } from '../../../../../../../../services'; import type { MultiPageStepLayoutProps } from '../../types'; import type { PackagePolicyFormState } from '../../../types'; import type { NewPackagePolicy } from '../../../../../../types'; -import { sendCreatePackagePolicy, useStartServices } from '../../../../../../hooks'; +import { sendCreatePackagePolicy, useStartServices, useUIExtension } from '../../../../../../hooks'; import type { RequestError } from '../../../../../../hooks'; -import { Error } from '../../../../../../components'; +import { Error, ExtensionWrapper } from '../../../../../../components'; import { sendGeneratePackagePolicy } from '../../hooks'; import { CreatePackagePolicyBottomBar, StandaloneModeWarningCallout } from '..'; import type { PackagePolicyValidationResults } from '../../../services'; @@ -212,6 +212,55 @@ export const AddIntegrationPageStep: React.FC = (props getBasePolicy(); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const extensionView = useUIExtension(packageInfo.name ?? '', 'package-policy-create-multi-step'); + const addIntegrationExtensionView = useMemo(() => { + return ( + extensionView && ( + + + + ) + ); + }, [packagePolicy, extensionView]); + + const content = useMemo(() => { + if (packageInfo.name !== 'endpoint') { + return ( + <> + + + {validationResults && ( + + + + )} + + ); + } + }, [ + formState, + integrationInfo?.name, + packageInfo, + packagePolicy, + updatePackagePolicy, + validationResults, + ]); + if (!agentPolicy) { return ( = (props return ( <> {isManaged ? null : } - - - {validationResults && ( - - - - )} + {content} + {addIntegrationExtensionView} setIsManaged(true)} diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index a55937999c4d6..823d46becb4ff 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -35,6 +35,9 @@ export type { PackagePolicyCreateExtension, PackagePolicyCreateExtensionComponent, PackagePolicyCreateExtensionComponentProps, + PackagePolicyCreateMultiStepExtension, + PackagePolicyCreateMultiStepExtensionComponent, + PackagePolicyCreateMultiStepExtensionComponentProps, PackagePolicyEditExtension, PackagePolicyEditExtensionComponent, PackagePolicyEditExtensionComponentProps, diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index c3a9f05fc7d9f..24f9fef730613 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -132,6 +132,25 @@ export interface PackagePolicyCreateExtension { Component: LazyExoticComponent; } +/** + * UI Component Extension is used on the pages displaying the ability to Create a multi step + * Integration Policy + */ +export type PackagePolicyCreateMultiStepExtensionComponent = + ComponentType; + +export interface PackagePolicyCreateMultiStepExtensionComponentProps { + /** The integration policy being created */ + newPolicy: NewPackagePolicy; +} + +/** Extension point registration contract for Integration Policy Create views in multi-step onboarding */ +export interface PackagePolicyCreateMultiStepExtension { + package: string; + view: 'package-policy-create-multi-step'; + Component: LazyExoticComponent; +} + /** * UI Component Extension is used to display a Custom tab (and view) under a given Integration */ @@ -178,4 +197,5 @@ export type UIExtensionPoint = | PackagePolicyCreateExtension | PackageAssetsExtension | PackageGenericErrorsListExtension - | AgentEnrollmentFlyoutFinalStepExtension; + | AgentEnrollmentFlyoutFinalStepExtension + | PackagePolicyCreateMultiStepExtension; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_multi_step_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_multi_step_extension.tsx new file mode 100644 index 0000000000000..747d0990b8fc0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_multi_step_extension.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import type { PackagePolicyCreateMultiStepExtensionComponentProps } from '@kbn/fleet-plugin/public'; + +/** + * TBD: A component to be displayed in multi step onboarding process. + */ +export const EndpointPolicyCreateMultiStepExtension = + memo(({ newPolicy }) => { + return <>; + }); +EndpointPolicyCreateMultiStepExtension.displayName = 'EndpointPolicyCreateMultiStepExtension'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_multi_step_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_multi_step_extension.tsx new file mode 100644 index 0000000000000..b989c07fef45b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_multi_step_extension.tsx @@ -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 { lazy } from 'react'; +import type { PackagePolicyCreateMultiStepExtensionComponent } from '@kbn/fleet-plugin/public'; + +export const LazyEndpointPolicyCreateMultiStepExtension = + lazy(async () => { + const { EndpointPolicyCreateMultiStepExtension } = await import( + './endpoint_policy_create_multi_step_extension' + ); + return { + default: EndpointPolicyCreateMultiStepExtension, + }; + }); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 7affdd066742f..6ab6fcd6e628c 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -60,6 +60,7 @@ import { ExperimentalFeaturesService } from './common/experimental_features_serv import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; +import { LazyEndpointPolicyCreateMultiStepExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_multi_step_extension'; import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension'; import { getLazyEndpointPolicyResponseExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_response_extension'; import { getLazyEndpointGenericErrorsListExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list'; @@ -246,6 +247,12 @@ export class Plugin implements IPlugin Date: Tue, 27 Sep 2022 09:55:44 -0400 Subject: [PATCH 070/172] change from host.os.name to host.os.type (#141432) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../infra/public/pages/metrics/hosts/components/hosts_table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 9975de8db7375..57b3ab0e683ac 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -64,7 +64,7 @@ const getLensHostsTable = ( dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: 'host.os.name', + sourceField: 'host.os.type', isBucketed: true, params: { size: 10000, From 9738b04524ab86be302256cdc7f2a420d98bec33 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Tue, 27 Sep 2022 16:01:23 +0200 Subject: [PATCH 071/172] [Security Solution] Rule bulk schedule fixes and test coverage expansion (#141604) ## Summary Fixes issues, nits and [expands test coverage](https://docs.google.com/document/d/116x7ITTTJQ6cTiwaGK831_f6Ox7XB3qyLiHxC3Cmf8w/edit#) for PR: https://github.com/elastic/kibana/pull/140166 - Extends definition of `TimeUnit` type and its tests - Adds e2e test to test default values of Bulk Schedule flyout - Corrects copy as reported by @elastic/security-docs - Corrects validation for Interval field when editing rule schedule individually ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../src/time_duration/index.test.ts | 24 +++++++++++++++++- .../src/time_duration/index.ts | 6 ++++- .../e2e/detection_rules/bulk_edit_rules.cy.ts | 13 ++++++++++ .../cypress/screens/rules_bulk_edit.ts | 2 ++ .../cypress/tasks/rules_bulk_edit.ts | 25 +++++++++++++------ .../rules/step_schedule_rule/index.tsx | 1 + .../rules/all/bulk_actions/translations.tsx | 2 +- .../action_to_rules_client_operation.ts | 2 -- .../bulk_actions/rule_params_modifier.test.ts | 6 ++--- 9 files changed, 66 insertions(+), 15 deletions(-) diff --git a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts index 8af5f04bf16e6..872affa9989a7 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts @@ -11,7 +11,7 @@ import { left } from 'fp-ts/lib/Either'; import { TimeDuration } from '.'; import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -describe('time_unit', () => { +describe('TimeDuration', () => { test('it should validate a correctly formed TimeDuration with time unit of seconds', () => { const payload = '1s'; const decoded = TimeDuration.decode(payload); @@ -39,6 +39,17 @@ describe('time_unit', () => { expect(message.schema).toEqual(payload); }); + test('it should NOT validate a TimeDuration of 0 length', () => { + const payload = '0s'; + const decoded = TimeDuration.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + test('it should NOT validate a negative TimeDuration', () => { const payload = '-10s'; const decoded = TimeDuration.decode(payload); @@ -50,6 +61,17 @@ describe('time_unit', () => { expect(message.schema).toEqual({}); }); + test('it should NOT validate a decimal TimeDuration', () => { + const payload = '1.5s'; + const decoded = TimeDuration.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1.5s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + test('it should NOT validate a TimeDuration with some other time unit', () => { const payload = '10000000w'; const decoded = TimeDuration.decode(payload); diff --git a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts index c486e1d171689..5549979ac68de 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts @@ -21,8 +21,12 @@ export const TimeDuration = new t.Type( if (typeof input === 'string' && input.trim() !== '') { try { const inputLength = input.length; - const time = parseInt(input.trim().substring(0, inputLength - 1), 10); const unit = input.trim().at(-1); + const time = parseFloat(input.trim().substring(0, inputLength - 1)); + + if (!Number.isInteger(time)) { + return t.failure(input, context); + } if ( time >= 1 && Number.isSafeInteger(time) && diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts index 3cdb5920101d1..745c542bf247c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts @@ -71,6 +71,7 @@ import { setScheduleIntervalTimeUnit, assertRuleScheduleValues, assertUpdateScheduleWarningExists, + assertDefaultValuesAreAppliedToScheduleFields, } from '../../tasks/rules_bulk_edit'; import { hasIndexPatterns, getDetails } from '../../tasks/rule_details'; @@ -493,6 +494,18 @@ describe('Detection rules, bulk edit', () => { }); describe('Schedule', () => { + it('Default values are applied to bulk edit schedule fields', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + clickUpdateScheduleMenuItem(); + + assertUpdateScheduleWarningExists(expectedNumberOfCustomRulesToBeEdited); + + assertDefaultValuesAreAppliedToScheduleFields({ + interval: 5, + lookback: 1, + }); + }); + it('Updates schedule for custom rules', () => { selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); clickUpdateScheduleMenuItem(); diff --git a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts index 341633380a3fa..34e4f9515b27f 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts @@ -56,4 +56,6 @@ export const UPDATE_SCHEDULE_INTERVAL_INPUT = export const UPDATE_SCHEDULE_LOOKBACK_INPUT = '[data-test-subj="bulkEditRulesScheduleLookbackSelector"]'; +export const UPDATE_SCHEDULE_TIME_INTERVAL = '[data-test-subj="interval"]'; + export const UPDATE_SCHEDULE_TIME_UNIT_SELECT = '[data-test-subj="timeType"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts index c3f9336e19fc7..5b3ae403f4a0b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts @@ -193,6 +193,19 @@ export const typeScheduleLookback = (lookback: string) => { .blur(); }; +interface ScheduleFormFields { + interval: number; + lookback: number; +} + +export const assertDefaultValuesAreAppliedToScheduleFields = ({ + interval, + lookback, +}: ScheduleFormFields) => { + cy.get(UPDATE_SCHEDULE_INTERVAL_INPUT).find('input').should('have.value', interval); + cy.get(UPDATE_SCHEDULE_LOOKBACK_INPUT).find('input').should('have.value', lookback); +}; + type TimeUnit = 'Seconds' | 'Minutes' | 'Hours'; export const setScheduleIntervalTimeUnit = (timeUnit: TimeUnit) => { cy.get(UPDATE_SCHEDULE_INTERVAL_INPUT).within(() => { @@ -209,17 +222,15 @@ export const setScheduleLookbackTimeUnit = (timeUnit: TimeUnit) => { export const assertUpdateScheduleWarningExists = (expectedNumberOfNotMLRules: number) => { cy.get(RULES_BULK_EDIT_SCHEDULES_WARNING).should( 'have.text', - `You're about to apply changes to ${expectedNumberOfNotMLRules} selected rules. The changes you made will be overwritten to the existing Rule schedules and additional look-back time (if any).` + `You're about to apply changes to ${expectedNumberOfNotMLRules} selected rules. The changes you make will overwrite the existing rule schedules and additional look-back time (if any).` ); }; - -export const assertRuleScheduleValues = ({ - interval, - lookback, -}: { +interface RuleSchedule { interval: string; lookback: string; -}) => { +} + +export const assertRuleScheduleValues = ({ interval, lookback }: RuleSchedule) => { cy.get(SCHEDULE_DETAILS).within(() => { cy.get('dd').eq(0).should('contain.text', interval); cy.get('dd').eq(1).should('contain.text', lookback); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx index c42e5e4b45976..04ad3a490c89a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx @@ -95,6 +95,7 @@ const StepScheduleRuleComponent: FC = ({ idAria: 'detectionEngineStepScheduleRuleInterval', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepScheduleRuleInterval', + minimumValue: 1, }} /> ( ), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts index c395798cdf618..550f624ed9351 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts @@ -93,8 +93,6 @@ export const bulkEditActionToRulesClientOperation = ( { field: 'schedule', operation: 'set', - // We need to pass a pure Interval object - // i.e. get rid of the meta property value: { interval: action.value.interval, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts index bfbad8ac2ece3..5c194cc6c4614 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts @@ -353,7 +353,7 @@ describe('ruleParamsModifier', () => { }); describe('schedule', () => { - test('should set timeline', () => { + test('should set schedule', () => { const INTERVAL_IN_MINUTES = 5; const LOOKBACK_IN_MINUTES = 1; const FROM_IN_SECONDS = (INTERVAL_IN_MINUTES + LOOKBACK_IN_MINUTES) * 60; @@ -367,8 +367,8 @@ describe('ruleParamsModifier', () => { }, ]); - // @ts-expect-error - expect(editedRuleParams.interval).toBeUndefined(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((editedRuleParams as any).interval).toBeUndefined(); expect(editedRuleParams.meta).toStrictEqual({ from: '1m', }); From bde73c847d5aedfdf08f08c2e050c2faeff7633d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Alvarez=20Pi=C3=B1eiro?= <95703246+emilioalvap@users.noreply.github.com> Date: Tue, 27 Sep 2022 16:12:34 +0200 Subject: [PATCH 072/172] [Synthetics UI] Update synthetics-monitor saved object name normalizer (#139736) * Update synthetic-monitor so to be case-insensitive * Add e2e test checking for monitor sorting Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../journeys/monitor_management.journey.ts | 69 +++++++++++++++++++ .../lib/saved_objects/synthetics_monitor.ts | 1 + 2 files changed, 70 insertions(+) diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts index 2b0aeca66f068..c3b325183b05a 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts @@ -225,3 +225,72 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page; expect(isSuccessful).toBeTruthy(); }); }); + +journey( + 'MonitorManagement-case-insensitive sort', + async ({ page, params }: { page: Page; params: any }) => { + const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); + + const sortedMonitors = [ + Object.assign({}, configuration[DataStream.ICMP].monitorConfig, { + name: `A ${uuid.v4()}`, + }), + Object.assign({}, configuration[DataStream.ICMP].monitorConfig, { + name: `B ${uuid.v4()}`, + }), + Object.assign({}, configuration[DataStream.ICMP].monitorConfig, { + name: `aa ${uuid.v4()}`, + }), + ]; + + before(async () => { + await uptime.waitForLoadingToFinish(); + }); + + after(async () => { + await uptime.navigateToMonitorManagement(); + await uptime.deleteMonitors(); + await uptime.enableMonitorManagement(false); + }); + + step('Go to monitor-management', async () => { + await uptime.navigateToMonitorManagement(); + }); + + step('login to Kibana', async () => { + await uptime.loginToKibana(); + const invalid = await page.locator( + `text=Username or password is incorrect. Please try again.` + ); + expect(await invalid.isVisible()).toBeFalsy(); + }); + + for (const monitorConfig of sortedMonitors) { + step(`create monitor ${monitorConfig.name}`, async () => { + await uptime.enableMonitorManagement(); + await uptime.clickAddMonitor(); + await uptime.createMonitor({ monitorConfig, monitorType: DataStream.ICMP }); + const isSuccessful = await uptime.confirmAndSave(); + expect(isSuccessful).toBeTruthy(); + }); + } + + step(`list monitors in Monitor Management UI`, async () => { + await uptime.navigateToMonitorManagement(); + await Promise.all( + sortedMonitors.map((monitor) => + page.waitForSelector(`text=${monitor.name}`, { timeout: 160 * 1000 }) + ) + ); + + // Get first cell value from monitor table -> monitor name + const rows = page.locator('tbody tr td:first-child div.euiTableCellContent'); + expect(await rows.count()).toEqual(sortedMonitors.length); + + const expectedSort = sortedMonitors + .map((mn) => mn.name) + .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); + expect(await rows.allTextContents()).toEqual(expectedSort); + }); + } +); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts index 61d0694d7cfa6..52be898373ef2 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts @@ -23,6 +23,7 @@ export const syntheticsMonitor: SavedObjectsType = { keyword: { type: 'keyword', ignore_above: 256, + normalizer: 'lowercase', }, }, }, From b854cd8f5156283acbca2aee5d9ec548553165bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 23:54:44 +0930 Subject: [PATCH 073/172] Update dependency @types/node-forge to ^1.3.0 (main) (#141553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 02511a00b56c3..6c0913394e246 100644 --- a/package.json +++ b/package.json @@ -1177,7 +1177,7 @@ "@types/nock": "^10.0.3", "@types/node": "16.11.41", "@types/node-fetch": "^2.6.0", - "@types/node-forge": "^1.0.4", + "@types/node-forge": "^1.3.0", "@types/nodemailer": "^6.4.0", "@types/normalize-path": "^3.0.0", "@types/object-hash": "^1.3.0", diff --git a/yarn.lock b/yarn.lock index 0752e8a1611c4..f809bc0cac463 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8158,10 +8158,10 @@ "@types/node" "*" form-data "^2.3.3" -"@types/node-forge@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.0.4.tgz#82df44938848e111463080286876e4cbe2b297a0" - integrity sha512-UpX8LTRrarEZPQvQqF5/6KQAqZolOVckH7txWdlsWIJrhBFFtwEUTcqeDouhrJl6t0F7Wg5cyUOAqqF8a6hheg== +"@types/node-forge@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.0.tgz#c655e951e0fb5c4b53c9f4746c2128d4f93002fd" + integrity sha512-yUsIEHG3d81E2c+akGjZAMdVcjbfqMzpMjvpebnTO7pEGfqxCtBzpRV52kR1RETtwJ9fHkLdEjtaM+uMKWpwFA== dependencies: "@types/node" "*" From d5dd24124d40e898600ff839ce2f8a0650b53998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 27 Sep 2022 16:25:24 +0200 Subject: [PATCH 074/172] [Security Solution][Endpoint] Removes endpoint package from the excluded_packages list (#141783) * Removes endpoint package from the excluded_packages list * Fixes unit test --- .../screens/detail/utils/get_install_route_options.test.ts | 4 ++-- .../epm/screens/detail/utils/get_install_route_options.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts index 8d556434f8828..e085b9034235b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts @@ -131,7 +131,7 @@ describe('getInstallPkgRouteOptions', () => { expect(getInstallPkgRouteOptions(opts)).toEqual(['fleet', expectedOptions]); }); - it('should not navigate to steps app for endpoint', () => { + it('should navigate to steps app for endpoint', () => { const opts = { currentPath: 'currentPath', integration: 'myintegration', @@ -144,7 +144,7 @@ describe('getInstallPkgRouteOptions', () => { const expectedRedirectURl = '/detail/endpoint-1.0.0/policies?integration=myintegration'; const expectedOptions = { - path: '/integrations/endpoint-1.0.0/add-integration/myintegration', + path: '/integrations/endpoint-1.0.0/add-integration/myintegration?useMultiPageLayout', state: { onCancelUrl: 'currentPath', onCancelNavigateTo: [ diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts index c4e6d7ffc4a84..6a8612a44f42f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts @@ -13,7 +13,6 @@ const EXCLUDED_PACKAGES = [ 'apm', 'cloud_security_posture', 'dga', - 'endpoint', 'fleet_server', 'kubernetes', 'osquery_manager', From 712f8801d6396448098248cf97dc671b12d7b174 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Tue, 27 Sep 2022 10:37:34 -0400 Subject: [PATCH 075/172] [8.5] Change default name of destination field in ML Inference pipeline (#141819) * Use pipeline name as default destination field --- .../routes/enterprise_search/indices.ts | 2 +- .../create_ml_inference_pipeline.test.ts | 53 +++++++++++++------ .../utils/create_ml_inference_pipeline.ts | 9 ++-- .../utils/ml_inference_pipeline_utils.ts | 2 +- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index fd4452ccb46af..db46da11f5f57 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -360,7 +360,7 @@ export function registerIndexRoutes({ pipelineName, modelId, sourceField, - destinationField || modelId, + destinationField, client.asCurrentUser ); } catch (error) { diff --git a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts index c34bbae2f97f1..d3aa24560594d 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts @@ -16,6 +16,16 @@ import { getPrefixedInferencePipelineProcessorName, } from './ml_inference_pipeline_utils'; +const mockClient = { + ingest: { + getPipeline: jest.fn(), + putPipeline: jest.fn(), + }, + ml: { + getTrainedModels: jest.fn(), + }, +}; + describe('createMlInferencePipeline util function', () => { const pipelineName = 'my-pipeline'; const modelId = 'my-model-id'; @@ -23,16 +33,6 @@ describe('createMlInferencePipeline util function', () => { const destinationField = 'my-dest-field'; const inferencePipelineGeneratedName = getPrefixedInferencePipelineProcessorName(pipelineName); - const mockClient = { - ingest: { - getPipeline: jest.fn(), - putPipeline: jest.fn(), - }, - ml: { - getTrainedModels: jest.fn(), - }, - }; - mockClient.ml.getTrainedModels.mockImplementation(() => Promise.resolve({ trained_model_configs: [ @@ -86,6 +86,32 @@ describe('createMlInferencePipeline util function', () => { ); }); + it('should default the destination field to the pipeline name', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.reject({ statusCode: 404 })); // Pipeline does not exist + mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); + + await createMlInferencePipeline( + pipelineName, + modelId, + sourceField, + undefined, // Omitted destination field + mockClient as unknown as ElasticsearchClient + ); + + // Verify the object passed to pipeline creation contains the default target field name + expect(mockClient.ingest.putPipeline).toHaveBeenCalledWith( + expect.objectContaining({ + processors: expect.arrayContaining([ + expect.objectContaining({ + inference: expect.objectContaining({ + target_field: `ml.inference.${pipelineName}`, + }), + }), + ]), + }) + ); + }); + it('should throw an error without creating the pipeline if it already exists', () => { mockClient.ingest.getPipeline.mockImplementation(() => Promise.resolve({ @@ -111,13 +137,6 @@ describe('addSubPipelineToIndexSpecificMlPipeline util function', () => { const parentPipelineId = getInferencePipelineNameFromIndexName(indexName); const pipelineName = 'ml-inference-my-pipeline'; - const mockClient = { - ingest: { - getPipeline: jest.fn(), - putPipeline: jest.fn(), - }, - }; - beforeEach(() => { jest.clearAllMocks(); }); diff --git a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts index ebe69f98118d1..dfcd8d4884972 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts @@ -15,6 +15,7 @@ import { formatMlPipelineBody } from '../lib/pipelines/create_pipeline_definitio import { getInferencePipelineNameFromIndexName, getPrefixedInferencePipelineProcessorName, + formatPipelineName, } from './ml_inference_pipeline_utils'; /** @@ -41,14 +42,14 @@ export const createAndReferenceMlInferencePipeline = async ( pipelineName: string, modelId: string, sourceField: string, - destinationField: string, + destinationField: string | null | undefined, esClient: ElasticsearchClient ): Promise => { const createPipelineResult = await createMlInferencePipeline( pipelineName, modelId, sourceField, - destinationField || modelId, + destinationField, esClient ); @@ -76,7 +77,7 @@ export const createMlInferencePipeline = async ( pipelineName: string, modelId: string, sourceField: string, - destinationField: string, + destinationField: string | null | undefined, esClient: ElasticsearchClient ): Promise => { const inferencePipelineGeneratedName = getPrefixedInferencePipelineProcessorName(pipelineName); @@ -99,7 +100,7 @@ export const createMlInferencePipeline = async ( inferencePipelineGeneratedName, modelId, sourceField, - destinationField, + destinationField || formatPipelineName(pipelineName), esClient ); diff --git a/x-pack/plugins/enterprise_search/server/utils/ml_inference_pipeline_utils.ts b/x-pack/plugins/enterprise_search/server/utils/ml_inference_pipeline_utils.ts index e059f5b9090c0..6547034f25403 100644 --- a/x-pack/plugins/enterprise_search/server/utils/ml_inference_pipeline_utils.ts +++ b/x-pack/plugins/enterprise_search/server/utils/ml_inference_pipeline_utils.ts @@ -13,7 +13,7 @@ export const getPrefixedInferencePipelineProcessorName = (pipelineName: string) ? formatPipelineName(pipelineName) : `ml-inference-${formatPipelineName(pipelineName)}`; -const formatPipelineName = (rawName: string) => +export const formatPipelineName = (rawName: string) => rawName .trim() .replace(/\s+/g, '_') // Convert whitespaces to underscores From d41e5ab12724f5dacbc91c416a6a0422e4b39b79 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 27 Sep 2022 10:44:04 -0400 Subject: [PATCH 076/172] [Response Ops] [Connectors] Move connectors type UI components to `stack_connectors` plugin (#140444) * Initial commit of stack connectors plugin * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Fixing up xmatters. Moving library functions around * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * Fixing up webhook * Fixing up teams and slack * Fixing up index, email, pagerduty, server log * Fixing i18n * wip * Moving well know email route to stack connectors plugin * Fixing types * Fixing unit tests * Adding index.ts * Cleanup * Updating READMEs * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * Moving to domain specific folders and updating codeowners * Fixing codeowners * Fixing types * Initial commit moving client code to stack connectors plugin * Updated all imports * Updating email * Updating index * Updating stack connectors * Updating cases connectors * Cleanup * Splitting test_utils * Fixing all imports * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Fixing types and limits * Fixing unit tests * Fixing CI * Updating codeowners Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .eslintrc.js | 4 +- .github/CODEOWNERS | 2 + packages/kbn-optimizer/limits.yml | 1 + .../plugins/stack_connectors/jest.config.js | 2 +- x-pack/plugins/stack_connectors/kibana.json | 4 +- .../cases}/cases_webhook/action_variables.ts | 0 .../cases/cases_webhook/index.ts | 8 + .../cases}/cases_webhook/steps/auth.tsx | 2 +- .../cases}/cases_webhook/steps/create.tsx | 2 +- .../cases}/cases_webhook/steps/get.tsx | 2 +- .../cases}/cases_webhook/steps/index.ts | 0 .../cases_webhook/steps/update.styles.ts | 0 .../cases}/cases_webhook/steps/update.tsx | 3 +- .../cases/cases_webhook/translations.ts | 477 +++++++++++ .../cases}/cases_webhook/types.ts | 4 +- .../cases}/cases_webhook/validator.ts | 2 +- .../cases/cases_webhook/webhook.test.tsx | 54 ++ .../cases}/cases_webhook/webhook.tsx | 11 +- .../cases_webhook/webhook_connectors.test.tsx | 10 +- .../cases_webhook/webhook_connectors.tsx | 4 +- .../cases_webhook/webhook_params.test.tsx | 4 +- .../cases}/cases_webhook/webhook_params.tsx | 34 +- .../public/connector_types/cases/index.ts | 17 + .../connector_types/cases}/jira/api.test.ts | 0 .../public/connector_types/cases}/jira/api.ts | 8 +- .../connector_types/cases/jira}/index.ts | 2 +- .../connector_types/cases}/jira/jira.test.tsx | 32 +- .../connector_types/cases}/jira/jira.tsx | 18 +- .../cases}/jira/jira_connectors.test.tsx | 4 +- .../cases}/jira/jira_connectors.tsx | 12 +- .../cases}/jira/jira_params.test.tsx | 4 +- .../cases}/jira/jira_params.tsx | 39 +- .../connector_types/cases}/jira/logo.tsx | 2 +- .../cases}/jira/search_issues.tsx | 2 +- .../cases}/jira/translations.ts | 35 +- .../connector_types/cases}/jira/types.ts | 4 +- .../jira/use_get_fields_by_issue_type.tsx | 2 +- .../cases}/jira/use_get_issue_types.tsx | 2 +- .../cases}/jira/use_get_issues.tsx | 2 +- .../cases}/jira/use_get_single_issue.tsx | 2 +- .../cases}/resilient/api.test.ts | 0 .../connector_types/cases}/resilient/api.ts | 7 +- .../connector_types/cases/resilient/index.ts | 8 + .../connector_types/cases}/resilient/logo.tsx | 2 +- .../cases/resilient/resilient.test.tsx | 53 ++ .../cases}/resilient/resilient.tsx | 18 +- .../resilient/resilient_connectors.test.tsx | 4 +- .../cases}/resilient/resilient_connectors.tsx | 11 +- .../resilient/resilient_params.test.tsx | 2 +- .../cases}/resilient/resilient_params.tsx | 31 +- .../cases}/resilient/translations.ts | 21 +- .../connector_types/cases}/resilient/types.ts | 4 +- .../resilient/use_get_incident_types.tsx | 2 +- .../cases}/resilient/use_get_severity.tsx | 2 +- .../cases}/servicenow/api.test.ts | 0 .../connector_types/cases}/servicenow/api.ts | 9 +- .../application_required_callout.test.tsx | 0 .../application_required_callout.tsx | 6 +- .../auth_types/credentials_auth.tsx | 2 +- .../cases}/servicenow/auth_types/index.ts | 0 .../cases}/servicenow/auth_types/oauth.tsx | 2 +- .../cases}/servicenow/credentials.test.tsx | 4 +- .../cases}/servicenow/credentials.tsx | 0 .../cases}/servicenow/credentials_api_url.tsx | 4 +- .../servicenow/deprecated_callout.test.tsx | 0 .../cases}/servicenow/deprecated_callout.tsx | 8 +- .../cases}/servicenow/helpers.test.ts | 2 +- .../cases}/servicenow/helpers.ts | 7 +- .../cases}/servicenow/index.ts | 6 +- .../servicenow/installation_callout.test.tsx | 0 .../servicenow/installation_callout.tsx | 0 .../cases}/servicenow/logo.tsx | 2 +- .../cases/servicenow/servicenow.test.tsx | 80 ++ .../cases}/servicenow/servicenow.tsx | 23 +- .../servicenow/servicenow_connectors.test.tsx | 12 +- .../servicenow/servicenow_connectors.tsx | 15 +- .../servicenow_connectors_no_app.test.tsx | 6 +- .../servicenow_connectors_no_app.tsx | 5 +- .../servicenow_itom_params.test.tsx | 4 +- .../servicenow/servicenow_itom_params.tsx | 10 +- .../servicenow_itsm_params.test.tsx | 4 +- .../servicenow/servicenow_itsm_params.tsx | 13 +- .../servicenow/servicenow_selection_row.tsx | 6 +- .../servicenow/servicenow_sir_params.test.tsx | 4 +- .../servicenow/servicenow_sir_params.tsx | 13 +- .../servicenow/sn_store_button.test.tsx | 0 .../cases}/servicenow/sn_store_button.tsx | 0 .../cases}/servicenow/translations.ts | 145 ++-- .../cases}/servicenow/types.ts | 4 +- .../servicenow/update_connector.test.tsx | 2 +- .../cases}/servicenow/update_connector.tsx | 25 +- .../cases}/servicenow/use_choices.test.tsx | 6 +- .../cases}/servicenow/use_choices.tsx | 2 +- .../servicenow/use_get_app_info.test.tsx | 2 +- .../cases}/servicenow/use_get_app_info.tsx | 0 .../servicenow/use_get_choices.test.tsx | 6 +- .../cases}/servicenow/use_get_choices.tsx | 2 +- .../cases}/swimlane/api.test.ts | 0 .../connector_types/cases}/swimlane/api.ts | 0 .../cases}/swimlane/helpers.ts | 0 .../connector_types/cases/swimlane/index.ts | 8 + .../connector_types/cases}/swimlane/logo.tsx | 2 +- .../connector_types/cases}/swimlane/mocks.ts | 0 .../cases}/swimlane/steps/index.ts | 0 .../swimlane/steps/swimlane_connection.tsx | 5 +- .../cases}/swimlane/steps/swimlane_fields.tsx | 2 +- .../cases}/swimlane/swimlane.test.tsx | 32 +- .../cases}/swimlane/swimlane.tsx | 11 +- .../swimlane/swimlane_connectors.test.tsx | 4 +- .../cases}/swimlane/swimlane_connectors.tsx | 4 +- .../cases}/swimlane/swimlane_params.test.tsx | 0 .../cases}/swimlane/swimlane_params.tsx | 8 +- .../cases}/swimlane/translations.ts | 81 +- .../connector_types/cases}/swimlane/types.ts | 4 +- .../swimlane/use_get_application.test.tsx | 4 +- .../cases}/swimlane/use_get_application.tsx | 0 .../connector_types/cases/xmatters/index.ts | 8 + .../connector_types/cases}/xmatters/logo.tsx | 2 +- .../cases}/xmatters/translations.ts | 27 +- .../cases/xmatters/xmatters.test.tsx | 48 ++ .../cases}/xmatters/xmatters.tsx | 22 +- .../xmatters/xmatters_connectors.test.tsx | 2 +- .../cases}/xmatters/xmatters_connectors.tsx | 18 +- .../cases}/xmatters/xmatters_params.test.tsx | 2 +- .../cases}/xmatters/xmatters_params.tsx | 41 +- .../public/connector_types/index.ts | 60 ++ .../lib}/extract_action_variable.ts | 0 .../lib}/rewrite_response_body.ts | 0 .../connector_types/lib}/test_utils.tsx | 23 +- .../public/connector_types/security}/index.ts | 2 - .../connector_types/stack}/email/api.ts | 4 +- .../stack}/email/email.test.tsx | 30 +- .../connector_types/stack}/email/email.tsx | 85 +- .../stack}/email/email_connector.test.tsx | 6 +- .../stack}/email/email_connector.tsx | 16 +- .../stack}/email/email_params.test.tsx | 0 .../stack}/email/email_params.tsx | 38 +- .../stack}/email/exchange_form.test.tsx | 4 +- .../stack}/email/exchange_form.tsx | 9 +- .../connector_types/stack/email}/index.ts | 2 +- .../stack}/email/translations.ts | 57 +- .../stack}/email/use_email_config.test.ts | 0 .../stack}/email/use_email_config.ts | 6 +- .../stack}/es_index/es_index.test.tsx | 40 +- .../stack}/es_index/es_index.tsx | 39 +- .../es_index/es_index_connector.test.tsx | 18 +- .../stack}/es_index/es_index_connector.tsx | 28 +- .../stack}/es_index/es_index_params.test.tsx | 8 +- .../stack}/es_index/es_index_params.tsx | 38 +- .../connector_types/stack/es_index}/index.ts | 2 +- .../stack}/es_index/translations.ts | 14 +- .../public/connector_types/stack/index.ts | 14 + .../connector_types/stack/pagerduty/index.ts | 8 + .../connector_types/stack}/pagerduty/logo.tsx | 2 +- .../stack}/pagerduty/pagerduty.test.tsx | 32 +- .../stack}/pagerduty/pagerduty.tsx | 45 +- .../pagerduty/pagerduty_connectors.test.tsx | 4 +- .../stack}/pagerduty/pagerduty_connectors.tsx | 6 +- .../pagerduty/pagerduty_params.test.tsx | 2 +- .../stack}/pagerduty/pagerduty_params.tsx | 54 +- .../stack}/pagerduty/translations.ts | 12 +- .../connector_types/stack/server_log/index.ts | 8 + .../stack/server_log/server_log.test.tsx | 55 ++ .../stack}/server_log/server_log.tsx | 22 +- .../server_log/server_log_params.test.tsx | 2 +- .../stack}/server_log/server_log_params.tsx | 24 +- .../connector_types/stack/slack}/index.ts | 2 +- .../stack/slack/slack.test.tsx | 54 ++ .../connector_types/stack}/slack/slack.tsx | 27 +- .../stack}/slack/slack_connectors.test.tsx | 4 +- .../stack}/slack/slack_connectors.tsx | 7 +- .../stack}/slack/slack_params.test.tsx | 0 .../stack}/slack/slack_params.tsx | 15 +- .../stack}/slack/translations.ts | 6 +- .../connector_types/stack/teams}/index.ts | 2 +- .../connector_types/stack}/teams/logo.tsx | 2 +- .../stack/teams/teams.test.tsx | 53 ++ .../connector_types/stack}/teams/teams.tsx | 27 +- .../stack}/teams/teams_connectors.test.tsx | 4 +- .../stack}/teams/teams_connectors.tsx | 6 +- .../stack}/teams/teams_params.test.tsx | 2 +- .../stack}/teams/teams_params.tsx | 15 +- .../stack}/teams/translations.ts | 6 +- .../connector_types/stack/webhook/index.ts | 8 + .../stack}/webhook/translations.ts | 28 +- .../stack/webhook/webhook.test.tsx | 54 ++ .../stack}/webhook/webhook.tsx | 27 +- .../webhook/webhook_connectors.test.tsx | 2 +- .../stack}/webhook/webhook_connectors.tsx | 6 +- .../stack}/webhook/webhook_params.test.tsx | 4 +- .../stack}/webhook/webhook_params.tsx | 17 +- .../public/connector_types}/types.ts | 2 +- .../plugins/stack_connectors/public/index.ts | 11 + .../plugins/stack_connectors/public/mocks.ts | 15 + .../plugins/stack_connectors/public/plugin.ts | 35 + x-pack/plugins/stack_connectors/tsconfig.json | 6 +- .../translations/translations/fr-FR.json | 755 +++++++++-------- .../translations/translations/ja-JP.json | 759 +++++++++--------- .../translations/translations/zh-CN.json | 758 +++++++++-------- x-pack/plugins/triggers_actions_ui/README.md | 4 +- .../cases_webhook/translations.ts | 510 ------------ .../cases_webhook/webhook.test.tsx | 54 -- .../components/builtin_action_types/index.ts | 58 -- .../builtin_action_types/pagerduty/index.ts | 8 - .../builtin_action_types/resilient/index.ts | 8 - .../resilient/resilient.test.tsx | 53 -- .../builtin_action_types/server_log/index.ts | 8 - .../server_log/server_log.test.tsx | 55 -- .../servicenow/servicenow.test.tsx | 80 -- .../builtin_action_types/slack/slack.test.tsx | 54 -- .../builtin_action_types/swimlane/index.ts | 8 - .../builtin_action_types/teams/index.ts | 8 - .../builtin_action_types/teams/teams.test.tsx | 53 -- .../webhook/webhook.test.tsx | 54 -- .../builtin_action_types/xmatters/index.ts | 8 - .../xmatters/xmatters.test.tsx | 48 -- .../public/application/components/index.ts | 17 + .../components/simple_connector_form.test.tsx | 2 +- .../application/components/test_utils.tsx | 86 ++ .../public/application/constants/index.ts | 1 - .../public/application/lib/index.ts | 3 + .../connector_form.test.tsx | 5 +- .../connector_form_fields.test.tsx | 2 +- .../connector_form_fields_global.test.tsx | 2 +- .../create_connector_flyout/index.test.tsx | 5 +- .../edit_connector_flyout/index.test.tsx | 5 +- .../encrypted_fields_callout.test.tsx | 2 +- .../sections/action_connector_form/index.ts | 2 + .../public/common/index.ts | 3 +- .../public/common/lib/index.ts | 1 + .../triggers_actions_ui/public/index.ts | 42 + .../triggers_actions_ui/public/mocks.ts | 8 - .../triggers_actions_ui/public/plugin.ts | 8 - .../plugins/triggers_actions_ui/tsconfig.json | 1 - 234 files changed, 3322 insertions(+), 3240 deletions(-) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/action_variables.ts (100%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/auth.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/create.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/get.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/index.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/update.styles.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/steps/update.tsx (97%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/translations.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/types.ts (82%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/validator.ts (98%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/webhook.tsx (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/webhook_connectors.test.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/webhook_connectors.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/webhook_params.test.tsx (93%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/cases_webhook/webhook_params.tsx (84%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/api.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/api.ts (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/webhook => stack_connectors/public/connector_types/cases/jira}/index.ts (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira.test.tsx (57%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira.tsx (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira_connectors.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira_connectors.tsx (86%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira_params.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/jira_params.tsx (90%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/logo.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/search_issues.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/translations.ts (55%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/types.ts (81%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/use_get_fields_by_issue_type.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/use_get_issue_types.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/use_get_issues.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/jira/use_get_single_issue.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/api.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/api.ts (88%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/logo.tsx (98%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/resilient.tsx (77%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/resilient_connectors.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/resilient_connectors.tsx (86%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/resilient_params.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/resilient_params.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/translations.ts (55%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/types.ts (75%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/use_get_incident_types.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/resilient/use_get_severity.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/api.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/api.ts (92%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/application_required_callout.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/application_required_callout.tsx (81%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/auth_types/credentials_auth.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/auth_types/index.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/auth_types/oauth.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/credentials.test.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/credentials.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/credentials_api_url.tsx (92%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/deprecated_callout.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/deprecated_callout.tsx (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/helpers.test.ts (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/helpers.ts (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/index.ts (73%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/installation_callout.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/installation_callout.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/logo.tsx (97%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow.tsx (84%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_connectors.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_connectors.tsx (92%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_connectors_no_app.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_connectors_no_app.tsx (85%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_itom_params.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_itom_params.tsx (94%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_itsm_params.test.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_itsm_params.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_selection_row.tsx (81%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_sir_params.test.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/servicenow_sir_params.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/sn_store_button.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/sn_store_button.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/translations.ts (50%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/types.ts (92%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/update_connector.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/update_connector.tsx (86%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_choices.test.tsx (94%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_choices.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_get_app_info.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_get_app_info.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_get_choices.test.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/servicenow/use_get_choices.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/api.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/api.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/helpers.ts (100%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/logo.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/mocks.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/steps/index.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/steps/swimlane_connection.tsx (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/steps/swimlane_fields.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane.test.tsx (55%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane.tsx (85%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane_connectors.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane_connectors.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane_params.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/swimlane_params.tsx (95%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/translations.ts (53%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/types.ts (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/use_get_application.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/swimlane/use_get_application.tsx (100%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/logo.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/translations.ts (54%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/xmatters.tsx (75%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/xmatters_connectors.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/xmatters_connectors.tsx (89%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/xmatters_params.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/cases}/xmatters/xmatters_params.tsx (68%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/lib}/extract_action_variable.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/lib}/rewrite_response_body.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/lib}/test_utils.tsx (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/jira => stack_connectors/public/connector_types/security}/index.ts (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/api.ts (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email.test.tsx (74%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email.tsx (67%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email_connector.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email_connector.tsx (94%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email_params.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/email_params.tsx (85%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/exchange_form.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/exchange_form.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/slack => stack_connectors/public/connector_types/stack/email}/index.ts (78%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/translations.ts (54%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/use_email_config.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/email/use_email_config.ts (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index.test.tsx (61%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index.tsx (59%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index_connector.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index_connector.tsx (87%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index_params.test.tsx (94%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/es_index_params.tsx (84%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook => stack_connectors/public/connector_types/stack/es_index}/index.ts (77%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/es_index/translations.ts (64%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/index.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/logo.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty.test.tsx (57%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty.tsx (71%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty_connectors.test.tsx (97%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty_connectors.tsx (91%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty_params.test.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/pagerduty_params.tsx (76%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/pagerduty/translations.ts (62%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/index.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/server_log/server_log.tsx (63%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/server_log/server_log_params.test.tsx (98%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/server_log/server_log_params.tsx (79%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/email => stack_connectors/public/connector_types/stack/slack}/index.ts (78%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/slack.tsx (59%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/slack_connectors.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/slack_connectors.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/slack_params.test.tsx (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/slack_params.tsx (79%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/slack/translations.ts (67%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types/es_index => stack_connectors/public/connector_types/stack/teams}/index.ts (78%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/logo.tsx (99%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/teams.tsx (59%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/teams_connectors.test.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/teams_connectors.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/teams_params.test.tsx (93%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/teams_params.tsx (74%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/teams/translations.ts (67%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/index.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/translations.ts (55%) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.test.tsx rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/webhook.tsx (67%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/webhook_connectors.test.tsx (99%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/webhook_connectors.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/webhook_params.test.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types/stack}/webhook/webhook_params.tsx (69%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_action_types => stack_connectors/public/connector_types}/types.ts (97%) create mode 100644 x-pack/plugins/stack_connectors/public/index.ts create mode 100644 x-pack/plugins/stack_connectors/public/mocks.ts create mode 100644 x-pack/plugins/stack_connectors/public/plugin.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/translations.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/index.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/test_utils.tsx diff --git a/.eslintrc.js b/.eslintrc.js index df107348cfafc..902643dbe5066 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1314,7 +1314,7 @@ module.exports = { { // typescript for front and back end files: [ - 'x-pack/plugins/{alerting,stack_alerts,stack_connectors,actions,task_manager,event_log}/**/*.{ts,tsx}', + 'x-pack/plugins/{alerting,stack_alerts,actions,task_manager,event_log}/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-explicit-any': 'error', @@ -1322,7 +1322,7 @@ module.exports = { }, { // typescript only for back end - files: ['x-pack/plugins/triggers_actions_ui/server/**/*.ts'], + files: ['x-pack/plugins/{stack_connectors,triggers_actions_ui}/server/**/*.ts'], rules: { '@typescript-eslint/no-explicit-any': 'error', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 63c5c8047c6b4..d0c6e476d6d44 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -331,7 +331,9 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/event_log/ @elastic/response-ops /x-pack/plugins/task_manager/ @elastic/response-ops /x-pack/plugins/stack_connectors/ @elastic/response-ops +/x-pack/plugins/stack_connectors/public/connector_types/stack/ @elastic/response-ops-execution /x-pack/plugins/stack_connectors/server/connector_types/stack/ @elastic/response-ops-execution +/x-pack/plugins/stack_connectors/public/connector_types/cases/ @elastic/response-ops-cases /x-pack/plugins/stack_connectors/server/connector_types/cases/ @elastic/response-ops-cases /x-pack/test/alerting_api_integration/ @elastic/response-ops /x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/response-ops diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c8daaf258deb6..440ff3ce2b12c 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -108,6 +108,7 @@ pageLoadAssetSize: snapshotRestore: 79032 spaces: 57868 stackAlerts: 29684 + stackConnectors: 36314 synthetics: 40958 telemetry: 51957 telemetryManagementSection: 38586 diff --git a/x-pack/plugins/stack_connectors/jest.config.js b/x-pack/plugins/stack_connectors/jest.config.js index 9a343089b9475..6bfda7da5ca5e 100644 --- a/x-pack/plugins/stack_connectors/jest.config.js +++ b/x-pack/plugins/stack_connectors/jest.config.js @@ -12,6 +12,6 @@ module.exports = { coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/stack_connectors', coverageReporters: ['text', 'html'], collectCoverageFrom: [ - '/x-pack/plugins/stack_connectors/{common,server}/**/*.{js,ts,tsx}', + '/x-pack/plugins/stack_connectors/{common,public,server}/**/*.{js,ts,tsx}', ], }; diff --git a/x-pack/plugins/stack_connectors/kibana.json b/x-pack/plugins/stack_connectors/kibana.json index a2fc97ad9547e..fc55b723e5c55 100644 --- a/x-pack/plugins/stack_connectors/kibana.json +++ b/x-pack/plugins/stack_connectors/kibana.json @@ -8,6 +8,6 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "stack_connectors"], - "requiredPlugins": ["actions"], - "ui": false + "requiredPlugins": ["actions", "esUiShared", "triggersActionsUi"], + "ui": true } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/action_variables.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/action_variables.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/action_variables.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/action_variables.ts diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/index.ts new file mode 100644 index 0000000000000..88107a4f6d517 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/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 { getConnectorType as getCasesWebhookConnectorType } from './webhook'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/auth.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/auth.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/auth.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/auth.tsx index b356fb716d06f..85e0f5e9e31b5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/auth.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/auth.tsx @@ -23,7 +23,7 @@ import { } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field, TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import { PasswordField } from '../../../password_field'; +import { PasswordField } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from '../translations'; const { emptyField } = fieldValidators; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/create.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/create.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/create.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/create.tsx index c754a89ed89fe..ffa72576feb69 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/create.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/create.tsx @@ -10,8 +10,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { FIELD_TYPES, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import { JsonFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; import { containsTitleAndDesc } from '../validator'; -import { JsonFieldWrapper } from '../../../json_field_wrapper'; import { casesVars } from '../action_variables'; import { HTTP_VERBS } from '../webhook_connectors'; import * as i18n from '../translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/get.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/get.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/get.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/get.tsx index 3f0cf52dcfcb2..e8f233408a4c9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/get.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/get.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import { MustacheTextFieldWrapper } from '../../../mustache_text_field_wrapper'; +import { MustacheTextFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; import { containsExternalId, containsExternalIdOrTitle } from '../validator'; import { urlVars, urlVarsExt } from '../action_variables'; import * as i18n from '../translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/index.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/index.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/update.styles.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/update.styles.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/update.styles.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/update.styles.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/update.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/update.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/update.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/update.tsx index e8305e7439778..a140ecbc6a221 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/steps/update.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/steps/update.tsx @@ -10,10 +10,9 @@ import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { FIELD_TYPES, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { JsonFieldWrapper, MustacheTextFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; import { containsCommentsOrEmpty, containsTitleAndDesc, isUrlButCanBeEmpty } from '../validator'; -import { MustacheTextFieldWrapper } from '../../../mustache_text_field_wrapper'; import { casesVars, commentVars, urlVars } from '../action_variables'; -import { JsonFieldWrapper } from '../../../json_field_wrapper'; import { HTTP_VERBS } from '../webhook_connectors'; import { styles } from './update.styles'; import * as i18n from '../translations'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/translations.ts new file mode 100644 index 0000000000000..1ef221566352d --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/translations.ts @@ -0,0 +1,477 @@ +/* + * 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'; + +export const CREATE_URL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateUrlText', + { + defaultMessage: 'Create case URL is required.', + } +); +export const CREATE_INCIDENT_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentText', + { + defaultMessage: 'Create case object is required and must be valid JSON.', + } +); + +export const CREATE_METHOD_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateMethodText', + { + defaultMessage: 'Create case method is required.', + } +); + +export const CREATE_RESPONSE_KEY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentResponseKeyText', + { + defaultMessage: 'Create case response case id key is required.', + } +); + +export const UPDATE_URL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredUpdateUrlText', + { + defaultMessage: 'Update case URL is required.', + } +); +export const UPDATE_INCIDENT_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredUpdateIncidentText', + { + defaultMessage: 'Update case object is required and must be valid JSON.', + } +); + +export const UPDATE_METHOD_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredUpdateMethodText', + { + defaultMessage: 'Update case method is required.', + } +); + +export const CREATE_COMMENT_URL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText', + { + defaultMessage: 'Create comment URL must be URL format.', + } +); +export const CREATE_COMMENT_MESSAGE = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText', + { + defaultMessage: 'Create comment object must be valid JSON.', + } +); + +export const CREATE_COMMENT_METHOD_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText', + { + defaultMessage: 'Create comment method is required.', + } +); + +export const GET_INCIDENT_URL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentUrlText', + { + defaultMessage: 'Get case URL is required.', + } +); +export const GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseExternalTitleKeyText', + { + defaultMessage: 'Get case response external case title key is re quired.', + } +); +export const GET_RESPONSE_EXTERNAL_CREATED_KEY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseCreatedKeyText', + { + defaultMessage: 'Get case response created date key is required.', + } +); +export const GET_RESPONSE_EXTERNAL_UPDATED_KEY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseUpdatedKeyText', + { + defaultMessage: 'Get case response updated date key is required.', + } +); +export const GET_INCIDENT_VIEW_URL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentViewUrlKeyText', + { + defaultMessage: 'View case URL is required.', + } +); + +export const MISSING_VARIABLES = (variables: string[]) => + i18n.translate('xpack.stackConnectors.components.casesWebhook.error.missingVariables', { + defaultMessage: + 'Missing required {variableCount, plural, one {variable} other {variables}}: {variables}', + values: { variableCount: variables.length, variables: variables.join(', ') }, + }); + +export const USERNAME_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText', + { + defaultMessage: 'Username is required.', + } +); + +export const SUMMARY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText', + { + defaultMessage: 'Title is required.', + } +); + +export const KEY_LABEL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel', + { + defaultMessage: 'Key', + } +); + +export const VALUE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel', + { + defaultMessage: 'Value', + } +); + +export const ADD_BUTTON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.addHeaderButton', + { + defaultMessage: 'Add', + } +); + +export const DELETE_BUTTON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.deleteHeaderButton', + { + defaultMessage: 'Delete', + description: 'Delete HTTP header', + } +); + +export const CREATE_INCIDENT_METHOD = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel', + { + defaultMessage: 'Create Case Method', + } +); + +export const CREATE_INCIDENT_URL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel', + { + defaultMessage: 'Create Case URL', + } +); + +export const CREATE_INCIDENT_JSON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentJsonTextFieldLabel', + { + defaultMessage: 'Create Case Object', + } +); + +export const CREATE_INCIDENT_JSON_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentJsonHelpText', + { + defaultMessage: + 'JSON object to create case. Use the variable selector to add Cases data to the payload.', + } +); + +export const JSON = i18n.translate('xpack.stackConnectors.components.casesWebhook.jsonFieldLabel', { + defaultMessage: 'JSON', +}); +export const CODE_EDITOR = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel', + { + defaultMessage: 'Code editor', + } +); + +export const CREATE_INCIDENT_RESPONSE_KEY = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel', + { + defaultMessage: 'Create Case Response Case Key', + } +); + +export const CREATE_INCIDENT_RESPONSE_KEY_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyHelpText', + { + defaultMessage: 'JSON key in create case response that contains the external case id', + } +); + +export const ADD_CASES_VARIABLE = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.addVariable', + { + defaultMessage: 'Add variable', + } +); + +export const GET_INCIDENT_URL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel', + { + defaultMessage: 'Get Case URL', + } +); +export const GET_INCIDENT_URL_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentUrlHelp', + { + defaultMessage: + 'API URL to GET case details JSON from external system. Use the variable selector to add external system id to the url.', + } +); + +export const GET_INCIDENT_TITLE_KEY = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyTextFieldLabel', + { + defaultMessage: 'Get Case Response External Title Key', + } +); +export const GET_INCIDENT_TITLE_KEY_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyHelp', + { + defaultMessage: 'JSON key in get case response that contains the external case title', + } +); + +export const EXTERNAL_INCIDENT_VIEW_URL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel', + { + defaultMessage: 'External Case View URL', + } +); +export const EXTERNAL_INCIDENT_VIEW_URL_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp', + { + defaultMessage: + 'URL to view case in external system. Use the variable selector to add external system id or external system title to the url.', + } +); + +export const UPDATE_INCIDENT_METHOD = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel', + { + defaultMessage: 'Update Case Method', + } +); + +export const UPDATE_INCIDENT_URL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel', + { + defaultMessage: 'Update Case URL', + } +); +export const UPDATE_INCIDENT_URL_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp', + { + defaultMessage: 'API URL to update case.', + } +); + +export const UPDATE_INCIDENT_JSON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.updateIncidentJsonTextFieldLabel', + { + defaultMessage: 'Update Case Object', + } +); +export const UPDATE_INCIDENT_JSON_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.updateIncidentJsonHelpl', + { + defaultMessage: + 'JSON object to update case. Use the variable selector to add Cases data to the payload.', + } +); + +export const CREATE_COMMENT_METHOD = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createCommentMethodTextFieldLabel', + { + defaultMessage: 'Create Comment Method', + } +); +export const CREATE_COMMENT_URL = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createCommentUrlTextFieldLabel', + { + defaultMessage: 'Create Comment URL', + } +); + +export const CREATE_COMMENT_URL_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createCommentUrlHelp', + { + defaultMessage: 'API URL to add comment to case.', + } +); + +export const CREATE_COMMENT_JSON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createCommentJsonTextFieldLabel', + { + defaultMessage: 'Create Comment Object', + } +); +export const CREATE_COMMENT_JSON_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.createCommentJsonHelp', + { + defaultMessage: + 'JSON object to create a comment. Use the variable selector to add Cases data to the payload.', + } +); + +export const HAS_AUTH = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel', + { + defaultMessage: 'Require authentication for this webhook', + } +); + +export const USERNAME = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.userTextFieldLabel', + { + defaultMessage: 'Username', + } +); + +export const PASSWORD = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel', + { + defaultMessage: 'Password', + } +); + +export const HEADERS_SWITCH = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch', + { + defaultMessage: 'Add HTTP header', + } +); + +export const HEADERS_TITLE = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.httpHeadersTitle', + { + defaultMessage: 'Headers in use', + } +); + +export const AUTH_TITLE = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.authenticationLabel', + { + defaultMessage: 'Authentication', + } +); + +export const STEP_1 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step1', { + defaultMessage: 'Set up connector', +}); + +export const STEP_2 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step2', { + defaultMessage: 'Create case', +}); + +export const STEP_2_DESCRIPTION = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.step2Description', + { + defaultMessage: + 'Set fields to create the case in the external system. Check your service’s API documentation to understand what fields are required', + } +); + +export const STEP_3 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step3', { + defaultMessage: 'Get case information', +}); + +export const STEP_3_DESCRIPTION = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.step3Description', + { + defaultMessage: + 'Set fields to add comments to the case in external system. For some systems, this may be the same method as creating updates in cases. Check your service’s API documentation to understand what fields are required.', + } +); + +export const STEP_4 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step4', { + defaultMessage: 'Comments and updates', +}); + +export const STEP_4A = i18n.translate('xpack.stackConnectors.components.casesWebhook.step4a', { + defaultMessage: 'Create update in case', +}); + +export const STEP_4A_DESCRIPTION = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.step4aDescription', + { + defaultMessage: + 'Set fields to create updates to the case in external system. For some systems, this may be the same method as adding comments to cases.', + } +); + +export const STEP_4B = i18n.translate('xpack.stackConnectors.components.casesWebhook.step4b', { + defaultMessage: 'Add comment in case', +}); + +export const STEP_4B_DESCRIPTION = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.step4bDescription', + { + defaultMessage: + 'Set fields to add comments to the case in external system. For some systems, this may be the same method as creating updates in cases.', + } +); + +export const NEXT = i18n.translate('xpack.stackConnectors.components.casesWebhook.next', { + defaultMessage: 'Next', +}); + +export const PREVIOUS = i18n.translate('xpack.stackConnectors.components.casesWebhook.previous', { + defaultMessage: 'Previous', +}); + +export const CASE_TITLE_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.caseTitleDesc', + { + defaultMessage: 'Kibana case title', + } +); + +export const CASE_DESCRIPTION_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc', + { + defaultMessage: 'Kibana case description', + } +); + +export const CASE_TAGS_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.caseTagsDesc', + { + defaultMessage: 'Kibana case tags', + } +); + +export const CASE_COMMENT_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.caseCommentDesc', + { + defaultMessage: 'Kibana case comment', + } +); + +export const EXTERNAL_ID_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.externalIdDesc', + { + defaultMessage: 'External system id', + } +); + +export const EXTERNAL_TITLE_DESC = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.externalTitleDesc', + { + defaultMessage: 'External system title', + } +); + +export const DOC_LINK = i18n.translate('xpack.stackConnectors.components.casesWebhook.docLink', { + defaultMessage: 'Configuring Webhook - Case Management connector.', +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/types.ts similarity index 82% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/types.ts index 36e072cc8db44..68f560dd59415 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/types.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType, ExecutorSubActionPushParams, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/cases_webhook/types'; -import { UserConfiguredActionConnector } from '../../../../types'; +} from '../../../../server/connector_types/cases/cases_webhook/types'; export interface CasesWebhookActionParams { subAction: string; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/validator.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/validator.ts similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/validator.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/validator.ts index 7f34f76807e55..d3d7f6dc8e612 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/validator.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/validator.ts @@ -11,9 +11,9 @@ import { ValidationFunc, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { containsChars, isUrl } from '@kbn/es-ui-shared-plugin/static/validators/string'; +import { templateActionVariable } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; import { casesVars, commentVars, urlVars, urlVarsExt } from './action_variables'; -import { templateActionVariable } from '../../../lib'; const errorCode: ERROR_CODE = 'ERR_FIELD_MISSING'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.test.tsx new file mode 100644 index 0000000000000..14e767915515a --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.test.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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; + +const CONNECTOR_TYPE_ID = '.cases-webhook'; +let connectorTypeModel: ConnectorTypeModel; + +beforeAll(() => { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('logoWebhook'); + }); +}); + +describe('webhook action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + subActionParams: { incident: { title: 'some title {{test}}' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { 'subActionParams.incident.title': [] }, + }); + }); + + test('params validation fails when body is not valid', async () => { + const actionParams = { + subActionParams: { incident: { title: '' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.title': ['Title is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.tsx similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.tsx index 5ac8d915e26d9..6e86df15c09c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook.tsx @@ -7,10 +7,13 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; import { CasesWebhookActionParams, CasesWebhookConfig, CasesWebhookSecrets } from './types'; -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< CasesWebhookConfig, CasesWebhookSecrets, CasesWebhookActionParams @@ -19,14 +22,14 @@ export function getActionType(): ActionTypeModel< id: '.cases-webhook', iconClass: 'logoWebhook', selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.selectMessageText', + 'xpack.stackConnectors.components.casesWebhook.selectMessageText', { defaultMessage: 'Send a request to a Case Management web service.', } ), isExperimental: true, actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.actionTypeTitle', + 'xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle', { defaultMessage: 'Webhook - Case Management data', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.test.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.test.tsx index ba0cf756530e7..fbe24f5e5d863 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import CasesWebhookActionConnectorFields from './webhook_connectors'; -import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../test_utils'; +import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../../lib/test_utils'; import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MockCodeEditor } from '../../../code_editor.mock'; +import { MockCodeEditor } from '@kbn/triggers-actions-ui-plugin/public/application/code_editor.mock'; import * as i18n from './translations'; -const kibanaReactPath = '../../../../../../../../src/plugins/kibana_react/public'; +const kibanaReactPath = '../../../../../../../src/plugins/kibana_react/public'; -jest.mock('../../../../common/lib/kibana', () => { - const originalModule = jest.requireActual('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public', () => { + const originalModule = jest.requireActual('@kbn/triggers-actions-ui-plugin/public'); return { ...originalModule, useKibana: () => ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.tsx index 29983935cf7f1..73e424901469a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_connectors.tsx @@ -17,8 +17,8 @@ import { EuiStepsHorizontal, EuiStepStatus, } from '@elastic/eui'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionConnectorFieldsProps } from '../../../../types'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; import { AuthStep, CreateStep, GetStep, UpdateStep } from './steps'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.test.tsx similarity index 93% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.test.tsx index 91adb9616c4ab..f931fd1eeddad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.test.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import WebhookParamsFields from './webhook_params'; -import { MockCodeEditor } from '../../../code_editor.mock'; +import { MockCodeEditor } from '@kbn/triggers-actions-ui-plugin/public/application/code_editor.mock'; import { CasesWebhookActionConnector } from './types'; -const kibanaReactPath = '../../../../../../../../src/plugins/kibana_react/public'; +const kibanaReactPath = '../../../../../../../src/plugins/kibana_react/public'; jest.mock(kibanaReactPath, () => { const original = jest.requireActual(kibanaReactPath); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.tsx similarity index 84% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.tsx index 2d1f8b03bd08f..dab3f8cc95832 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/cases_webhook/webhook_params.tsx @@ -8,20 +8,22 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiComboBox, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { ActionParamsProps } from '../../../../types'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, +} from '@kbn/triggers-actions-ui-plugin/public'; import { CasesWebhookActionConnector, CasesWebhookActionParams } from './types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; const CREATE_COMMENT_WARNING_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningTitle', + 'xpack.stackConnectors.components.casesWebhook.createCommentWarningTitle', { defaultMessage: 'Unable to share case comments', } ); const CREATE_COMMENT_WARNING_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningDesc', + 'xpack.stackConnectors.components.casesWebhook.createCommentWarningDesc', { defaultMessage: 'Configure the Create Comment URL and Create Comment Objects fields for the connector to share comments externally.', @@ -108,12 +110,9 @@ const WebhookParamsFields: React.FunctionComponent 0 && incident.title !== undefined } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.titleFieldLabel', - { - defaultMessage: 'Summary (required)', - } - )} + label={i18n.translate('xpack.stackConnectors.components.casesWebhook.titleFieldLabel', { + defaultMessage: 'Summary (required)', + })} > 0 ? comments[0].comment : undefined} label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.commentsTextAreaFieldLabel', + 'xpack.stackConnectors.components.casesWebhook.commentsTextAreaFieldLabel', { defaultMessage: 'Additional comments', } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/index.ts new file mode 100644 index 0000000000000..22f32cf636036 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getCasesWebhookConnectorType } from './cases_webhook'; +export { getJiraConnectorType } from './jira'; +export { getResilientConnectorType } from './resilient'; +export { + getServiceNowITSMConnectorType, + getServiceNowSIRConnectorType, + getServiceNowITOMConnectorType, +} from './servicenow'; +export { getSwimlaneConnectorType } from './swimlane'; +export { getXmattersConnectorType } from './xmatters'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/api.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/api.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/api.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/api.ts index f213bbc7bda40..e6db358b7988d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/api.ts @@ -6,9 +6,11 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; -import { BASE_ACTION_API_PATH } from '../../../constants'; -import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; +import { ActionTypeExecutorResult, BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../../lib/rewrite_response_body'; import { Fields, Issue, IssueTypes } from './types'; export async function getIssueTypes({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/index.ts similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/index.ts index a597a5ea6df4d..184109b17cc92 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getActionType as getWebhookActionType } from './webhook'; +export { getConnectorType as getJiraConnectorType } from './jira'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.test.tsx similarity index 57% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.test.tsx index 07aa45cfe1925..2f8299ea010da 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.test.tsx @@ -5,26 +5,26 @@ * 2.0. */ -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; +import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; -const ACTION_TYPE_ID = '.jira'; -let actionTypeModel: ActionTypeModel; +const CONNECTOR_TYPE_ID = '.jira'; +let connectorTypeModel: ConnectorTypeModel; beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); if (getResult !== null) { - actionTypeModel = getResult; + connectorTypeModel = getResult; } }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); }); }); @@ -34,7 +34,7 @@ describe('jira action params validation', () => { subActionParams: { incident: { summary: 'some title {{test}}' }, comments: [] }, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.summary': [], 'subActionParams.incident.labels': [] }, }); }); @@ -44,7 +44,7 @@ describe('jira action params validation', () => { subActionParams: { incident: { summary: '' }, comments: [] }, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.summary': ['Summary is required.'], 'subActionParams.incident.labels': [], @@ -60,7 +60,7 @@ describe('jira action params validation', () => { }, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.summary': [], 'subActionParams.incident.labels': ['Labels cannot contain spaces.'], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.tsx similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.tsx index 627429a39b5b3..92a98a3270cc3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira.tsx @@ -7,24 +7,24 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { GenericValidationResult, ActionTypeModel } from '../../../../types'; +import type { + GenericValidationResult, + ActionTypeModel as ConnectorTypeModel, +} from '@kbn/triggers-actions-ui-plugin/public'; import { JiraConfig, JiraSecrets, JiraActionParams } from './types'; -export const JIRA_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.selectMessageText', - { - defaultMessage: 'Create an incident in Jira.', - } -); +export const JIRA_DESC = i18n.translate('xpack.stackConnectors.components.jira.selectMessageText', { + defaultMessage: 'Create an incident in Jira.', +}); export const JIRA_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.actionTypeTitle', + 'xpack.stackConnectors.components.jira.connectorTypeTitle', { defaultMessage: 'Jira', } ); -export function getActionType(): ActionTypeModel { +export function getConnectorType(): ConnectorTypeModel { return { id: '.jira', iconClass: lazy(() => import('./logo')), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.test.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.test.tsx index 3afb0d16f8cbd..17166ae5c05c3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.test.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import JiraConnectorFields from './jira_connectors'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('JiraActionConnectorFields renders', () => { test('Jira connector fields are rendered', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.tsx similarity index 86% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.tsx index 5d056281cd38a..781b605696590 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_connectors.tsx @@ -6,15 +6,13 @@ */ import React from 'react'; - -import { ActionConnectorFieldsProps } from '../../../../types'; - -import * as i18n from './translations'; -import { +import type { + ActionConnectorFieldsProps, ConfigFieldSchema, - SimpleConnectorForm, SecretsFieldSchema, -} from '../../simple_connector_form'; +} from '@kbn/triggers-actions-ui-plugin/public'; +import { SimpleConnectorForm } from '@kbn/triggers-actions-ui-plugin/public'; +import * as i18n from './translations'; const configFormSchema: ConfigFieldSchema[] = [ { id: 'apiUrl', label: i18n.API_URL_LABEL, isUrlField: true }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.test.tsx index 8a478c84b509e..3865da8ead3a7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.test.tsx @@ -11,11 +11,11 @@ import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; import { useGetIssues } from './use_get_issues'; import { useGetSingleIssue } from './use_get_single_issue'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { act, fireEvent, render, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); jest.mock('./use_get_issue_types'); jest.mock('./use_get_fields_by_issue_type'); jest.mock('./use_get_issues'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.tsx similarity index 90% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.tsx index 5228fbcaf119f..f4aa607f7cf34 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/jira_params.tsx @@ -18,15 +18,16 @@ import { EuiFlexItem, EuiSpacer, } from '@elastic/eui'; - -import { ActionParamsProps } from '../../../../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import { JiraActionParams } from './types'; import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; import { SearchIssues } from './search_issues'; -import { useKibana } from '../../../../common/lib/kibana'; const JiraParamsFields: React.FunctionComponent> = ({ actionConnector, @@ -198,12 +199,9 @@ const JiraParamsFields: React.FunctionComponent 0 && incident.summary !== undefined } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.summaryFieldLabel', - { - defaultMessage: 'Summary (required)', - } - )} + label={i18n.translate('xpack.stackConnectors.components.jira.summaryFieldLabel', { + defaultMessage: 'Summary (required)', + })} > 0 ? comments[0].comment : undefined} label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.commentsTextAreaFieldLabel', + 'xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel', { defaultMessage: 'Additional comments', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx index f42b571408502..945dc955e4b20 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueMessage', - { - defaultMessage: 'Unable to get issue with id {id}', - values: { id }, - } - ); + i18n.translate('xpack.stackConnectors.components.jira.unableToGetIssueMessage', { + defaultMessage: 'Unable to get issue with id {id}', + values: { id }, + }); export const SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel', + 'xpack.stackConnectors.components.jira.searchIssuesComboBoxAriaLabel', { defaultMessage: 'Type to search', } ); export const SEARCH_ISSUES_PLACEHOLDER = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxPlaceholder', + 'xpack.stackConnectors.components.jira.searchIssuesComboBoxPlaceholder', { defaultMessage: 'Type to search', } ); export const SEARCH_ISSUES_LOADING = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesLoading', + 'xpack.stackConnectors.components.jira.searchIssuesLoading', { defaultMessage: 'Loading...', } ); export const LABELS_WHITE_SPACES = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.labelsSpacesErrorMessage', + 'xpack.stackConnectors.components.jira.labelsSpacesErrorMessage', { defaultMessage: 'Labels cannot contain spaces.', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/types.ts similarity index 81% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/types.ts index 85e7be1626b0c..e5796f44591ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/types.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/server/connector_types/cases/jira/types'; -import { UserConfiguredActionConnector } from '../../../../types'; +import { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; +import type { ExecutorSubActionPushParams } from '../../../../server/connector_types/cases/jira/types'; export type JiraActionConnector = UserConfiguredActionConnector; export interface JiraActionParams { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_fields_by_issue_type.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_fields_by_issue_type.tsx index 4f0e1e143291c..11b8c7fee8294 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_fields_by_issue_type.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { Fields } from './types'; import { getFieldsByIssueType } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issue_types.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issue_types.tsx index 97db74630258c..ed5a0e0a48191 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issue_types.tsx @@ -8,7 +8,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { IssueTypes } from './types'; import { getIssueTypes } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issues.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issues.tsx index 0dea608b78ac9..04153fdd5e4fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_issues.tsx @@ -8,7 +8,7 @@ import { isEmpty, debounce } from 'lodash/fp'; import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { Issue } from './types'; import { getIssues } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_single_issue.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_single_issue.tsx index 3967ab25c889e..bacc57c971ad4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/use_get_single_issue.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { Issue } from './types'; import { getIssue } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/api.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/api.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/api.ts similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/api.ts index b0378be04efd3..80341d45402e2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/api.ts @@ -6,8 +6,11 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BASE_ACTION_API_PATH } from '../../../constants'; -import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; +import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../../lib/rewrite_response_body'; export async function getIncidentTypes({ http, diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/index.ts new file mode 100644 index 0000000000000..0d6ca3e87e736 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/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 { getConnectorType as getResilientConnectorType } from './resilient'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/logo.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/logo.tsx index 7b64a1330d401..7437984034bd5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + }); +}); + +describe('resilient action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + subActionParams: { incident: { name: 'some title {{test}}' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { 'subActionParams.incident.name': [] }, + }); + }); + + test('params validation fails when body is not valid', async () => { + const actionParams = { + subActionParams: { incident: { name: '' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.name': ['Name is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient.tsx similarity index 77% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient.tsx index 2297107e914cc..9ec583762b973 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient.tsx @@ -7,24 +7,24 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { GenericValidationResult, ActionTypeModel } from '../../../../types'; +import type { + GenericValidationResult, + ActionTypeModel as ConnectorTypeModel, +} from '@kbn/triggers-actions-ui-plugin/public'; import { ResilientConfig, ResilientSecrets, ResilientActionParams } from './types'; -export const DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.selectMessageText', - { - defaultMessage: 'Create an incident in IBM Resilient.', - } -); +export const DESC = i18n.translate('xpack.stackConnectors.components.resilient.selectMessageText', { + defaultMessage: 'Create an incident in IBM Resilient.', +}); export const TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.actionTypeTitle', + 'xpack.stackConnectors.components.resilient.connectorTypeTitle', { defaultMessage: 'Resilient', } ); -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< ResilientConfig, ResilientSecrets, ResilientActionParams diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.test.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.test.tsx index 0fbe63b28b166..8364e614a0335 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.test.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import ResilientConnectorFields from './resilient_connectors'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('ResilientActionConnectorFields renders', () => { test('alerting Resilient connector fields are rendered', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.tsx similarity index 86% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.tsx index 6cc4d31f1b405..ddd219d4fc52b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_connectors.tsx @@ -6,14 +6,13 @@ */ import React from 'react'; - -import { ActionConnectorFieldsProps } from '../../../../types'; -import * as i18n from './translations'; -import { +import type { + ActionConnectorFieldsProps, ConfigFieldSchema, SecretsFieldSchema, - SimpleConnectorForm, -} from '../../simple_connector_form'; +} from '@kbn/triggers-actions-ui-plugin/public'; +import { SimpleConnectorForm } from '@kbn/triggers-actions-ui-plugin/public'; +import * as i18n from './translations'; const configFormSchema: ConfigFieldSchema[] = [ { id: 'apiUrl', label: i18n.API_URL_LABEL, isUrlField: true }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.test.tsx index 09ae6fe002d41..c6417660720f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.test.tsx @@ -14,7 +14,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; jest.mock('./use_get_incident_types'); jest.mock('./use_get_severity'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; const useGetSeverityMock = useGetSeverity as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.tsx index a50736b8c737b..04a0ba4f85ec4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/resilient_params.tsx @@ -16,15 +16,16 @@ import { EuiSelectOption, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - -import { ActionParamsProps } from '../../../../types'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import { ResilientActionParams } from './types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; -import { useKibana } from '../../../../common/lib/kibana'; const ResilientParamsFields: React.FunctionComponent> = ({ actionConnector, @@ -169,7 +170,7 @@ const ResilientParamsFields: React.FunctionComponent @@ -188,10 +189,9 @@ const ResilientParamsFields: React.FunctionComponent 0 && incident.name !== undefined } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.nameFieldLabel', - { defaultMessage: 'Name (required)' } - )} + label={i18n.translate('xpack.stackConnectors.components.resilient.nameFieldLabel', { + defaultMessage: 'Name (required)', + })} > @@ -245,7 +244,7 @@ const ResilientParamsFields: React.FunctionComponent 0 ? comments[0].comment : undefined} label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.commentsTextAreaFieldLabel', + 'xpack.stackConnectors.components.resilient.commentsTextAreaFieldLabel', { defaultMessage: 'Additional comments' } )} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/translations.ts similarity index 55% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/translations.ts index df4c15b0f322a..d049ba633e699 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/translations.ts @@ -8,49 +8,46 @@ import { i18n } from '@kbn/i18n'; export const API_URL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiUrlTextFieldLabel', + 'xpack.stackConnectors.components.resilient.apiUrlTextFieldLabel', { defaultMessage: 'URL', } ); -export const ORG_ID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.orgId', - { - defaultMessage: 'Organization ID', - } -); +export const ORG_ID_LABEL = i18n.translate('xpack.stackConnectors.components.resilient.orgId', { + defaultMessage: 'Organization ID', +}); export const API_KEY_ID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeyId', + 'xpack.stackConnectors.components.resilient.apiKeyId', { defaultMessage: 'API Key ID', } ); export const API_KEY_SECRET_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeySecret', + 'xpack.stackConnectors.components.resilient.apiKeySecret', { defaultMessage: 'API Key Secret', } ); export const NAME_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField', + 'xpack.stackConnectors.components.resilient.requiredNameTextField', { defaultMessage: 'Name is required.', } ); export const INCIDENT_TYPES_API_ERROR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetIncidentTypesMessage', + 'xpack.stackConnectors.components.resilient.unableToGetIncidentTypesMessage', { defaultMessage: 'Unable to get incident types', } ); export const SEVERITY_API_ERROR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetSeverityMessage', + 'xpack.stackConnectors.components.resilient.unableToGetSeverityMessage', { defaultMessage: 'Unable to get severity', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/types.ts similarity index 75% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/types.ts index 12c46d2900213..67006b967b5d6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/types.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/server/connector_types/cases/resilient/types'; -import { UserConfiguredActionConnector } from '../../../../types'; +import { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; +import type { ExecutorSubActionPushParams } from '../../../../server/connector_types/cases/resilient/types'; export type ResilientActionConnector = UserConfiguredActionConnector< ResilientConfig, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_incident_types.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_incident_types.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_incident_types.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_incident_types.tsx index e398f1a8dd32c..577af149d77c0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_incident_types.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_incident_types.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { getIncidentTypes } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_severity.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_severity.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_severity.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_severity.tsx index 79e3b27d0a081..7f93e908dbb9a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/use_get_severity.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/resilient/use_get_severity.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { getSeverity } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/api.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/api.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/api.ts similarity index 92% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/api.ts index 95fac75cb9f5b..4cf46d57eb7f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/api.ts @@ -10,12 +10,15 @@ import { HttpSetup } from '@kbn/core/public'; import { ActionTypeExecutorResult, INTERNAL_BASE_ACTION_API_PATH, + BASE_ACTION_API_PATH, } from '@kbn/actions-plugin/common'; -import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; -import { BASE_ACTION_API_PATH } from '../../../constants'; +import { snExternalServiceConfig } from '../../../../common/servicenow_config'; import { API_INFO_ERROR } from './translations'; import { AppInfo, RESTApiError, ServiceNowActionConnector } from './types'; -import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../../lib/rewrite_response_body'; import { Choice } from './types'; export async function getChoices({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/application_required_callout.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/application_required_callout.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/application_required_callout.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/application_required_callout.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/application_required_callout.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/application_required_callout.tsx similarity index 81% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/application_required_callout.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/application_required_callout.tsx index 9aefd7a2259b2..2938685f1102c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/application_required_callout.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/application_required_callout.tsx @@ -11,14 +11,14 @@ import { i18n } from '@kbn/i18n'; import { SNStoreButton } from './sn_store_button'; const content = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.content', + 'xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.content', { defaultMessage: 'Please go to the ServiceNow app store and install the application', } ); const ERROR_MESSAGE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.errorMessage', + 'xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.errorMessage', { defaultMessage: 'Error message', } @@ -39,7 +39,7 @@ const ApplicationRequiredCalloutComponent: React.FC = ({ appId, message } data-test-subj="snApplicationCallout" color="danger" title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout', + 'xpack.stackConnectors.components.serviceNow.applicationRequiredCallout', { defaultMessage: 'Elastic ServiceNow App not installed', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/credentials_auth.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/credentials_auth.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/credentials_auth.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/credentials_auth.tsx index b996d8feac8e2..46b509e22d6fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/credentials_auth.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/credentials_auth.tsx @@ -9,7 +9,7 @@ import React, { memo } from 'react'; import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { PasswordField } from '../../../password_field'; +import { PasswordField } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from '../translations'; interface Props { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/index.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/index.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/oauth.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/oauth.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/oauth.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/oauth.tsx index 4c51641ea0bf1..febcb6c498307 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/auth_types/oauth.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/auth_types/oauth.tsx @@ -9,8 +9,8 @@ import React, { memo } from 'react'; import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { TextAreaField, TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { PasswordField } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from '../translations'; -import { PasswordField } from '../../../password_field'; interface Props { readOnly: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials.test.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials.test.tsx index d0b0b50b0ffc6..aab2ab0fb21c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { Credentials } from './credentials'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('Credentials', () => { const connector = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials_api_url.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials_api_url.tsx similarity index 92% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials_api_url.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials_api_url.tsx index 392b173080346..58130ee8d8a09 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/credentials_api_url.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/credentials_api_url.tsx @@ -12,7 +12,7 @@ import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; interface Props { @@ -31,7 +31,7 @@ const CredentialsApiUrlComponent: React.FC = ({ isLoading, readOnly, path

= ({ onMigrate }) => { data-test-subj="snDeprecatedCallout" color="warning" title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutTitle', + 'xpack.stackConnectors.components.serviceNow.deprecatedCalloutTitle', { defaultMessage: 'This connector type is deprecated', } @@ -40,13 +40,13 @@ const DeprecatedCalloutComponent: React.FC = ({ onMigrate }) => { > {i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutCreate', + 'xpack.stackConnectors.components.serviceNow.deprecatedCalloutCreate', { defaultMessage: 'or create a new one.', } @@ -64,7 +64,7 @@ const DeprecatedCalloutComponent: React.FC = ({ onMigrate }) => { export const DeprecatedCallout = memo(DeprecatedCalloutComponent); const updateThisConnectorMessage = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutMigrate', + 'xpack.stackConnectors.components.serviceNow.deprecatedCalloutMigrate', { defaultMessage: 'Update this connector,', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.test.ts similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.test.ts index a02fca8f2dca4..7700014e58021 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.test.ts @@ -11,7 +11,7 @@ import { getConnectorDescriptiveTitle, getSelectedConnectorIcon, } from './helpers'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; const deprecatedConnector: ActionConnector = { secrets: {}, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.ts index 3798a312083e1..def683edbdd33 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/helpers.ts @@ -7,9 +7,12 @@ import { lazy, ComponentType } from 'react'; import { EuiSelectOption } from '@elastic/eui'; +import { + ActionConnector, + deprecatedMessage, + IErrorObject, +} from '@kbn/triggers-actions-ui-plugin/public'; import { AppInfo, Choice, RESTApiError } from './types'; -import { ActionConnector, IErrorObject } from '../../../../types'; -import { deprecatedMessage } from '../../../../common/connectors_selection'; export const DEFAULT_CORRELATION_ID = '{{rule.id}}:{{alert.id}}'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts similarity index 73% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts index c313fd5d0edd6..553cf2edde846 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts @@ -6,7 +6,7 @@ */ export { - getServiceNowITSMActionType, - getServiceNowSIRActionType, - getServiceNowITOMActionType, + getServiceNowITSMConnectorType, + getServiceNowSIRConnectorType, + getServiceNowITOMConnectorType, } from './servicenow'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/installation_callout.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/installation_callout.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/installation_callout.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/installation_callout.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/installation_callout.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/installation_callout.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/installation_callout.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/installation_callout.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/logo.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/logo.tsx index e2c5546e31a72..f97b07247569d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; function Logo(props: LogoProps) { return ( diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.test.tsx new file mode 100644 index 0000000000000..9427623f0de8a --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.test.tsx @@ -0,0 +1,80 @@ +/* + * 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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; + +const SERVICENOW_ITSM_CONNECTOR_TYPE_ID = '.servicenow'; +const SERVICENOW_SIR_CONNECTOR_TYPE_ID = '.servicenow-sir'; +const SERVICENOW_ITOM_CONNECTOR_TYPE_ID = '.servicenow-itom'; +let connectorTypeRegistry: TypeRegistry; + +beforeAll(() => { + connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); +}); + +describe('connectorTypeRegistry.get() works', () => { + [ + SERVICENOW_ITSM_CONNECTOR_TYPE_ID, + SERVICENOW_SIR_CONNECTOR_TYPE_ID, + SERVICENOW_ITOM_CONNECTOR_TYPE_ID, + ].forEach((id) => { + test(`${id}: connector type static data is as expected`, () => { + const connectorTypeModel = connectorTypeRegistry.get(id); + expect(connectorTypeModel.id).toEqual(id); + }); + }); +}); + +describe('servicenow action params validation', () => { + [SERVICENOW_ITSM_CONNECTOR_TYPE_ID, SERVICENOW_SIR_CONNECTOR_TYPE_ID].forEach((id) => { + test(`${id}: action params validation succeeds when action params is valid`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(id); + const actionParams = { + subActionParams: { incident: { short_description: 'some title {{test}}' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { ['subActionParams.incident.short_description']: [] }, + }); + }); + + test(`${id}: params validation fails when short_description is not valid`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(id); + const actionParams = { + subActionParams: { incident: { short_description: '' }, comments: [] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + ['subActionParams.incident.short_description']: ['Short description is required.'], + }, + }); + }); + }); + + test(`${SERVICENOW_ITOM_CONNECTOR_TYPE_ID}: action params validation succeeds when action params is valid`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITOM_CONNECTOR_TYPE_ID); + const actionParams = { subActionParams: { severity: 'Critical' } }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { ['severity']: [] }, + }); + }); + + test(`${SERVICENOW_ITOM_CONNECTOR_TYPE_ID}: params validation fails when severity is not valid`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITOM_CONNECTOR_TYPE_ID); + const actionParams = { subActionParams: { severity: null } }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { ['severity']: ['Severity is required.'] }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.tsx similarity index 84% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.tsx index 85b2aea862848..932d244e852f8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow.tsx @@ -7,7 +7,10 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; import { ServiceNowConfig, ServiceNowITOMActionParams, @@ -18,48 +21,48 @@ import { import { getConnectorDescriptiveTitle, getSelectedConnectorIcon } from './helpers'; export const SERVICENOW_ITOM_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.actionTypeTitle', + 'xpack.stackConnectors.components.serviceNowITOM.connectorTypeTitle', { defaultMessage: 'ServiceNow ITOM', } ); export const SERVICENOW_ITOM_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.selectMessageText', + 'xpack.stackConnectors.components.serviceNowITOM.selectMessageText', { defaultMessage: 'Create an event in ServiceNow ITOM.', } ); export const SERVICENOW_ITSM_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.selectMessageText', + 'xpack.stackConnectors.components.serviceNowITSM.selectMessageText', { defaultMessage: 'Create an incident in ServiceNow ITSM.', } ); export const SERVICENOW_SIR_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.selectMessageText', + 'xpack.stackConnectors.components.serviceNowSIR.selectMessageText', { defaultMessage: 'Create an incident in ServiceNow SecOps.', } ); export const SERVICENOW_ITSM_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.actionTypeTitle', + 'xpack.stackConnectors.components.serviceNowITSM.connectorTypeTitle', { defaultMessage: 'ServiceNow ITSM', } ); export const SERVICENOW_SIR_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.actionTypeTitle', + 'xpack.stackConnectors.components.serviceNowSIR.connectorTypeTitle', { defaultMessage: 'ServiceNow SecOps', } ); -export function getServiceNowITSMActionType(): ActionTypeModel< +export function getServiceNowITSMConnectorType(): ConnectorTypeModel< ServiceNowConfig, ServiceNowSecrets, ServiceNowITSMActionParams @@ -97,7 +100,7 @@ export function getServiceNowITSMActionType(): ActionTypeModel< }; } -export function getServiceNowSIRActionType(): ActionTypeModel< +export function getServiceNowSIRConnectorType(): ConnectorTypeModel< ServiceNowConfig, ServiceNowSecrets, ServiceNowSIRActionParams @@ -135,7 +138,7 @@ export function getServiceNowSIRActionType(): ActionTypeModel< }; } -export function getServiceNowITOMActionType(): ActionTypeModel< +export function getServiceNowITOMConnectorType(): ConnectorTypeModel< ServiceNowConfig, ServiceNowSecrets, ServiceNowITOMActionParams diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.test.tsx index f3ec5594144ec..6bf81f5aeae74 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.test.tsx @@ -10,17 +10,17 @@ import { act, within } from '@testing-library/react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { render, act as reactAct } from '@testing-library/react'; -import { ConnectorValidationFunc } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; -import { updateActionConnector } from '../../../lib/action_connector_api'; +import { ConnectorValidationFunc } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; +import { updateActionConnector } from '@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'; import ServiceNowConnectorFields from './servicenow_connectors'; import { getAppInfo } from './api'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import { mount } from 'enzyme'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../lib/action_connector_api'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.tsx similarity index 92% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.tsx index d9e462ae552de..b9aeca98b6cde 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors.tsx @@ -8,23 +8,24 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; import { useFormContext, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; - -import { ActionConnectorFieldsProps } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + HiddenField, + updateActionConnector, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; +import type { ConnectorFormSchema } from '@kbn/triggers-actions-ui-plugin/public'; +import { snExternalServiceConfig } from '../../../../common/servicenow_config'; import { DeprecatedCallout } from './deprecated_callout'; import { useGetAppInfo } from './use_get_app_info'; import { ApplicationRequiredCallout } from './application_required_callout'; import { isRESTApiError } from './helpers'; import { InstallationCallout } from './installation_callout'; import { UpdateConnector, UpdateConnectorFormSchema } from './update_connector'; -import { updateActionConnector } from '../../../lib/action_connector_api'; import { Credentials } from './credentials'; import * as i18n from './translations'; import { ServiceNowActionConnector, ServiceNowConfig, ServiceNowSecrets } from './types'; -import { HiddenField } from '../../hidden_field'; -import { ConnectorFormSchema } from '../../../sections/action_connector_form/types'; // eslint-disable-next-line import/no-default-export export { ServiceNowConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.test.tsx index a0dda6edf76e0..e70005f8c7e1b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.test.tsx @@ -8,7 +8,11 @@ import { act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { AppMockRenderer, ConnectorFormTestProvider, createAppMockRenderer } from '../test_utils'; +import { + AppMockRenderer, + ConnectorFormTestProvider, + createAppMockRenderer, +} from '../../lib/test_utils'; import ServiceNowConnectorFieldsNoApp from './servicenow_connectors_no_app'; describe('ServiceNowActionConnectorFields renders', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.tsx similarity index 85% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.tsx index e4332f163151b..d1a2f3472acbb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors_no_app.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_connectors_no_app.tsx @@ -7,10 +7,9 @@ import React from 'react'; import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; - -import { ActionConnectorFieldsProps } from '../../../../types'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { ConnectorFormSchema } from '@kbn/triggers-actions-ui-plugin/public'; import { Credentials } from './credentials'; -import { ConnectorFormSchema } from '../../../sections/action_connector_form/types'; import { ServiceNowConfig, ServiceNowSecrets } from './types'; const ServiceNowConnectorFieldsNoApp: React.FC = ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.test.tsx index d17c77da1f820..60531c7a7104d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.test.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { mount } from 'enzyme'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useChoices } from './use_choices'; import ServiceNowITOMParamsFields from './servicenow_itom_params'; jest.mock('./use_choices'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useChoicesMock = useChoices as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.tsx similarity index 94% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.tsx index 8ad32fc0bc86b..caa2f40bac2c1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itom_params.tsx @@ -7,10 +7,12 @@ import React, { useCallback, useEffect, useRef, useMemo } from 'react'; import { EuiFormRow, EuiSpacer, EuiTitle, EuiSelect } from '@elastic/eui'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionParamsProps } from '../../../../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; import { useChoices } from './use_choices'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.test.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.test.tsx index f8375a5aaeb6e..aa6cb6c71278d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.test.tsx @@ -9,14 +9,14 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useGetChoices } from './use_get_choices'; import ServiceNowITSMParamsFields from './servicenow_itsm_params'; import { Choice } from './types'; import { merge } from 'lodash'; jest.mock('./use_get_choices'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useGetChoicesMock = useGetChoices as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.tsx index 3bae1c3b858d6..a585ee48864e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_itsm_params.tsx @@ -16,12 +16,13 @@ import { EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionParamsProps } from '../../../../types'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import { ServiceNowITSMActionParams, Choice, Fields } from './types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; import { useGetChoices } from './use_get_choices'; import { choicesToEuiOptions, DEFAULT_CORRELATION_ID } from './helpers'; @@ -252,7 +253,7 @@ const ServiceNowParamsFields: React.FunctionComponent< helpText={ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_selection_row.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_selection_row.tsx similarity index 81% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_selection_row.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_selection_row.tsx index 65068c6f56a07..f1fbb1a009ec7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_selection_row.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_selection_row.tsx @@ -8,8 +8,8 @@ import { EuiIconTip } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionConnector } from '../../../../types'; -import { connectorDeprecatedMessage } from '../../../../common/connectors_selection'; +import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; +import { connectorDeprecatedMessage } from '@kbn/triggers-actions-ui-plugin/public'; // eslint-disable-next-line import/no-default-export export { ServiceNowSelectableRowIcon as default }; @@ -32,7 +32,7 @@ export function ServiceNowSelectableRowIcon({ } const deprecatedTooltipTitle = i18n.translate( - 'xpack.triggersActionsUI.sections.actionForm.deprecatedTooltipTitle', + 'xpack.stackConnectors.components.serviceNow.deprecatedTooltipTitle', { defaultMessage: 'Deprecated connector', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.test.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.test.tsx index 9f15cb07f92e1..8739938891625 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.test.tsx @@ -9,14 +9,14 @@ import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useGetChoices } from './use_get_choices'; import ServiceNowSIRParamsFields from './servicenow_sir_params'; import { Choice } from './types'; import { merge } from 'lodash'; jest.mock('./use_get_choices'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useGetChoicesMock = useGetChoices as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.tsx index a341dca3f255a..e58d635f9ef2d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/servicenow_sir_params.tsx @@ -16,11 +16,12 @@ import { EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionParamsProps } from '../../../../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; import { useGetChoices } from './use_get_choices'; @@ -239,7 +240,7 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< helpText={ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/sn_store_button.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/sn_store_button.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/sn_store_button.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/sn_store_button.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/sn_store_button.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/sn_store_button.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/sn_store_button.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/sn_store_button.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/translations.ts similarity index 50% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/translations.ts index 7c6fab2b18792..191fc001d7e80 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/translations.ts @@ -8,146 +8,143 @@ import { i18n } from '@kbn/i18n'; export const API_URL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.apiUrlTextFieldLabel', { defaultMessage: 'ServiceNow instance URL', } ); export const API_URL_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField', + 'xpack.stackConnectors.components.serviceNow.invalidApiUrlTextField', { defaultMessage: 'URL is invalid.', } ); export const AUTHENTICATION_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel', + 'xpack.stackConnectors.components.serviceNow.authenticationLabel', { defaultMessage: 'Authentication', } ); export const USERNAME_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.usernameTextFieldLabel', { defaultMessage: 'Username', } ); export const USERNAME_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField', + 'xpack.stackConnectors.components.serviceNow.requiredUsernameTextField', { defaultMessage: 'Username is required.', } ); export const PASSWORD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.passwordTextFieldLabel', { defaultMessage: 'Password', } ); export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField', + 'xpack.stackConnectors.components.serviceNow.requiredShortDescTextField', { defaultMessage: 'Short description is required.', } ); -export const INCIDENT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.title', - { - defaultMessage: 'Incident', - } -); +export const INCIDENT = i18n.translate('xpack.stackConnectors.components.serviceNow.title', { + defaultMessage: 'Incident', +}); export const SECURITY_INCIDENT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenowSIR.title', + 'xpack.stackConnectors.components.serviceNowSIR.title', { defaultMessage: 'Security Incident', } ); export const SHORT_DESCRIPTION_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.titleFieldLabel', + 'xpack.stackConnectors.components.serviceNow.titleFieldLabel', { defaultMessage: 'Short description (required)', } ); export const DESCRIPTION_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.descriptionTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.descriptionTextAreaFieldLabel', { defaultMessage: 'Description', } ); export const COMMENTS_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.commentsTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.commentsTextAreaFieldLabel', { defaultMessage: 'Additional comments', } ); export const CHOICES_API_ERROR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unableToGetChoicesMessage', + 'xpack.stackConnectors.components.serviceNow.unableToGetChoicesMessage', { defaultMessage: 'Unable to get choices', } ); export const CATEGORY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.categoryTitle', + 'xpack.stackConnectors.components.serviceNow.categoryTitle', { defaultMessage: 'Category', } ); export const SUBCATEGORY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.subcategoryTitle', + 'xpack.stackConnectors.components.serviceNow.subcategoryTitle', { defaultMessage: 'Subcategory', } ); export const URGENCY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.urgencySelectFieldLabel', + 'xpack.stackConnectors.components.serviceNow.urgencySelectFieldLabel', { defaultMessage: 'Urgency', } ); export const SEVERITY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectFieldLabel', + 'xpack.stackConnectors.components.serviceNow.severitySelectFieldLabel', { defaultMessage: 'Severity', } ); export const IMPACT_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.impactSelectFieldLabel', + 'xpack.stackConnectors.components.serviceNow.impactSelectFieldLabel', { defaultMessage: 'Impact', } ); export const PRIORITY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.prioritySelectFieldLabel', + 'xpack.stackConnectors.components.serviceNow.prioritySelectFieldLabel', { defaultMessage: 'Priority', } ); export const API_INFO_ERROR = (status: number) => - i18n.translate('xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiInfoError', { + i18n.translate('xpack.stackConnectors.components.serviceNow.apiInfoError', { values: { status }, defaultMessage: 'Received status: {status} when attempting to get application information', }); export const FETCH_ERROR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.fetchErrorMsg', + 'xpack.stackConnectors.components.serviceNow.fetchErrorMsg', { defaultMessage: 'Failed to fetch. Check the URL or the CORS configuration of your ServiceNow instance.', @@ -155,7 +152,7 @@ export const FETCH_ERROR = i18n.translate( ); export const INSTALLATION_CALLOUT_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.installationCalloutTitle', + 'xpack.stackConnectors.components.serviceNow.installationCalloutTitle', { defaultMessage: 'To use this connector, first install the Elastic app from the ServiceNow app store.', @@ -163,218 +160,206 @@ export const INSTALLATION_CALLOUT_TITLE = i18n.translate( ); export const UPDATE_SUCCESS_TOAST_TITLE = (connectorName: string) => - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateSuccessToastTitle', - { - defaultMessage: '{connectorName} connector updated', - values: { - connectorName, - }, - } - ); + i18n.translate('xpack.stackConnectors.components.serviceNow.updateSuccessToastTitle', { + defaultMessage: '{connectorName} connector updated', + values: { + connectorName, + }, + }); export const UPDATE_SUCCESS_TOAST_TEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateCalloutText', + 'xpack.stackConnectors.components.serviceNow.updateCalloutText', { defaultMessage: 'Connector has been updated.', } ); export const VISIT_SN_STORE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.visitSNStore', + 'xpack.stackConnectors.components.serviceNow.visitSNStore', { defaultMessage: 'Visit ServiceNow app store', } ); export const SETUP_DEV_INSTANCE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.setupDevInstance', + 'xpack.stackConnectors.components.serviceNow.setupDevInstance', { defaultMessage: 'setup a developer instance', } ); export const SN_INSTANCE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.snInstanceLabel', + 'xpack.stackConnectors.components.serviceNow.snInstanceLabel', { defaultMessage: 'ServiceNow instance', } ); -export const UNKNOWN = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unknown', - { - defaultMessage: 'UNKNOWN', - } -); +export const UNKNOWN = i18n.translate('xpack.stackConnectors.components.serviceNow.unknown', { + defaultMessage: 'UNKNOWN', +}); export const CORRELATION_ID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationID', + 'xpack.stackConnectors.components.serviceNow.correlationID', { defaultMessage: 'Correlation ID (optional)', } ); export const CORRELATION_DISPLAY = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationDisplay', + 'xpack.stackConnectors.components.serviceNow.correlationDisplay', { defaultMessage: 'Correlation display (optional)', } ); -export const EVENT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenowITOM.event', - { - defaultMessage: 'Event', - } -); +export const EVENT = i18n.translate('xpack.stackConnectors.components.serviceNowITOM.event', { + defaultMessage: 'Event', +}); /** * ITOM */ export const SOURCE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.sourceTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.sourceTextAreaFieldLabel', { defaultMessage: 'Source', } ); export const EVENT_CLASS = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.eventClassTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.eventClassTextAreaFieldLabel', { defaultMessage: 'Source instance', } ); export const RESOURCE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.resourceTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.resourceTextAreaFieldLabel', { defaultMessage: 'Resource', } ); export const NODE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.nodeTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.nodeTextAreaFieldLabel', { defaultMessage: 'Node', } ); export const METRIC_NAME = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.metricNameTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.metricNameTextAreaFieldLabel', { defaultMessage: 'Metric name', } ); export const TYPE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.typeTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.typeTextAreaFieldLabel', { defaultMessage: 'Type', } ); export const MESSAGE_KEY = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.messageKeyTextAreaFieldLabel', + 'xpack.stackConnectors.components.serviceNow.messageKeyTextAreaFieldLabel', { defaultMessage: 'Message key', } ); export const SEVERITY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredSeverityTextField', + 'xpack.stackConnectors.components.serviceNow.requiredSeverityTextField', { defaultMessage: 'Severity is required.', } ); export const SEVERITY_REQUIRED_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severityRequiredSelectFieldLabel', + 'xpack.stackConnectors.components.serviceNow.severityRequiredSelectFieldLabel', { defaultMessage: 'Severity (required)', } ); export const CLIENTID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientIdTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.clientIdTextFieldLabel', { defaultMessage: 'Client ID', } ); export const CLIENTSECRET_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientSecretTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.clientSecretTextFieldLabel', { defaultMessage: 'Client Secret', } ); export const KEY_ID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.keyIdTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.keyIdTextFieldLabel', { defaultMessage: 'JWT Verifier Key ID', } ); export const USER_IDENTIFIER_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.userEmailTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.userEmailTextFieldLabel', { defaultMessage: 'User Identifier', } ); export const PRIVATE_KEY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.privateKeyTextFieldLabel', { defaultMessage: 'Private Key', } ); export const PRIVATE_KEY_PASSWORD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassTextFieldLabel', + 'xpack.stackConnectors.components.serviceNow.privateKeyPassTextFieldLabel', { defaultMessage: 'Private Key Password', } ); export const PRIVATE_KEY_PASSWORD_HELPER_TEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassLabelHelpText', + 'xpack.stackConnectors.components.serviceNow.privateKeyPassLabelHelpText', { defaultMessage: 'This is only required if you have set a password on your private key', } ); export const CLIENTID_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredClientIdTextField', + 'xpack.stackConnectors.components.serviceNow.requiredClientIdTextField', { defaultMessage: 'Client ID is required.', } ); export const PRIVATE_KEY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPrivateKeyTextField', + 'xpack.stackConnectors.components.serviceNow.requiredPrivateKeyTextField', { defaultMessage: 'Private Key is required.', } ); export const KEYID_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredKeyIdTextField', + 'xpack.stackConnectors.components.serviceNow.requiredKeyIdTextField', { defaultMessage: 'JWT Verifier Key ID is required.', } ); export const USER_IDENTIFIER_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUserIdentifierTextField', + 'xpack.stackConnectors.components.serviceNow.requiredUserIdentifierTextField', { defaultMessage: 'User Identifier is required.', } ); -export const IS_OAUTH = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.useOAuth', - { - defaultMessage: 'Use OAuth authentication', - } -); +export const IS_OAUTH = i18n.translate('xpack.stackConnectors.components.serviceNow.useOAuth', { + defaultMessage: 'Use OAuth authentication', +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/types.ts similarity index 92% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/types.ts index e6fbcf6e81939..f10de69252f9d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/types.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { ExecutorSubActionPushParamsITSM, ExecutorSubActionPushParamsSIR, ExecutorSubActionAddEventParams, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/servicenow/types'; -import { UserConfiguredActionConnector } from '../../../../types'; +} from '../../../../server/connector_types/cases/servicenow/types'; export type ServiceNowActionConnector = UserConfiguredActionConnector< ServiceNowConfig, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.test.tsx index 219ba68114852..9a6d7c26e25f0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.test.tsx @@ -13,7 +13,7 @@ import { Props, UpdateConnector } from './update_connector'; import { act } from 'react-dom/test-utils'; import { render, act as reactAct } from '@testing-library/react'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const mountUpdateConnector = (props: Partial = {}, isOAuth: boolean = false) => { return mountWithIntl( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.tsx similarity index 86% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.tsx index 936e12a564b4a..84bfba31ca56b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/update_connector.tsx @@ -21,58 +21,55 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { snExternalServiceConfig } from '../../../../common/servicenow_config'; import { CredentialsApiUrl } from './credentials_api_url'; import { CredentialsAuth, OAuth } from './auth_types'; import { SNStoreLink } from './sn_store_button'; import { ApplicationRequiredCallout } from './application_required_callout'; import { ServiceNowConfig, ServiceNowSecrets } from './types'; -const title = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormTitle', - { - defaultMessage: 'Update ServiceNow connector', - } -); +const title = i18n.translate('xpack.stackConnectors.components.serviceNow.updateFormTitle', { + defaultMessage: 'Update ServiceNow connector', +}); const step1InstallTitle = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormInstallTitle', + 'xpack.stackConnectors.components.serviceNow.updateFormInstallTitle', { defaultMessage: 'Install the Elastic ServiceNow app', } ); const step2InstanceUrlTitle = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormUrlTitle', + 'xpack.stackConnectors.components.serviceNow.updateFormUrlTitle', { defaultMessage: 'Enter your ServiceNow instance URL', } ); const step3CredentialsTitle = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormCredentialsTitle', + 'xpack.stackConnectors.components.serviceNow.updateFormCredentialsTitle', { defaultMessage: 'Provide authentication credentials', } ); const cancelButtonText = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.cancelButtonText', + 'xpack.stackConnectors.components.serviceNow.cancelButtonText', { defaultMessage: 'Cancel', } ); const confirmButtonText = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.confirmButtonText', + 'xpack.stackConnectors.components.serviceNow.confirmButtonText', { defaultMessage: 'Update', } ); const warningMessage = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.warningMessage', + 'xpack.stackConnectors.components.serviceNow.warningMessage', { defaultMessage: 'This updates all instances of this connector and cannot be reversed.', } @@ -144,7 +141,7 @@ const UpdateConnectorComponent: React.FC = ({ title: step1InstallTitle, children: ( ; const getChoicesMock = getChoices as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_choices.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_choices.tsx index e3b10896e8707..9b1b0e453f4da 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_choices.tsx @@ -8,7 +8,7 @@ import { useCallback, useMemo, useState } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { Choice, Fields } from './types'; import { useGetChoices } from './use_get_choices'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_app_info.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_app_info.test.tsx index f8e9a2c4bac1a..c8c061c9d07f1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_app_info.test.tsx @@ -13,7 +13,7 @@ import { ServiceNowActionConnector } from './types'; import { httpServiceMock } from '@kbn/core/public/mocks'; jest.mock('./api'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const getAppInfoMock = getAppInfo as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_app_info.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_app_info.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.test.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.test.tsx index 06956e6402300..b4d204c117b50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.test.tsx @@ -7,13 +7,13 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionConnector } from '../../../../types'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices'; import { getChoices } from './api'; jest.mock('./api'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; const getChoicesMock = getChoices as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.tsx index e81e0efe56a42..e2f0117e78ce4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/use_get_choices.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { ActionConnector } from '../../../../types'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { getChoices } from './api'; import { Choice } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/api.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/api.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/api.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/api.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/api.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/api.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/helpers.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/helpers.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/helpers.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/helpers.ts diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/index.ts new file mode 100644 index 0000000000000..0819c1b731ac6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/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 { getConnectorType as getSwimlaneConnectorType } from './swimlane'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/logo.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/logo.tsx index 572ff62d52f82..c86f97bf93a7c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => { return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/mocks.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/mocks.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/mocks.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/mocks.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/index.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/index.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_connection.tsx similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_connection.tsx index 1dbb2f21a4fb8..2a32a7cace748 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_connection.tsx @@ -11,8 +11,7 @@ import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { FormattedMessage } from '@kbn/i18n-react'; -import { PasswordField } from '../../../password_field'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { PasswordField, useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from '../translations'; interface Props { @@ -73,7 +72,7 @@ const SwimlaneConnectionComponent: React.FunctionComponent = ({ readOnly target="_blank" > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_fields.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_fields.tsx index 10afa07c65a16..4afb2eacc8918 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/steps/swimlane_fields.tsx @@ -16,6 +16,7 @@ import { } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { ButtonGroupField } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from '../translations'; import { MappingConfigurationKeys, @@ -23,7 +24,6 @@ import { SwimlaneFieldMappingConfig, } from '../types'; import { isRequiredField, isValidFieldForConnector } from '../helpers'; -import { ButtonGroupField } from '../../../button_group_field'; const SINGLE_SELECTION = { asPlainText: true }; const EMPTY_COMBO_BOX_ARRAY: Array> | undefined = []; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.test.tsx similarity index 55% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.test.tsx index 479496b57ba83..13b68388573a7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.test.tsx @@ -5,26 +5,26 @@ * 2.0. */ -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; +import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; -const ACTION_TYPE_ID = '.swimlane'; -let actionTypeModel: ActionTypeModel; +const CONNECTOR_TYPE_ID = '.swimlane'; +let connectorTypeModel: ConnectorTypeModel; beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); if (getResult !== null) { - actionTypeModel = getResult; + connectorTypeModel = getResult; } }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); }); }); @@ -37,7 +37,7 @@ describe('swimlane action params validation', () => { }, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.ruleName': [], 'subActionParams.incident.alertId': [], @@ -50,7 +50,7 @@ describe('swimlane action params validation', () => { subActionParams: { incident: {} }, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.ruleName': ['Rule name is required.'], 'subActionParams.incident.alertId': ['Alert ID is required.'], @@ -63,7 +63,7 @@ describe('swimlane action params validation', () => { subActionParams: {}, }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { 'subActionParams.incident.ruleName': [], 'subActionParams.incident.alertId': [], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.tsx similarity index 85% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.tsx index 5a072a54efb67..594c2e83c891f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane.tsx @@ -7,24 +7,27 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; import { SwimlaneConfig, SwimlaneSecrets, SwimlaneActionParams } from './types'; export const SW_SELECT_MESSAGE_TEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText', + 'xpack.stackConnectors.components.swimlane.selectMessageText', { defaultMessage: 'Create record in Swimlane', } ); export const SW_ACTION_TYPE_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle', + 'xpack.stackConnectors.components.swimlane.connectorTypeTitle', { defaultMessage: 'Create Swimlane Record', } ); -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< SwimlaneConfig, SwimlaneSecrets, SwimlaneActionParams diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.test.tsx index bbe86a4fe7fe1..c36b786863e3e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.test.tsx @@ -11,12 +11,12 @@ import { act } from 'react-dom/test-utils'; import SwimlaneActionConnectorFields from './swimlane_connectors'; import { useGetApplication } from './use_get_application'; import { applicationFields, mappings } from './mocks'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import { waitFor } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import { render } from '@testing-library/react'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); jest.mock('./use_get_application'); const useGetApplicationMock = useGetApplication as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.tsx index af59b9517b91d..a7533f912ed46 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_connectors.tsx @@ -15,8 +15,8 @@ import { EuiStepStatus, } from '@elastic/eui'; import { useFormContext, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionConnectorFieldsProps } from '../../../../types'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import { SwimlaneFieldMappingConfig } from './types'; import { SwimlaneConnection, SwimlaneFields } from './steps'; import { useGetApplication } from './use_get_application'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_params.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_params.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_params.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_params.tsx index 098e6490f3fe2..1faa8c8bbabf4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/swimlane_params.tsx @@ -7,11 +7,13 @@ import React, { useCallback, useEffect, useRef, useMemo } from 'react'; import { EuiCallOut, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, +} from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; -import { ActionParamsProps } from '../../../../types'; import { SwimlaneActionConnector, SwimlaneActionParams, SwimlaneConnectorType } from './types'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; const SwimlaneParamsFields: React.FunctionComponent> = ({ actionParams, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/translations.ts similarity index 53% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/translations.ts index 631e69e8e7a66..08d4a9fd198d7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/translations.ts @@ -8,140 +8,137 @@ import { i18n } from '@kbn/i18n'; export const SW_REQUIRED_RULE_NAME = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredRuleName', + 'xpack.stackConnectors.components.swimlane.error.requiredRuleName', { defaultMessage: 'Rule name is required.', } ); export const SW_REQUIRED_APP_ID_TEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAppIdText', + 'xpack.stackConnectors.components.swimlane.error.requiredAppIdText', { defaultMessage: 'An App ID is required.', } ); export const SW_GET_APPLICATION_API_ERROR = (id: string | null) => - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationMessage', - { - defaultMessage: 'Unable to get application with id {id}', - values: { id }, - } - ); + i18n.translate('xpack.stackConnectors.components.swimlane.unableToGetApplicationMessage', { + defaultMessage: 'Unable to get application with id {id}', + values: { id }, + }); export const SW_GET_APPLICATION_API_NO_FIELDS_ERROR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationFieldsMessage', + 'xpack.stackConnectors.components.swimlane.unableToGetApplicationFieldsMessage', { defaultMessage: 'Unable to get application fields', } ); export const SW_API_URL_TEXT_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiUrlTextFieldLabel', + 'xpack.stackConnectors.components.swimlane.apiUrlTextFieldLabel', { defaultMessage: 'API Url', } ); export const SW_API_URL_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.invalidApiUrlTextField', + 'xpack.stackConnectors.components.swimlane.invalidApiUrlTextField', { defaultMessage: 'URL is invalid.', } ); export const SW_APP_ID_TEXT_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.appIdTextFieldLabel', + 'xpack.stackConnectors.components.swimlane.appIdTextFieldLabel', { defaultMessage: 'Application ID', } ); export const SW_API_TOKEN_TEXT_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenTextFieldLabel', + 'xpack.stackConnectors.components.swimlane.apiTokenTextFieldLabel', { defaultMessage: 'API Token', } ); export const SW_MAPPING_TITLE_TEXT_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.mappingTitleTextFieldLabel', + 'xpack.stackConnectors.components.swimlane.mappingTitleTextFieldLabel', { defaultMessage: 'Configure Field Mappings', } ); export const SW_SEVERITY_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.severityFieldLabel', + 'xpack.stackConnectors.components.swimlane.severityFieldLabel', { defaultMessage: 'Severity', } ); export const SW_RULE_NAME_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.ruleNameFieldLabel', + 'xpack.stackConnectors.components.swimlane.ruleNameFieldLabel', { defaultMessage: 'Rule name', } ); export const SW_ALERT_ID_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.alertIdFieldLabel', + 'xpack.stackConnectors.components.swimlane.alertIdFieldLabel', { defaultMessage: 'Alert ID', } ); export const SW_CASE_ID_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseIdFieldLabel', + 'xpack.stackConnectors.components.swimlane.caseIdFieldLabel', { defaultMessage: 'Case ID', } ); export const SW_CASE_NAME_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseNameFieldLabel', + 'xpack.stackConnectors.components.swimlane.caseNameFieldLabel', { defaultMessage: 'Case name', } ); export const SW_COMMENTS_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.commentsFieldLabel', + 'xpack.stackConnectors.components.swimlane.commentsFieldLabel', { defaultMessage: 'Comments', } ); export const SW_DESCRIPTION_FIELD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.descriptionFieldLabel', + 'xpack.stackConnectors.components.swimlane.descriptionFieldLabel', { defaultMessage: 'Description', } ); export const SW_CONFIGURE_CONNECTION_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.configureConnectionLabel', + 'xpack.stackConnectors.components.swimlane.configureConnectionLabel', { defaultMessage: 'Configure API Connection' } ); export const SW_CONNECTOR_TYPE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.connectorType', + 'xpack.stackConnectors.components.swimlane.connectorType', { defaultMessage: 'Connector Type', } ); export const EMPTY_MAPPING_WARNING_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningTitle', + 'xpack.stackConnectors.components.swimlane.emptyMappingWarningTitle', { defaultMessage: 'This connector has missing field mappings', } ); export const EMPTY_MAPPING_WARNING_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningDesc', + 'xpack.stackConnectors.components.swimlane.emptyMappingWarningDesc', { defaultMessage: 'This connector cannot be selected because it is missing the required alert field mappings. You can edit this connector to add required field mappings or select a connector of type Alerts.', @@ -149,63 +146,57 @@ export const EMPTY_MAPPING_WARNING_DESC = i18n.translate( ); export const SW_REQUIRED_SEVERITY = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredSeverity', + 'xpack.stackConnectors.components.swimlane.error.requiredSeverity', { defaultMessage: 'Severity is required.', } ); export const SW_REQUIRED_CASE_NAME = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseName', + 'xpack.stackConnectors.components.swimlane.error.requiredCaseName', { defaultMessage: 'Case name is required.', } ); export const SW_REQUIRED_CASE_ID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseID', + 'xpack.stackConnectors.components.swimlane.error.requiredCaseID', { defaultMessage: 'Case ID is required.', } ); export const SW_REQUIRED_COMMENTS = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredComments', + 'xpack.stackConnectors.components.swimlane.error.requiredComments', { defaultMessage: 'Comments are required.', } ); export const SW_REQUIRED_DESCRIPTION = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredDescription', + 'xpack.stackConnectors.components.swimlane.error.requiredDescription', { defaultMessage: 'Description is required.', } ); export const SW_REQUIRED_ALERT_ID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAlertID', + 'xpack.stackConnectors.components.swimlane.error.requiredAlertID', { defaultMessage: 'Alert ID is required.', } ); -export const SW_BACK = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.prevStep', - { - defaultMessage: 'Back', - } -); +export const SW_BACK = i18n.translate('xpack.stackConnectors.components.swimlane.prevStep', { + defaultMessage: 'Back', +}); -export const SW_NEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStep', - { - defaultMessage: 'Next', - } -); +export const SW_NEXT = i18n.translate('xpack.stackConnectors.components.swimlane.nextStep', { + defaultMessage: 'Next', +}); export const SW_FIELDS_BUTTON_HELP_TEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStepHelpText', + 'xpack.stackConnectors.components.swimlane.nextStepHelpText', { defaultMessage: 'If field mappings are not configured, Swimlane connector type will be set to all.', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/types.ts similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/types.ts index cf77b094e5a12..398a63164f9e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/types.ts @@ -5,11 +5,11 @@ * 2.0. */ +import { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { ExecutorSubActionPushParams, MappingConfigType, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/swimlane/types'; -import { UserConfiguredActionConnector } from '../../../../types'; +} from '../../../../server/connector_types/cases/swimlane/types'; export type SwimlaneActionConnector = UserConfiguredActionConnector< SwimlaneConfig, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/use_get_application.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/use_get_application.test.tsx index f852d40ebef2f..d56f0d0a1d7a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/use_get_application.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'; import { getApplication } from './api'; import { useGetApplication, UseGetApplication } from './use_get_application'; jest.mock('./api'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; const getApplicationMock = getApplication as jest.Mock; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/use_get_application.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/swimlane/use_get_application.tsx diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/index.ts new file mode 100644 index 0000000000000..0faa7732194c5 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/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 { getConnectorType as getXmattersConnectorType } from './xmatters'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/logo.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/logo.tsx index dad43f666ad0a..438710f2516c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.actionTypeTitle).toEqual('xMatters data'); + }); +}); + +describe('xmatters action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + alertActionGroupName: 'Small t-shirt', + signalId: 'c9437cab-6a5b-45e8-bc8a-f4a8af440e97', + ruleName: 'Test xMatters', + date: '2022-01-18T19:01:08.818Z', + severity: 'high', + spaceId: 'default', + tags: 'test1, test2', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { alertActionGroupName: [], signalId: [] }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters.tsx similarity index 75% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters.tsx index 9f2955479d52e..2ad23f91b7c3f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters.tsx @@ -7,11 +7,14 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { XmattersActionParams, XmattersConfig, XmattersSecrets } from '../types'; -import { AlertProvidedActionVariables } from '../../../lib/action_variables'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { AlertProvidedActionVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { XmattersActionParams, XmattersConfig, XmattersSecrets } from '../../types'; -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< XmattersConfig, XmattersSecrets, XmattersActionParams @@ -19,14 +22,11 @@ export function getActionType(): ActionTypeModel< return { id: '.xmatters', iconClass: lazy(() => import('./logo')), - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.selectMessageText', - { - defaultMessage: 'Trigger an xMatters workflow.', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.xmatters.selectMessageText', { + defaultMessage: 'Trigger an xMatters workflow.', + }), actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.actionTypeTitle', + 'xpack.stackConnectors.components.xmatters.connectorTypeTitle', { defaultMessage: 'xMatters data', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.test.tsx index cef4c2c2bef81..be7a6dc0ebdb4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import XmattersActionConnectorFields from './xmatters_connectors'; -import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../test_utils'; +import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../../lib/test_utils'; import userEvent from '@testing-library/user-event'; import { act } from 'react-dom/test-utils'; import { render } from '@testing-library/react'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.tsx similarity index 89% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.tsx index 247a700876d14..881ea4c2164e2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_connectors.tsx @@ -16,12 +16,14 @@ import { useFormData, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { ActionConnectorFieldsProps } from '../../../../types'; -import { XmattersAuthenticationType } from '../types'; -import { ButtonGroupField } from '../../button_group_field'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + ButtonGroupField, + HiddenField, + PasswordField, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { XmattersAuthenticationType } from '../../types'; import * as i18n from './translations'; -import { PasswordField } from '../../password_field'; -import { HiddenField } from '../../hidden_field'; const { emptyField, urlField } = fieldValidators; @@ -53,7 +55,7 @@ const XmattersUrlField: React.FC<{ path: string; readOnly: boolean }> = ({ path, label: i18n.URL_LABEL, helpText: ( ), @@ -99,7 +101,7 @@ const XmattersActionConnectorFields: React.FunctionComponent

@@ -133,7 +135,7 @@ const XmattersActionConnectorFields: React.FunctionComponent diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.test.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.test.tsx index 64c4b5ead81ae..500ee77efbd0d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { XmattersSeverityOptions } from '../types'; +import { XmattersSeverityOptions } from '../../types'; import XmattersParamsFields from './xmatters_params'; describe('XmattersParamsFields renders', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.tsx similarity index 68% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.tsx index f66583ef32dd8..b97db2f349f8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/xmatters/xmatters_params.tsx @@ -9,15 +9,15 @@ import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; import { isUndefined } from 'lodash'; -import { ActionParamsProps } from '../../../../types'; -import { XmattersActionParams } from '../types'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextFieldWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { XmattersActionParams } from '../../types'; const severityOptions = [ { value: 'critical', text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectCriticalOptionLabel', + 'xpack.stackConnectors.components.xmatters.severitySelectCriticalOptionLabel', { defaultMessage: 'Critical', } @@ -26,7 +26,7 @@ const severityOptions = [ { value: 'high', text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectHighOptionLabel', + 'xpack.stackConnectors.components.xmatters.severitySelectHighOptionLabel', { defaultMessage: 'High', } @@ -35,7 +35,7 @@ const severityOptions = [ { value: 'medium', text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMediumOptionLabel', + 'xpack.stackConnectors.components.xmatters.severitySelectMediumOptionLabel', { defaultMessage: 'Medium', } @@ -43,17 +43,14 @@ const severityOptions = [ }, { value: 'low', - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectLowOptionLabel', - { - defaultMessage: 'Low', - } - ), + text: i18n.translate('xpack.stackConnectors.components.xmatters.severitySelectLowOptionLabel', { + defaultMessage: 'Low', + }), }, { value: 'minimal', text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMinimalOptionLabel', + 'xpack.stackConnectors.components.xmatters.severitySelectMinimalOptionLabel', { defaultMessage: 'Minimal', } @@ -91,12 +88,9 @@ const XmattersParamsFields: React.FunctionComponent ValidatedEmail[]; +} + +export function registerConnectorTypes({ + connectorTypeRegistry, + services, +}: { + connectorTypeRegistry: TriggersAndActionsUIPublicPluginSetup['actionTypeRegistry']; + services: RegistrationServices; +}) { + connectorTypeRegistry.register(getServerLogConnectorType()); + connectorTypeRegistry.register(getSlackConnectorType()); + connectorTypeRegistry.register(getEmailConnectorType(services)); + connectorTypeRegistry.register(getIndexConnectorType()); + connectorTypeRegistry.register(getPagerDutyConnectorType()); + connectorTypeRegistry.register(getSwimlaneConnectorType()); + connectorTypeRegistry.register(getCasesWebhookConnectorType()); + connectorTypeRegistry.register(getWebhookConnectorType()); + connectorTypeRegistry.register(getXmattersConnectorType()); + connectorTypeRegistry.register(getServiceNowITSMConnectorType()); + connectorTypeRegistry.register(getServiceNowITOMConnectorType()); + connectorTypeRegistry.register(getServiceNowSIRConnectorType()); + connectorTypeRegistry.register(getJiraConnectorType()); + connectorTypeRegistry.register(getResilientConnectorType()); + connectorTypeRegistry.register(getTeamsConnectorType()); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/extract_action_variable.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/extract_action_variable.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/extract_action_variable.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/extract_action_variable.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/rewrite_response_body.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/rewrite_response_body.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/rewrite_response_body.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/rewrite_response_body.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/test_utils.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/test_utils.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx index c1c5eaefaa42c..59a6d3808ff81 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/test_utils.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx @@ -6,7 +6,6 @@ */ import React, { useCallback } from 'react'; -import { ReactWrapper } from 'enzyme'; import { of } from 'rxjs'; import { I18nProvider } from '@kbn/i18n-react'; import { EuiButton } from '@elastic/eui'; @@ -16,12 +15,12 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { ConnectorServices } from '../../../types'; -import { TriggersAndActionsUiServices } from '../../..'; -import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; -import { ConnectorFormSchema } from '../../sections/action_connector_form/types'; -import { ConnectorFormFieldsGlobal } from '../../sections/action_connector_form/connector_form_fields_global'; -import { ConnectorProvider } from '../../context/connector_context'; +import { ConnectorServices } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { TriggersAndActionsUiServices } from '@kbn/triggers-actions-ui-plugin/public'; +import { createStartServicesMock } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana/kibana_react.mock'; +import { ConnectorFormSchema } from '@kbn/triggers-actions-ui-plugin/public/application/sections/action_connector_form/types'; +import { ConnectorFormFieldsGlobal } from '@kbn/triggers-actions-ui-plugin/public/application/sections/action_connector_form/connector_form_fields_global'; +import { ConnectorProvider } from '@kbn/triggers-actions-ui-plugin/public/application/context/connector_context'; interface FormTestProviderProps { children: React.ReactNode; @@ -81,16 +80,6 @@ const FormTestProviderComponent: React.FC = ({ ); }; -FormTestProviderComponent.displayName = 'FormTestProvider'; -export const FormTestProvider = React.memo(FormTestProviderComponent); - -export async function waitForComponentToPaint

(wrapper: ReactWrapper

, amount = 0) { - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, amount)); - wrapper.update(); - }); -} - export const waitForComponentToUpdate = async () => await act(async () => { return Promise.resolve(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/security/index.ts similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/security/index.ts index ee207a6d8e8a0..1fec1c76430eb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/security/index.ts @@ -4,5 +4,3 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -export { getActionType as getJiraActionType } from './jira'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/api.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/api.ts index 913bda49fe53d..3323d3527428e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/api.ts @@ -6,8 +6,8 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../../constants'; -import { EmailConfig } from '../types'; +import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../../../common'; +import { EmailConfig } from '../../types'; export async function getServiceConfig({ http, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.test.tsx similarity index 74% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.test.tsx index 9e61d84f5fb31..4e67bdabef993 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.test.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; +import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; import { getEmailServices } from './email'; import { ValidatedEmail, @@ -16,8 +16,8 @@ import { MustacheInEmailRegExp, } from '@kbn/actions-plugin/common'; -const ACTION_TYPE_ID = '.email'; -let actionTypeModel: ActionTypeModel; +const CONNECTOR_TYPE_ID = '.email'; +let connectorTypeModel: ConnectorTypeModel; const RegistrationServices = { validateEmailAddresses: validateEmails, @@ -45,18 +45,18 @@ beforeEach(() => { }); beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: RegistrationServices }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: RegistrationServices }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); if (getResult !== null) { - actionTypeModel = getResult; + connectorTypeModel = getResult; } }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('email'); +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('email'); }); }); @@ -82,7 +82,7 @@ describe('action params validation', () => { subject: 'test', }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { to: [], cc: [], @@ -101,7 +101,7 @@ describe('action params validation', () => { subject: 'test', }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { to: ['Email address invalid.com is not valid.'], cc: ['Email address bob@notallowed.com is not allowed.'], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.tsx similarity index 67% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.tsx index b44d13fb02ec1..73330de39be7e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email.tsx @@ -10,63 +10,48 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelectOption } from '@elastic/eui'; import { InvalidEmailReason } from '@kbn/actions-plugin/common'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { EmailActionParams, EmailConfig, EmailSecrets } from '../types'; -import { RegistrationServices } from '..'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { EmailActionParams, EmailConfig, EmailSecrets } from '../../types'; +import { RegistrationServices } from '../..'; const emailServices: EuiSelectOption[] = [ { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.gmailServerTypeLabel', - { - defaultMessage: 'Gmail', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.gmailServerTypeLabel', { + defaultMessage: 'Gmail', + }), value: 'gmail', }, { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.outlookServerTypeLabel', - { - defaultMessage: 'Outlook', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.outlookServerTypeLabel', { + defaultMessage: 'Outlook', + }), value: 'outlook365', }, { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.amazonSesServerTypeLabel', - { - defaultMessage: 'Amazon SES', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.amazonSesServerTypeLabel', { + defaultMessage: 'Amazon SES', + }), value: 'ses', }, { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.elasticCloudServerTypeLabel', - { - defaultMessage: 'Elastic Cloud', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.elasticCloudServerTypeLabel', { + defaultMessage: 'Elastic Cloud', + }), value: 'elastic_cloud', }, { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.exchangeServerTypeLabel', - { - defaultMessage: 'MS Exchange Server', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.exchangeServerTypeLabel', { + defaultMessage: 'MS Exchange Server', + }), value: 'exchange_server', }, { - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.otherServerTypeLabel', - { - defaultMessage: 'Other', - } - ), + text: i18n.translate('xpack.stackConnectors.components.email.otherServerTypeLabel', { + defaultMessage: 'Other', + }), value: 'other', }, ]; @@ -77,24 +62,18 @@ export function getEmailServices(isCloudEnabled: boolean) { : emailServices.filter((service) => service.value !== 'elastic_cloud'); } -export function getActionType( +export function getConnectorType( services: RegistrationServices -): ActionTypeModel { +): ConnectorTypeModel { return { id: '.email', iconClass: 'email', - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText', - { - defaultMessage: 'Send email from your server.', - } - ), - actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle', - { - defaultMessage: 'Send to email', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.email.selectMessageText', { + defaultMessage: 'Send email from your server.', + }), + actionTypeTitle: i18n.translate('xpack.stackConnectors.components.email.connectorTypeTitle', { + defaultMessage: 'Send to email', + }), validateParams: async ( actionParams: EmailActionParams ): Promise> => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.test.tsx index b06340c7a180d..ac9ba471b2442 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.test.tsx @@ -9,7 +9,7 @@ import React, { Suspense } from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import EmailActionConnectorFields from './email_connector'; import * as hooks from './use_email_config'; import { @@ -17,9 +17,9 @@ import { ConnectorFormTestProvider, createAppMockRenderer, waitForComponentToUpdate, -} from '../test_utils'; +} from '../../lib/test_utils'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; describe('EmailActionConnectorFields', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.tsx similarity index 94% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.tsx index 213d30ff4e5e4..502dc1d82dd1e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_connector.tsx @@ -11,7 +11,6 @@ import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiLink } from '@elastic/eui'; import { InvalidEmailReason } from '@kbn/actions-plugin/common'; -import { AdditionalEmailServices } from '@kbn/stack-connectors-plugin/common'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { @@ -26,13 +25,16 @@ import { TextField, ToggleField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { ActionConnectorFieldsProps } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + PasswordField, + useConnectorContext, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { AdditionalEmailServices } from '../../../../common'; import { getEmailServices } from './email'; import { useEmailConfig } from './use_email_config'; -import { PasswordField } from '../../password_field'; import * as i18n from './translations'; -import { useConnectorContext } from '../../../context/use_connector_context'; const { emptyField } = fieldValidators; @@ -50,7 +52,7 @@ const getEmailConfig = ( helpText: ( @@ -254,7 +256,7 @@ export const EmailActionConnectorFields: React.FunctionComponent

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_params.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_params.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_params.tsx similarity index 85% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_params.tsx index 0f894e011d3ea..9e5f1b06ad92b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/email_params.tsx @@ -9,10 +9,12 @@ import React, { useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiComboBox, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ActionParamsProps } from '../../../../types'; -import { EmailActionParams } from '../types'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { EmailActionParams } from '../../types'; export const EmailParamsFields = ({ actionParams, @@ -59,12 +61,9 @@ export const EmailParamsFields = ({ fullWidth error={errors.to} isInvalid={isToInvalid} - label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientTextFieldLabel', - { - defaultMessage: 'To', - } - )} + label={i18n.translate('xpack.stackConnectors.components.email.recipientTextFieldLabel', { + defaultMessage: 'To', + })} labelAppend={ <> @@ -72,7 +71,7 @@ export const EmailParamsFields = ({ setAddCC(true)}> ) : null} @@ -80,7 +79,7 @@ export const EmailParamsFields = ({ setAddBCC(true)}> ) : null} @@ -125,7 +124,7 @@ export const EmailParamsFields = ({ isInvalid={isCCInvalid} isDisabled={isDisabled} label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel', + 'xpack.stackConnectors.components.email.recipientCopyTextFieldLabel', { defaultMessage: 'Cc', } @@ -167,7 +166,7 @@ export const EmailParamsFields = ({ error={errors.bcc} isInvalid={isBCCInvalid} label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel', + 'xpack.stackConnectors.components.email.recipientBccTextFieldLabel', { defaultMessage: 'Bcc', } @@ -209,12 +208,9 @@ export const EmailParamsFields = ({ fullWidth error={errors.subject} isInvalid={isSubjectInvalid} - label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel', - { - defaultMessage: 'Subject', - } - )} + label={i18n.translate('xpack.stackConnectors.components.email.subjectTextFieldLabel', { + defaultMessage: 'Subject', + })} > { const actionConnector = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/exchange_form.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/exchange_form.tsx index 41f82b3a32bfc..c38eebcf44fc1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/exchange_form.tsx @@ -11,9 +11,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { useKibana } from '../../../../common/lib/kibana'; +import { PasswordField, useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; -import { PasswordField } from '../../password_field'; const { emptyField } = fieldValidators; @@ -36,7 +35,7 @@ const ExchangeFormFields: React.FC = ({ readOnly }) => helpText: ( @@ -63,7 +62,7 @@ const ExchangeFormFields: React.FC = ({ readOnly }) => helpText: ( @@ -94,7 +93,7 @@ const ExchangeFormFields: React.FC = ({ readOnly }) => target="_blank" > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/index.ts similarity index 78% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/index.ts index 2990773856ac4..42fa6d59fefd3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getActionType as getSlackActionType } from './slack'; +export { getConnectorType as getEmailConnectorType } from './email'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/translations.ts similarity index 54% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/translations.ts index 65fc2bdb542e8..c21418f349096 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/translations.ts @@ -8,171 +8,168 @@ import { i18n } from '@kbn/i18n'; export const USERNAME_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel', + 'xpack.stackConnectors.components.email.userTextFieldLabel', { defaultMessage: 'Username', } ); export const PASSWORD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel', + 'xpack.stackConnectors.components.email.passwordFieldLabel', { defaultMessage: 'Password', } ); export const FROM_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.fromTextFieldLabel', + 'xpack.stackConnectors.components.email.fromTextFieldLabel', { defaultMessage: 'Sender', } ); export const SERVICE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.serviceTextFieldLabel', + 'xpack.stackConnectors.components.email.serviceTextFieldLabel', { defaultMessage: 'Service', } ); export const TENANT_ID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.tenantIdFieldLabel', + 'xpack.stackConnectors.components.email.tenantIdFieldLabel', { defaultMessage: 'Tenant ID', } ); export const CLIENT_ID_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientIdFieldLabel', + 'xpack.stackConnectors.components.email.clientIdFieldLabel', { defaultMessage: 'Client ID', } ); export const CLIENT_SECRET_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientSecretTextFieldLabel', + 'xpack.stackConnectors.components.email.clientSecretTextFieldLabel', { defaultMessage: 'Client Secret', } ); export const HOST_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hostTextFieldLabel', + 'xpack.stackConnectors.components.email.hostTextFieldLabel', { defaultMessage: 'Host', } ); export const PORT_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.portTextFieldLabel', + 'xpack.stackConnectors.components.email.portTextFieldLabel', { defaultMessage: 'Port', } ); export const SECURE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.secureSwitchLabel', + 'xpack.stackConnectors.components.email.secureSwitchLabel', { defaultMessage: 'Secure', } ); export const HAS_AUTH_LABEL = i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hasAuthSwitchLabel', + 'xpack.stackConnectors.components.email.hasAuthSwitchLabel', { defaultMessage: 'Require authentication for this server', } ); export const SENDER_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText', + 'xpack.stackConnectors.components.email.error.requiredFromText', { defaultMessage: 'Sender is required.', } ); export const CLIENT_ID_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredClientIdText', + 'xpack.stackConnectors.components.email.error.requiredClientIdText', { defaultMessage: 'Client ID is required.', } ); export const TENANT_ID_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredTenantIdText', + 'xpack.stackConnectors.components.email.error.requiredTenantIdText', { defaultMessage: 'Tenant ID is required.', } ); export const PORT_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText', + 'xpack.stackConnectors.components.email.error.requiredPortText', { defaultMessage: 'Port is required.', } ); export const PORT_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.invalidPortText', + 'xpack.stackConnectors.components.email.error.invalidPortText', { defaultMessage: 'Port is invalid.', } ); export const SERVICE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText', + 'xpack.stackConnectors.components.email.error.requiredServiceText', { defaultMessage: 'Service is required.', } ); export const HOST_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText', + 'xpack.stackConnectors.components.email.error.requiredHostText', { defaultMessage: 'Host is required.', } ); export const USERNAME_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthUserNameText', + 'xpack.stackConnectors.components.email.error.requiredAuthUserNameText', { defaultMessage: 'Username is required.', } ); export const TO_CC_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText', + 'xpack.stackConnectors.components.email.error.requiredEntryText', { defaultMessage: 'No To, Cc, or Bcc entry. At least one entry is required.', } ); export const MESSAGE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText', + 'xpack.stackConnectors.components.email.error.requiredMessageText', { defaultMessage: 'Message is required.', } ); export const SUBJECT_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText', + 'xpack.stackConnectors.components.email.error.requiredSubjectText', { defaultMessage: 'Subject is required.', } ); export function getInvalidEmailAddress(email: string) { - return i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.invalidEmail', - { - defaultMessage: 'Email address {email} is not valid.', - values: { email }, - } - ); + return i18n.translate('xpack.stackConnectors.components.email.error.invalidEmail', { + defaultMessage: 'Email address {email} is not valid.', + values: { email }, + }); } export function getNotAllowedEmailAddress(email: string) { - return i18n.translate('xpack.triggersActionsUI.components.builtinActionTypes.error.notAllowed', { + return i18n.translate('xpack.stackConnectors.components.email.error.notAllowed', { defaultMessage: 'Email address {email} is not allowed.', values: { email }, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/use_email_config.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/use_email_config.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/use_email_config.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/email/use_email_config.ts index fc0221227783d..c93715e34ef84 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/email/use_email_config.ts @@ -8,9 +8,9 @@ import { isEmpty } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { AdditionalEmailServices } from '@kbn/stack-connectors-plugin/common'; import { i18n } from '@kbn/i18n'; -import { EmailConfig } from '../types'; +import { AdditionalEmailServices } from '../../../../common'; +import { EmailConfig } from '../../types'; import { getServiceConfig } from './api'; interface Props { @@ -73,7 +73,7 @@ export function useEmailConfig({ http, toasts }: Props): UseEmailConfigReturnVal toasts.addDanger( error.body?.message ?? i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.updateErrorNotificationText', + 'xpack.stackConnectors.components.email.updateErrorNotificationText', { defaultMessage: 'Cannot get service configuration' } ) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.test.tsx similarity index 61% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.test.tsx index ccf90cb91d837..f2e927fa42f33 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.test.tsx @@ -5,34 +5,34 @@ * 2.0. */ -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; +import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; -const ACTION_TYPE_ID = '.index'; -let actionTypeModel: ActionTypeModel; +const CONNECTOR_TYPE_ID = '.index'; +let connectorTypeModel: ConnectorTypeModel; beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); if (getResult !== null) { - actionTypeModel = getResult; + connectorTypeModel = getResult; } }); -describe('actionTypeRegistry.get() works', () => { - test('action type .index is registered', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('indexOpen'); +describe('connectorTypeRegistry.get() works', () => { + test('connector type .index is registered', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('indexOpen'); }); }); describe('action params validation', () => { test('action params validation succeeds when action params are valid', async () => { expect( - await actionTypeModel.validateParams({ + await connectorTypeModel.validateParams({ documents: [{ test: 1234 }], }) ).toEqual({ @@ -43,7 +43,7 @@ describe('action params validation', () => { }); expect( - await actionTypeModel.validateParams({ + await connectorTypeModel.validateParams({ documents: [{ test: 1234 }], indexOverride: 'kibana-alert-history-anything', }) @@ -56,7 +56,7 @@ describe('action params validation', () => { }); test('action params validation fails when action params are invalid', async () => { - expect(await actionTypeModel.validateParams({})).toEqual({ + expect(await connectorTypeModel.validateParams({})).toEqual({ errors: { documents: ['Document is required and should be a valid JSON object.'], indexOverride: [], @@ -64,7 +64,7 @@ describe('action params validation', () => { }); expect( - await actionTypeModel.validateParams({ + await connectorTypeModel.validateParams({ documents: [{}], }) ).toEqual({ @@ -75,7 +75,7 @@ describe('action params validation', () => { }); expect( - await actionTypeModel.validateParams({ + await connectorTypeModel.validateParams({ documents: [{}], indexOverride: 'kibana-alert-history-', }) @@ -87,7 +87,7 @@ describe('action params validation', () => { }); expect( - await actionTypeModel.validateParams({ + await connectorTypeModel.validateParams({ documents: [{}], indexOverride: 'this.is-a_string', }) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.tsx similarity index 59% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.tsx index 75666c1282da3..8265af8a74efb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index.tsx @@ -7,25 +7,23 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult, ALERT_HISTORY_PREFIX } from '../../../../types'; -import { EsIndexConfig, IndexActionParams } from '../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { ALERT_HISTORY_PREFIX } from '@kbn/triggers-actions-ui-plugin/public'; +import { EsIndexConfig, IndexActionParams } from '../../types'; -export function getActionType(): ActionTypeModel { +export function getConnectorType(): ConnectorTypeModel { return { id: '.index', iconClass: 'indexOpen', - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText', - { - defaultMessage: 'Index data into Elasticsearch.', - } - ), - actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.actionTypeTitle', - { - defaultMessage: 'Index data', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.index.selectMessageText', { + defaultMessage: 'Index data into Elasticsearch.', + }), + actionTypeTitle: i18n.translate('xpack.stackConnectors.components.index.connectorTypeTitle', { + defaultMessage: 'Index data', + }), actionConnectorFields: lazy(() => import('./es_index_connector')), actionParamsFields: lazy(() => import('./es_index_params')), validateParams: async ( @@ -43,13 +41,10 @@ export function getActionType(): ActionTypeModel { const module = jest.requireActual('lodash'); @@ -24,7 +28,7 @@ jest.mock('lodash', () => { }; }); -jest.mock('../../../../common/index_controls', () => ({ +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/index_controls', () => ({ firstFieldOption: { text: 'Select a field', value: '', @@ -33,7 +37,9 @@ jest.mock('../../../../common/index_controls', () => ({ getIndexOptions: jest.fn(), })); -const { getIndexOptions } = jest.requireMock('../../../../common/index_controls'); +const { getIndexOptions } = jest.requireMock( + '@kbn/triggers-actions-ui-plugin/public/common/index_controls' +); getIndexOptions.mockResolvedValueOnce([ { @@ -45,7 +51,9 @@ getIndexOptions.mockResolvedValueOnce([ }, ]); -const { getFields } = jest.requireMock('../../../../common/index_controls'); +const { getFields } = jest.requireMock( + '@kbn/triggers-actions-ui-plugin/public/common/index_controls' +); async function setup(actionConnector: any) { const wrapper = mountWithIntl( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_connector.tsx similarity index 87% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_connector.tsx index 12fb79bd2845a..ec090c3c004d1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_connector.tsx @@ -29,10 +29,14 @@ import { ToggleField, SelectField } from '@kbn/es-ui-shared-plugin/static/forms/ import { DocLinksStart } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { ActionConnectorFieldsProps } from '../../../../types'; -import { getTimeFieldOptions } from '../../../../common/lib/get_time_options'; -import { firstFieldOption, getFields, getIndexOptions } from '../../../../common/index_controls'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + firstFieldOption, + getFields, + getIndexOptions, + getTimeFieldOptions, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; import * as translations from './translations'; interface TimeFieldOptions { @@ -47,13 +51,13 @@ const getIndexConfig = (docLinks: DocLinksStart): FieldConfig => ({ helpText: ( <> @@ -123,7 +127,7 @@ const IndexActionConnectorFields: React.FunctionComponent @@ -159,7 +163,7 @@ const IndexActionConnectorFields: React.FunctionComponent } @@ -168,13 +172,13 @@ const IndexActionConnectorFields: React.FunctionComponent @@ -221,7 +225,7 @@ const IndexActionConnectorFields: React.FunctionComponent {' '} { const original = jest.requireActual(kibanaReactPath); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_params.tsx similarity index 84% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_params.tsx index 0601d0a9b1284..7c6e5bc97cfb1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/es_index_params.tsx @@ -17,16 +17,16 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; import { - ActionParamsProps, AlertHistoryEsIndexConnectorId, AlertHistoryDocumentTemplate, AlertHistoryDefaultIndexName, ALERT_HISTORY_PREFIX, -} from '../../../../types'; -import { IndexActionParams } from '../types'; -import { JsonEditorWithMessageVariables } from '../../json_editor_with_message_variables'; -import { useKibana } from '../../../../common/lib/kibana'; + JsonEditorWithMessageVariables, + useKibana, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { IndexActionParams } from '../../types'; export const IndexParamsFields = ({ actionParams, @@ -90,7 +90,7 @@ export const IndexParamsFields = ({ }; const documentsFieldLabel = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', + 'xpack.stackConnectors.components.index.documentsFieldLabel', { defaultMessage: 'Document to index', } @@ -108,7 +108,7 @@ export const IndexParamsFields = ({ > @@ -127,17 +127,14 @@ export const IndexParamsFields = ({ (errors.indexOverride as string[]) && errors.indexOverride.length > 0 } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndex', - { - defaultMessage: 'Elasticsearch index', - } - )} + label={i18n.translate('xpack.stackConnectors.components.index.preconfiguredIndex', { + defaultMessage: 'Elasticsearch index', + })} labelAppend={resetDefaultIndex} helpText={ <> @@ -146,7 +143,7 @@ export const IndexParamsFields = ({ target="_blank" > @@ -189,18 +186,15 @@ export const IndexParamsFields = ({ : documentToIndex } label={documentsFieldLabel} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel', - { - defaultMessage: 'Code editor', - } - )} + aria-label={i18n.translate('xpack.stackConnectors.components.index.jsonDocAriaLabel', { + defaultMessage: 'Code editor', + })} errors={errors.documents as string[]} onDocumentsChange={onDocumentsChange} helpText={ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/index.ts similarity index 77% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/index.ts index 63e1475a115fd..f3c7daa86fbf6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getActionType as getCasesWebhookActionType } from './webhook'; +export { getConnectorType as getIndexConnectorType } from './es_index'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/translations.ts similarity index 64% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/translations.ts index d86824fd1813f..3153d84182040 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/es_index/translations.ts @@ -8,49 +8,49 @@ import { i18n } from '@kbn/i18n'; export const INDEX_IS_NOT_VALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.error.notValidIndexText', + 'xpack.stackConnectors.components.index.error.notValidIndexText', { defaultMessage: 'Index is not valid.', } ); export const DOCUMENT_NOT_VALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredDocumentJson', + 'xpack.stackConnectors.components.index.error.requiredDocumentJson', { defaultMessage: 'Document is required and should be a valid JSON object.', } ); export const HISTORY_NOT_VALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideSuffix', + 'xpack.stackConnectors.components.index.error.badIndexOverrideSuffix', { defaultMessage: 'Alert history index must contain valid suffix.', } ); export const EXECUTION_TIME_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.executionTimeFieldLabel', + 'xpack.stackConnectors.components.index.executionTimeFieldLabel', { defaultMessage: 'Time field', } ); export const SHOW_TIME_FIELD_TOGGLE_TOOLTIP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip', + 'xpack.stackConnectors.components.index.definedateFieldTooltip', { defaultMessage: `Set this time field to the time the document was indexed.`, } ); export const REFRESH_FIELD_TOGGLE_TOOLTIP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip', + 'xpack.stackConnectors.components.index.refreshTooltip', { defaultMessage: 'Refresh the affected shards to make this operation visible to search.', } ); export const INDEX_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesToQueryLabel', + 'xpack.stackConnectors.components.index.indicesToQueryLabel', { defaultMessage: 'Index', } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/index.ts new file mode 100644 index 0000000000000..93d444d20204d --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/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. + */ + +export { getEmailConnectorType } from './email'; +export { getIndexConnectorType } from './es_index'; +export { getPagerDutyConnectorType } from './pagerduty'; +export { getServerLogConnectorType } from './server_log'; +export { getSlackConnectorType } from './slack'; +export { getTeamsConnectorType } from './teams'; +export { getWebhookConnectorType } from './webhook'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/index.ts new file mode 100644 index 0000000000000..48ee4b5288ec8 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/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 { getConnectorType as getPagerDutyConnectorType } from './pagerduty'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/logo.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/logo.tsx index ab991651a8a52..9383a4cf5d59f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); if (getResult !== null) { - actionTypeModel = getResult; + connectorTypeModel = getResult; } }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.actionTypeTitle).toEqual('Send to PagerDuty'); +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.actionTypeTitle).toEqual('Send to PagerDuty'); }); }); @@ -43,7 +43,7 @@ describe('pagerduty action params validation', () => { class: 'test class', }; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { dedupKey: [], summary: [], @@ -67,7 +67,7 @@ describe('pagerduty action params validation', () => { const expected = [expect.stringMatching(/^Timestamp must be a valid date/)]; - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { dedupKey: [], summary: [], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty.tsx similarity index 71% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty.tsx index 8c3961e6d7116..edb1e9b71b848 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty.tsx @@ -8,17 +8,22 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { + AlertProvidedActionVariables, + hasMustacheTokens, +} from '@kbn/triggers-actions-ui-plugin/public'; import { PagerDutyConfig, PagerDutySecrets, PagerDutyActionParams, EventActionOptions, -} from '../types'; -import { hasMustacheTokens } from '../../../lib/has_mustache_tokens'; -import { AlertProvidedActionVariables } from '../../../lib/action_variables'; +} from '../../types'; -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< PagerDutyConfig, PagerDutySecrets, PagerDutyActionParams @@ -26,14 +31,11 @@ export function getActionType(): ActionTypeModel< return { id: '.pagerduty', iconClass: lazy(() => import('./logo')), - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText', - { - defaultMessage: 'Send an event in PagerDuty.', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.pagerDuty.selectMessageText', { + defaultMessage: 'Send an event in PagerDuty.', + }), actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle', + 'xpack.stackConnectors.components.pagerDuty.connectorTypeTitle', { defaultMessage: 'Send to PagerDuty', } @@ -66,17 +68,14 @@ export function getActionType(): ActionTypeModel< if (!moment(actionParams.timestamp).isValid()) { const { nowShortFormat, nowLongFormat } = getValidTimestampExamples(); errors.timestamp.push( - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestamp', - { - defaultMessage: - 'Timestamp must be a valid date, such as {nowShortFormat} or {nowLongFormat}.', - values: { - nowShortFormat, - nowLongFormat, - }, - } - ) + i18n.translate('xpack.stackConnectors.components.pagerDuty.error.invalidTimestamp', { + defaultMessage: + 'Timestamp must be a valid date, such as {nowShortFormat} or {nowLongFormat}.', + values: { + nowShortFormat, + nowLongFormat, + }, + }) ); } } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.test.tsx similarity index 97% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.test.tsx index 1ccd7fba702f6..184ae124cbc04 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.test.tsx @@ -9,11 +9,11 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import PagerDutyActionConnectorFields from './pagerduty_connectors'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('PagerDutyActionConnectorFields renders', () => { test('all connector fields is rendered', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.tsx similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.tsx index 21f7443b6b545..13139306ea781 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_connectors.tsx @@ -13,8 +13,8 @@ import { FieldConfig, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hoo import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { DocLinksStart } from '@kbn/core/public'; -import { ActionConnectorFieldsProps } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; const { emptyField, urlField } = fieldValidators; @@ -44,7 +44,7 @@ const getRoutingKeyConfig = (docLinks: DocLinksStart): FieldConfig => ({ helpText: ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.test.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.test.tsx index 19f47166b2726..7f2933587ebca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { EventActionOptions, SeverityActionOptions } from '../types'; +import { EventActionOptions, SeverityActionOptions } from '../../types'; import PagerDutyParamsFields from './pagerduty_params'; describe('PagerDutyParamsFields renders', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.tsx similarity index 76% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.tsx index 738f1ca74b1a6..f9f9921d84ceb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/pagerduty/pagerduty_params.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isUndefined } from 'lodash'; -import { ActionParamsProps } from '../../../../types'; -import { PagerDutyActionParams } from '../types'; -import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextFieldWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { PagerDutyActionParams } from '../../types'; const PagerDutyParamsFields: React.FunctionComponent> = ({ actionParams, @@ -26,7 +26,7 @@ const PagerDutyParamsFields: React.FunctionComponent { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('logsApp'); + }); +}); + +describe('action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + message: 'test message', + level: 'trace', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { message: [] }, + }); + }); + + test('params validation fails when message is not valid', async () => { + const actionParams = { + message: '', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + message: ['Message is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log.tsx similarity index 63% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log.tsx index 4fe024f8861f0..b1dadf0bf2ddc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log.tsx @@ -7,21 +7,21 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { ServerLogActionParams } from '../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { ServerLogActionParams } from '../../types'; -export function getActionType(): ActionTypeModel { +export function getConnectorType(): ConnectorTypeModel { return { id: '.server-log', iconClass: 'logsApp', - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText', - { - defaultMessage: 'Add a message to a Kibana log.', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.serverLog.selectMessageText', { + defaultMessage: 'Add a message to a Kibana log.', + }), actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle', + 'xpack.stackConnectors.components.serverLog.connectorTypeTitle', { defaultMessage: 'Send to Server log', } @@ -36,7 +36,7 @@ export function getActionType(): ActionTypeModel { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log_params.tsx similarity index 79% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log_params.tsx index b8f5b84eb471e..d923e1165c3f1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/server_log/server_log_params.tsx @@ -8,9 +8,9 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelect, EuiFormRow } from '@elastic/eui'; -import { ActionParamsProps } from '../../../../types'; -import { ServerLogActionParams } from '../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextAreaWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { ServerLogActionParams } from '../../types'; export const ServerLogParamsFields: React.FunctionComponent< ActionParamsProps @@ -52,12 +52,9 @@ export const ServerLogParamsFields: React.FunctionComponent< diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/index.ts similarity index 78% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/index.ts index a1ca62d0cc862..05d27afff76fb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getActionType as getEmailActionType } from './email'; +export { getConnectorType as getSlackConnectorType } from './slack'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.test.tsx new file mode 100644 index 0000000000000..dc1f27ec3998b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.test.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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; + +const CONNECTOR_TYPE_ID = '.slack'; +let connectorTypeModel: ConnectorTypeModel; + +beforeAll(async () => { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('logoSlack'); + }); +}); + +describe('slack action params validation', () => { + test('if action params validation succeeds when action params is valid', async () => { + const actionParams = { + message: 'message {test}', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { message: [] }, + }); + }); + + test('params validation fails when message is not valid', async () => { + const actionParams = { + message: '', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + message: ['Message is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.tsx similarity index 59% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.tsx index 2252677084ba6..a3a800b2036a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack.tsx @@ -7,25 +7,22 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { SlackActionParams, SlackSecrets } from '../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { SlackActionParams, SlackSecrets } from '../../types'; -export function getActionType(): ActionTypeModel { +export function getConnectorType(): ConnectorTypeModel { return { id: '.slack', iconClass: 'logoSlack', - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText', - { - defaultMessage: 'Send a message to a Slack channel or user.', - } - ), - actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle', - { - defaultMessage: 'Send to Slack', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.slack.selectMessageText', { + defaultMessage: 'Send a message to a Slack channel or user.', + }), + actionTypeTitle: i18n.translate('xpack.stackConnectors.components.slack.connectorTypeTitle', { + defaultMessage: 'Send to Slack', + }), validateParams: async ( actionParams: SlackActionParams ): Promise> => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.test.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.test.tsx index 640bef0c22685..ac381ba07e3b0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.test.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act, render } from '@testing-library/react'; import SlackActionFields from './slack_connectors'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('SlackActionFields renders', () => { test('all connector fields is rendered', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.tsx index 0725096c5a719..188b8912fc390 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_connectors.tsx @@ -12,9 +12,8 @@ import { FieldConfig, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hoo import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { DocLinksStart } from '@kbn/core/public'; - -import { ActionConnectorFieldsProps } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; const { urlField } = fieldValidators; @@ -24,7 +23,7 @@ const getWebhookUrlConfig = (docLinks: DocLinksStart): FieldConfig => ({ helpText: ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_params.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_params.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_params.tsx similarity index 79% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_params.tsx index 59e10277cfe08..d5cd699caaae5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/slack_params.tsx @@ -7,9 +7,9 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionParamsProps } from '../../../../types'; -import { SlackActionParams } from '../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextAreaWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { SlackActionParams } from '../../types'; const SlackParamsFields: React.FunctionComponent> = ({ actionParams, @@ -43,12 +43,9 @@ const SlackParamsFields: React.FunctionComponent ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/translations.ts similarity index 67% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/slack/translations.ts index e7d37082b53ff..7caed9ca07237 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/slack/translations.ts @@ -8,21 +8,21 @@ import { i18n } from '@kbn/i18n'; export const WEBHOOK_URL_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.invalidWebhookUrlText', + 'xpack.stackConnectors.components.slack.error.invalidWebhookUrlText', { defaultMessage: 'Webhook URL is invalid.', } ); export const MESSAGE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText', + 'xpack.stackConnectors.components.slack..error.requiredSlackMessageText', { defaultMessage: 'Message is required.', } ); export const WEBHOOK_URL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel', + 'xpack.stackConnectors.components.slack.webhookUrlTextFieldLabel', { defaultMessage: 'Webhook URL', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/index.ts similarity index 78% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/index.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/index.ts index 419fb069aa8c7..a64d07770b4dd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getActionType as getIndexActionType } from './es_index'; +export { getConnectorType as getTeamsConnectorType } from './teams'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/logo.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/logo.tsx index 666cb8d854032..a7740ece4323f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/logo.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { LogoProps } from '../types'; +import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + }); +}); + +describe('teams action params validation', () => { + test('if action params validation succeeds when action params is valid', async () => { + const actionParams = { + message: 'message {test}', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { message: [] }, + }); + }); + + test('params validation fails when message is not valid', async () => { + const actionParams = { + message: '', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + message: ['Message is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams.tsx similarity index 59% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams.tsx index e9c286cdc1b56..560d647253a5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams.tsx @@ -7,25 +7,22 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { TeamsActionParams, TeamsSecrets } from '../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { TeamsActionParams, TeamsSecrets } from '../../types'; -export function getActionType(): ActionTypeModel { +export function getConnectorType(): ConnectorTypeModel { return { id: '.teams', iconClass: lazy(() => import('./logo')), - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.selectMessageText', - { - defaultMessage: 'Send a message to a Microsoft Teams channel.', - } - ), - actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.actionTypeTitle', - { - defaultMessage: 'Send a message to a Microsoft Teams channel.', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.teams.selectMessageText', { + defaultMessage: 'Send a message to a Microsoft Teams channel.', + }), + actionTypeTitle: i18n.translate('xpack.stackConnectors.components.teams.connectorTypeTitle', { + defaultMessage: 'Send a message to a Microsoft Teams channel.', + }), validateParams: async ( actionParams: TeamsActionParams ): Promise> => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.test.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.test.tsx index a0a082d36a864..37078d7efd115 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act, render } from '@testing-library/react'; import TeamsActionFields from './teams_connectors'; -import { ConnectorFormTestProvider } from '../test_utils'; +import { ConnectorFormTestProvider } from '../../lib/test_utils'; import userEvent from '@testing-library/user-event'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('TeamsActionFields renders', () => { test('all connector fields are rendered', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.tsx index 34e2e02a0611c..7d989b9b04c6e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_connectors.tsx @@ -12,8 +12,8 @@ import { FieldConfig, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hoo import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { DocLinksStart } from '@kbn/core/public'; -import { ActionConnectorFieldsProps } from '../../../../types'; -import { useKibana } from '../../../../common/lib/kibana'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; const { urlField } = fieldValidators; @@ -23,7 +23,7 @@ const getWebhookUrlConfig = (docLinks: DocLinksStart): FieldConfig => ({ helpText: ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.test.tsx similarity index 93% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.test.tsx index cf0bfe9db0e97..ac1228ac5fda4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import TeamsParamsFields from './teams_params'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('TeamsParamsFields renders', () => { test('all params fields is rendered', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.tsx similarity index 74% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.tsx index 0aea576c10b31..5a4c9e85f583b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/teams_params.tsx @@ -7,9 +7,9 @@ import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionParamsProps } from '../../../../types'; -import { TeamsActionParams } from '../types'; -import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextAreaWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { TeamsActionParams } from '../../types'; const TeamsParamsFields: React.FunctionComponent> = ({ actionParams, @@ -34,12 +34,9 @@ const TeamsParamsFields: React.FunctionComponent ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/translations.ts similarity index 67% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/teams/translations.ts index 2bf4cae881f7b..539e0867dc97c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/teams/translations.ts @@ -8,21 +8,21 @@ import { i18n } from '@kbn/i18n'; export const WEBHOOK_URL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.webhookUrlTextLabel', + 'xpack.stackConnectors.components.teams.error.webhookUrlTextLabel', { defaultMessage: 'Webhook URL', } ); export const WEBHOOK_URL_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.invalidWebhookUrlText', + 'xpack.stackConnectors.components.teams.error.invalidWebhookUrlText', { defaultMessage: 'Webhook URL is invalid.', } ); export const MESSAGE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requiredMessageText', + 'xpack.stackConnectors.components.teams.error.requiredMessageText', { defaultMessage: 'Message is required.', } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/index.ts new file mode 100644 index 0000000000000..dd5934986c846 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/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 { getConnectorType as getWebhookConnectorType } from './webhook'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/translations.ts similarity index 55% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/translations.ts rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/translations.ts index 27a7d08b8c767..7095c91729f91 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/translations.ts @@ -8,98 +8,98 @@ import { i18n } from '@kbn/i18n'; export const METHOD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.methodTextFieldLabel', + 'xpack.stackConnectors.components.webhook.methodTextFieldLabel', { defaultMessage: 'Method', } ); export const HAS_AUTH_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.hasAuthSwitchLabel', + 'xpack.stackConnectors.components.webhook.hasAuthSwitchLabel', { defaultMessage: 'Require authentication for this webhook', } ); export const URL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.urlTextFieldLabel', + 'xpack.stackConnectors.components.webhook.urlTextFieldLabel', { defaultMessage: 'URL', } ); export const USERNAME_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.userTextFieldLabel', + 'xpack.stackConnectors.components.webhook.userTextFieldLabel', { defaultMessage: 'Username', } ); export const PASSWORD_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.passwordTextFieldLabel', + 'xpack.stackConnectors.components.webhook.passwordTextFieldLabel', { defaultMessage: 'Password', } ); export const ADD_HEADERS_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch', + 'xpack.stackConnectors.components.webhook.viewHeadersSwitch', { defaultMessage: 'Add HTTP header', } ); export const HEADER_KEY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerKeyTextFieldLabel', + 'xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel', { defaultMessage: 'Key', } ); export const REMOVE_ITEM_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.removeHeaderIconLabel', + 'xpack.stackConnectors.components.webhook.removeHeaderIconLabel', { defaultMessage: 'Key', } ); export const ADD_HEADER_BTN = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButtonLabel', + 'xpack.stackConnectors.components.webhook.addHeaderButtonLabel', { defaultMessage: 'Add header', } ); export const HEADER_VALUE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerValueTextFieldLabel', + 'xpack.stackConnectors.components.webhook.headerValueTextFieldLabel', { defaultMessage: 'Value', } ); export const URL_INVALID = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.invalidUrlTextField', + 'xpack.stackConnectors.components.webhook.error.invalidUrlTextField', { defaultMessage: 'URL is invalid.', } ); export const METHOD_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText', + 'xpack.stackConnectors.components.webhook.error.requiredMethodText', { defaultMessage: 'Method is required.', } ); export const USERNAME_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthUserNameText', + 'xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText', { defaultMessage: 'Username is required.', } ); export const BODY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText', + 'xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText', { defaultMessage: 'Body is required.', } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.test.tsx new file mode 100644 index 0000000000000..d24e1e865e17f --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.test.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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '../..'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registrationServicesMock } from '../../../mocks'; + +const CONNECTOR_TYPE_ID = '.webhook'; +let connectorTypeModel: ConnectorTypeModel; + +beforeAll(() => { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get() works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + expect(connectorTypeModel.iconClass).toEqual('logoWebhook'); + }); +}); + +describe('webhook action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + body: 'message {test}', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { body: [] }, + }); + }); + + test('params validation fails when body is not valid', async () => { + const actionParams = { + body: '', + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + body: ['Body is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.tsx similarity index 67% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.tsx index 5ee08cc027003..9740038f85a66 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook.tsx @@ -7,10 +7,13 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionTypeModel, GenericValidationResult } from '../../../../types'; -import { WebhookActionParams, WebhookConfig, WebhookSecrets } from '../types'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { WebhookActionParams, WebhookConfig, WebhookSecrets } from '../../types'; -export function getActionType(): ActionTypeModel< +export function getConnectorType(): ConnectorTypeModel< WebhookConfig, WebhookSecrets, WebhookActionParams @@ -18,18 +21,12 @@ export function getActionType(): ActionTypeModel< return { id: '.webhook', iconClass: 'logoWebhook', - selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.selectMessageText', - { - defaultMessage: 'Send a request to a web service.', - } - ), - actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle', - { - defaultMessage: 'Webhook data', - } - ), + selectMessage: i18n.translate('xpack.stackConnectors.components.webhook.selectMessageText', { + defaultMessage: 'Send a request to a web service.', + }), + actionTypeTitle: i18n.translate('xpack.stackConnectors.components.webhook.connectorTypeTitle', { + defaultMessage: 'Webhook data', + }), validateParams: async ( actionParams: WebhookActionParams ): Promise> => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.test.tsx similarity index 99% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.test.tsx index d3b933e9e2dc4..8744f126e1541 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import WebhookActionConnectorFields from './webhook_connectors'; -import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../test_utils'; +import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../../lib/test_utils'; import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.tsx index 7981f8fa4fa78..233d2eea4b117 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_connectors.tsx @@ -29,9 +29,9 @@ import { ToggleField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import { ActionConnectorFieldsProps } from '../../../../types'; +import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { PasswordField } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; -import { PasswordField } from '../../password_field'; const HTTP_VERBS = ['post', 'put']; const { emptyField, urlField } = fieldValidators; @@ -99,7 +99,7 @@ const WebhookActionConnectorFields: React.FunctionComponent

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.test.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.test.tsx index 064d21b50e463..6cf29adfe89bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.test.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import WebhookParamsFields from './webhook_params'; -import { MockCodeEditor } from '../../../code_editor.mock'; +import { MockCodeEditor } from '@kbn/triggers-actions-ui-plugin/public/application/code_editor.mock'; -const kibanaReactPath = '../../../../../../../../src/plugins/kibana_react/public'; +const kibanaReactPath = '../../../../../../../src/plugins/kibana_react/public'; jest.mock(kibanaReactPath, () => { const original = jest.requireActual(kibanaReactPath); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.tsx similarity index 69% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx rename to x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.tsx index 2eab79b14f53a..4a48027e8153a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/webhook/webhook_params.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionParamsProps } from '../../../../types'; -import { WebhookActionParams } from '../types'; -import { JsonEditorWithMessageVariables } from '../../json_editor_with_message_variables'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { JsonEditorWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { WebhookActionParams } from '../../types'; const WebhookParamsFields: React.FunctionComponent> = ({ actionParams, @@ -24,14 +24,11 @@ const WebhookParamsFields: React.FunctionComponent new StackConnectorsPublicPlugin(); diff --git a/x-pack/plugins/stack_connectors/public/mocks.ts b/x-pack/plugins/stack_connectors/public/mocks.ts new file mode 100644 index 0000000000000..9e087c3cee6eb --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/mocks.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. + */ + +import { ValidatedEmail } from '@kbn/actions-plugin/common'; +import { RegistrationServices } from './connector_types'; + +function validateEmailAddresses(addresses: string[]): ValidatedEmail[] { + return addresses.map((address) => ({ address, valid: true })); +} + +export const registrationServicesMock: RegistrationServices = { validateEmailAddresses }; diff --git a/x-pack/plugins/stack_connectors/public/plugin.ts b/x-pack/plugins/stack_connectors/public/plugin.ts new file mode 100644 index 0000000000000..bc9d855a14303 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/plugin.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup, Plugin } from '@kbn/core/public'; +import { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public'; +import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; +import { registerConnectorTypes } from './connector_types'; + +export type Setup = void; +export type Start = void; + +export interface StackConnectorsPublicSetupDeps { + triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; + actions: ActionsPublicPluginSetup; +} + +export class StackConnectorsPublicPlugin + implements Plugin +{ + public setup(core: CoreSetup, { triggersActionsUi, actions }: StackConnectorsPublicSetupDeps) { + registerConnectorTypes({ + connectorTypeRegistry: triggersActionsUi.actionTypeRegistry, + services: { + validateEmailAddresses: actions.validateEmailAddresses, + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index 395dc5a65be86..1cf8281670d0a 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -10,10 +10,12 @@ "server/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "server/**/*.json", - "common/**/*" + "common/**/*", + "public/**/*" ], "references": [ { "path": "../../../src/core/tsconfig.json" }, - { "path": "../actions/tsconfig.json" } + { "path": "../actions/tsconfig.json" }, + { "path": "../triggers_actions_ui/tsconfig.json" } ] } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 04f32940b161a..b2984037b0302 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6377,6 +6377,380 @@ "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "secretsUrl ne doit pas être fournie lorsque usesBasic est vrai", "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "Le nom d'utilisateur et le mot de passe ne doivent pas être fournis lorsque usesBasic est faux", "xpack.stackConnectors.xmatters.title": "xMatters", + "xpack.stackConnectors.components.casesWebhook.error.missingVariables": "{variableCount, plural, one {Variable obligatoire manquante} other {Variables obligatoires manquantes}} : {variables}", + "xpack.stackConnectors.components.index.preconfiguredIndexHelpText": "Les documents sont indexés dans l'index {alertHistoryIndex}. ", + "xpack.stackConnectors.components.jira.unableToGetIssueMessage": "Impossible d'obtenir le problème ayant l'ID {id}", + "xpack.stackConnectors.components.pagerDuty.error.invalidTimestamp": "L'horodatage doit être une date valide, telle que {nowShortFormat} ou {nowLongFormat}.", + "xpack.stackConnectors.components.serviceNow.apiInfoError": "Statut reçu : {status} lors de la tentative d'obtention d'informations sur l'application", + "xpack.stackConnectors.components.serviceNow.appInstallationInfo": "{update} {create} ", + "xpack.stackConnectors.components.serviceNow.updateSuccessToastTitle": "Connecteur {connectorName} mis à jour", + "xpack.stackConnectors.components.serviceNow.apiUrlHelpLabel": "Fournissez l'URL complète vers l'instance ServiceNow souhaitée. Si vous n'en avez pas, {instance}.", + "xpack.stackConnectors.components.serviceNow.appRunning": "L'application Elastic de l'app store ServiceNow doit être installée avant d'exécuter la mise à jour. {visitLink} pour installer l'application", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationMessage": "Impossible d'obtenir l'application avec l'ID {id}", + "xpack.stackConnectors.components.casesWebhook.commentsTextAreaFieldLabel": "Commentaires supplémentaires", + "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "Description", + "xpack.stackConnectors.components.casesWebhook.tagsFieldLabel": "Balises", + "xpack.stackConnectors.components.casesWebhook.titleFieldLabel": "Résumé (requis)", + "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "Ajouter", + "xpack.stackConnectors.components.casesWebhook.addVariable": "Ajouter une variable", + "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "Authentification", + "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Commentaire de cas Kibana", + "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Description de cas Kibana", + "xpack.stackConnectors.components.casesWebhook.caseTagsDesc": "Balises de cas Kibana", + "xpack.stackConnectors.components.casesWebhook.caseTitleDesc": "Titre de cas Kibana", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonHelp": "Objet JSON pour créer un commentaire. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonTextFieldLabel": "Objet de création de commentaire", + "xpack.stackConnectors.components.casesWebhook.createCommentMethodTextFieldLabel": "Méthode de création de commentaire", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlHelp": "URL de l'API pour ajouter un commentaire au cas.", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlTextFieldLabel": "URL de création de commentaire", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonHelpText": "Objet JSON pour créer un cas. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonTextFieldLabel": "Objet de création de cas", + "xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel": "Méthode de création de cas", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyHelpText": "Clé JSON dans la réponse de création de cas qui contient l'ID de cas externe", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "Clé de cas pour la réponse de création de cas", + "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "URL de création de cas", + "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "Supprimer", + "xpack.stackConnectors.components.casesWebhook.docLink": "Configuration de Webhook - Connecteur de gestion des cas.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "L'objet de création de commentaire doit être un JSON valide.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "L'URL de création de commentaire doit être au format URL.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentText": "L'objet de création de cas est requis et doit être un JSON valide.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateUrlText": "L'URL de création de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentUrlText": "L'URL d'obtention de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateIncidentText": "L'objet de mise à jour de cas est requis et doit être un JSON valide.", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateUrlText": "L'URL de mise à jour de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.externalIdDesc": "ID du système externe", + "xpack.stackConnectors.components.casesWebhook.externalTitleDesc": "Titre du système externe", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyHelp": "Clé JSON dans la réponse d’obtention de cas qui contient le titre de cas externe", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyTextFieldLabel": "Clé de titre externe pour la réponse d’obtention de cas", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlHelp": "URL d’API pour le JSON de détails d’obtention de cas provenant d’un système externe. Utilisez le sélecteur de variable pour ajouter à l’URL l'ID du système externe.", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "URL d’obtention de cas", + "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", + "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "En-têtes utilisés", + "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "Éditeur de code", + "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", + "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "Clé", + "xpack.stackConnectors.components.casesWebhook.next": "Suivant", + "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "Mot de passe", + "xpack.stackConnectors.components.casesWebhook.previous": "Précédent", + "xpack.stackConnectors.components.casesWebhook.selectMessageText": "Envoyer une requête à un service web de gestion de cas.", + "xpack.stackConnectors.components.casesWebhook.step1": "Configurer le connecteur", + "xpack.stackConnectors.components.casesWebhook.step2": "Créer un cas", + "xpack.stackConnectors.components.casesWebhook.step2Description": "Définissez les champs pour créer le cas dans le système externe. Consultez la documentation de l'API de votre service pour savoir quels sont les champs obligatoires", + "xpack.stackConnectors.components.casesWebhook.step3": "Informations sur l’obtention de cas", + "xpack.stackConnectors.components.casesWebhook.step3Description": "Définissez les champs pour ajouter des commentaires au cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour la création de mises à jour dans les cas. Consultez la documentation de l'API de votre service pour savoir quels sont les champs obligatoires.", + "xpack.stackConnectors.components.casesWebhook.step4": "Commentaires et mises à jour", + "xpack.stackConnectors.components.casesWebhook.step4a": "Créer une mise à jour dans le cas", + "xpack.stackConnectors.components.casesWebhook.step4aDescription": "Définissez les champs pour créer des mises à jour du cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour l’ajout de commentaires aux cas.", + "xpack.stackConnectors.components.casesWebhook.step4b": "Ajouter un commentaire dans le cas", + "xpack.stackConnectors.components.casesWebhook.step4bDescription": "Définissez les champs pour ajouter des commentaires au cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour la création de mises à jour dans les cas.", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonHelpl": "Objet JSON pour mettre à jour le cas. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonTextFieldLabel": "Objet de mise à jour de cas", + "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "Méthode de mise à jour de cas", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "URL d’API pour mettre à jour le cas.", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "URL de mise à jour du cas", + "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "Nom d'utilisateur", + "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "Valeur", + "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "Ajouter un en-tête HTTP", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "URL pour voir le cas dans le système externe. Utilisez le sélecteur de variable pour ajouter à l’URL l'ID ou le titre du système externe.", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "URL de visualisation de cas externe", + "xpack.stackConnectors.components.serviceNow.requiredShortDescTextField": "Une brève description est requise.", + "xpack.stackConnectors.components.email.exchangeForm.clientIdHelpLabel": "Configurer l'ID client", + "xpack.stackConnectors.components.email.exchangeForm.clientSecretHelpLabel": "Configurer l'identifiant client secret", + "xpack.stackConnectors.components.email.exchangeForm.tenantIdHelpLabel": "Configurer l'ID locataire", + "xpack.stackConnectors.components.email.connectorTypeTitle": "Envoyer vers la messagerie électronique", + "xpack.stackConnectors.components.email.amazonSesServerTypeLabel": "Amazon SES", + "xpack.stackConnectors.components.email.configureAccountsHelpLabel": "Configurer les comptes de messagerie électronique", + "xpack.stackConnectors.components.email.elasticCloudServerTypeLabel": "Elastic Cloud", + "xpack.stackConnectors.components.email.exchangeServerTypeLabel": "MS Exchange Server", + "xpack.stackConnectors.components.email.gmailServerTypeLabel": "Gmail", + "xpack.stackConnectors.components.email.otherServerTypeLabel": "Autre", + "xpack.stackConnectors.components.email.outlookServerTypeLabel": "Outlook", + "xpack.stackConnectors.components.email.selectMessageText": "Envoyez un e-mail à partir de votre serveur.", + "xpack.stackConnectors.components.email.updateErrorNotificationText": "Impossible d’obtenir la configuration du service", + "xpack.stackConnectors.components.index.error.badIndexOverrideSuffix": "L'index d'historique d'alertes doit contenir un suffixe valide.", + "xpack.stackConnectors.components.email.error.invalidPortText": "Le port n'est pas valide.", + "xpack.stackConnectors.components.email.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", + "xpack.stackConnectors.components.email.error.requiredClientIdText": "L'ID client est requis.", + "xpack.stackConnectors.components.index.error.requiredDocumentJson": "Le document est requis et doit être un objet JSON valide.", + "xpack.stackConnectors.components.email.error.requiredEntryText": "Aucune entrée À, Cc ou Cci. Au moins une entrée est requise.", + "xpack.stackConnectors.components.email.error.requiredFromText": "L'expéditeur est requis.", + "xpack.stackConnectors.components.email.error.requiredHostText": "L'hôte est requis.", + "xpack.stackConnectors.components.email.error.requiredMessageText": "Le message est requis.", + "xpack.stackConnectors.components.email.error.requiredPortText": "Le port est requis.", + "xpack.stackConnectors.components.serverLog.error.requiredServerLogMessageText": "Le message est requis.", + "xpack.stackConnectors.components.email.error.requiredServiceText": "Le service est requis.", + "xpack.stackConnectors.components.email.error.requiredSubjectText": "Le sujet est requis.", + "xpack.stackConnectors.components.email.error.requiredTenantIdText": "L'ID locataire est requis.", + "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "Le corps est requis.", + "xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText": "Le titre est requis.", + "xpack.stackConnectors.components.index.connectorTypeTitle": "Données d'index", + "xpack.stackConnectors.components.index.configureIndexHelpLabel": "Configuration du connecteur d'index.", + "xpack.stackConnectors.components.index.connectorSectionTitle": "Écrire dans l'index", + "xpack.stackConnectors.components.index.definedateFieldTooltip": "Définissez ce champ temporel sur l'heure à laquelle le document a été indexé.", + "xpack.stackConnectors.components.index.defineTimeFieldLabel": "Définissez l'heure pour chaque document", + "xpack.stackConnectors.components.index.documentsFieldLabel": "Document à indexer", + "xpack.stackConnectors.components.index.error.notValidIndexText": "L’index n’est pas valide.", + "xpack.stackConnectors.components.index.executionTimeFieldLabel": "Champ temporel", + "xpack.stackConnectors.components.index.howToBroadenSearchQueryDescription": "Utilisez le caractère * pour élargir votre recherche.", + "xpack.stackConnectors.components.index.indexDocumentHelpLabel": "Exemple de document d'index.", + "xpack.stackConnectors.components.index.indicesToQueryLabel": "Index", + "xpack.stackConnectors.components.index.jsonDocAriaLabel": "Éditeur de code", + "xpack.stackConnectors.components.index.preconfiguredIndex": "Index Elasticsearch", + "xpack.stackConnectors.components.index.preconfiguredIndexDocLink": "Affichez les documents.", + "xpack.stackConnectors.components.index.refreshLabel": "Actualiser l'index", + "xpack.stackConnectors.components.index.refreshTooltip": "Actualisez les partitions affectées pour rendre cette opération visible pour la recherche.", + "xpack.stackConnectors.components.index.selectMessageText": "Indexez les données dans Elasticsearch.", + "xpack.stackConnectors.components.jira.connectorTypeTitle": "Jira", + "xpack.stackConnectors.components.jira.apiTokenTextFieldLabel": "Token d'API", + "xpack.stackConnectors.components.jira.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel": "Commentaires supplémentaires", + "xpack.stackConnectors.components.jira.descriptionTextAreaFieldLabel": "Description", + "xpack.stackConnectors.components.jira.emailTextFieldLabel": "Adresse e-mail", + "xpack.stackConnectors.components.jira.impactSelectFieldLabel": "Étiquettes", + "xpack.stackConnectors.components.jira.labelsSpacesErrorMessage": "Les étiquettes ne peuvent pas contenir d'espaces.", + "xpack.stackConnectors.components.jira.parentIssueSearchLabel": "Problème parent", + "xpack.stackConnectors.components.jira.projectKey": "Clé de projet", + "xpack.stackConnectors.components.jira.requiredSummaryTextField": "Le résumé est requis.", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxAriaLabel": "Taper pour rechercher", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxPlaceholder": "Taper pour rechercher", + "xpack.stackConnectors.components.jira.searchIssuesLoading": "Chargement...", + "xpack.stackConnectors.components.jira.selectMessageText": "Créez un incident dans Jira.", + "xpack.stackConnectors.components.jira.severitySelectFieldLabel": "Priorité", + "xpack.stackConnectors.components.jira.summaryFieldLabel": "Résumé (requis)", + "xpack.stackConnectors.components.jira.unableToGetFieldsMessage": "Impossible d'obtenir les champs", + "xpack.stackConnectors.components.jira.unableToGetIssuesMessage": "Impossible d'obtenir les problèmes", + "xpack.stackConnectors.components.jira.unableToGetIssueTypesMessage": "Impossible d'obtenir les types d'erreurs", + "xpack.stackConnectors.components.jira.urgencySelectFieldLabel": "Type d'erreur", + "xpack.stackConnectors.components.pagerDuty.connectorTypeTitle": "Envoyer à PagerDuty", + "xpack.stackConnectors.components.pagerDuty.apiUrlInvalid": "URL d’API non valide", + "xpack.stackConnectors.components.pagerDuty.apiUrlTextFieldLabel": "URL de l'API (facultative)", + "xpack.stackConnectors.components.pagerDuty.classFieldLabel": "Classe (facultative)", + "xpack.stackConnectors.components.pagerDuty.componentTextFieldLabel": "Composant (facultatif)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextFieldLabel": "DedupKey (facultatif)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextRequiredFieldLabel": "DedupKey", + "xpack.stackConnectors.components.pagerDuty.error.requiredDedupKeyText": "DedupKey est requis lors de la résolution ou de la reconnaissance d'un incident.", + "xpack.stackConnectors.components.pagerDuty.error.requiredRoutingKeyText": "Une clé d'intégration / clé de routage est requise.", + "xpack.stackConnectors.components.pagerDuty.error.requiredSummaryText": "Le résumé est requis.", + "xpack.stackConnectors.components.pagerDuty.eventActionSelectFieldLabel": "Action de l'événement", + "xpack.stackConnectors.components.pagerDuty.eventSelectAcknowledgeOptionLabel": "Reconnaissance", + "xpack.stackConnectors.components.pagerDuty.eventSelectResolveOptionLabel": "Résoudre", + "xpack.stackConnectors.components.pagerDuty.eventSelectTriggerOptionLabel": "Déclencher", + "xpack.stackConnectors.components.pagerDuty.groupTextFieldLabel": "Regrouper (facultatif)", + "xpack.stackConnectors.components.pagerDuty.routingKeyNameHelpLabel": "Configurer un compte PagerDuty", + "xpack.stackConnectors.components.pagerDuty.routingKeyTextFieldLabel": "Clé d'intégration", + "xpack.stackConnectors.components.pagerDuty.selectMessageText": "Envoyez un événement dans PagerDuty.", + "xpack.stackConnectors.components.pagerDuty.severitySelectCriticalOptionLabel": "Critique", + "xpack.stackConnectors.components.pagerDuty.severitySelectErrorOptionLabel": "Erreur", + "xpack.stackConnectors.components.pagerDuty.severitySelectFieldLabel": "Sévérité (facultative)", + "xpack.stackConnectors.components.pagerDuty.severitySelectInfoOptionLabel": "Info", + "xpack.stackConnectors.components.pagerDuty.severitySelectWarningOptionLabel": "Avertissement", + "xpack.stackConnectors.components.pagerDuty.sourceTextFieldLabel": "Source (facultative)", + "xpack.stackConnectors.components.pagerDuty.summaryFieldLabel": "Résumé", + "xpack.stackConnectors.components.pagerDuty.timestampTextFieldLabel": "Horodatage (facultatif)", + "xpack.stackConnectors.components.resilient.connectorTypeTitle": "Résilient", + "xpack.stackConnectors.components.resilient.apiKeyId": "ID de clé d'API", + "xpack.stackConnectors.components.resilient.apiKeySecret": "Secret de clé d'API", + "xpack.stackConnectors.components.resilient.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.resilient.commentsTextAreaFieldLabel": "Commentaires supplémentaires", + "xpack.stackConnectors.components.resilient.descriptionTextAreaFieldLabel": "Description", + "xpack.stackConnectors.components.resilient.nameFieldLabel": "Nom (requis)", + "xpack.stackConnectors.components.resilient.orgId": "ID d'organisation", + "xpack.stackConnectors.components.resilient.requiredNameTextField": "Un nom est requis.", + "xpack.stackConnectors.components.resilient.selectMessageText": "Créez un incident dans IBM Resilient.", + "xpack.stackConnectors.components.resilient.severity": "Sévérité", + "xpack.stackConnectors.components.resilient.unableToGetIncidentTypesMessage": "Impossible d'obtenir les types d'incidents", + "xpack.stackConnectors.components.resilient.unableToGetSeverityMessage": "Impossible d'obtenir la sévérité", + "xpack.stackConnectors.components.resilient.urgencySelectFieldLabel": "Type d'incident", + "xpack.stackConnectors.components.serverLog.connectorTypeTitle": "Envoyer vers le log de serveur", + "xpack.stackConnectors.components.serverLog.logLevelFieldLabel": "Niveau", + "xpack.stackConnectors.components.serverLog.logMessageFieldLabel": "Message", + "xpack.stackConnectors.components.serverLog.selectMessageText": "Ajouter un message au log Kibana.", + "xpack.stackConnectors.components.serviceNow.apiUrlTextFieldLabel": "URL d'instance ServiceNow", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout": "Application Elastic ServiceNow non installée", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.content": "Veuillez vous rendre dans l'app store ServiceNow pour installer l'application", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.errorMessage": "Message d'erreur", + "xpack.stackConnectors.components.serviceNow.authenticationLabel": "Authentification", + "xpack.stackConnectors.components.serviceNow.cancelButtonText": "Annuler", + "xpack.stackConnectors.components.serviceNow.categoryTitle": "Catégorie", + "xpack.stackConnectors.components.serviceNow.clientIdTextFieldLabel": "ID client", + "xpack.stackConnectors.components.serviceNow.clientSecretTextFieldLabel": "Identifiant client secret", + "xpack.stackConnectors.components.serviceNow.commentsTextAreaFieldLabel": "Commentaires supplémentaires", + "xpack.stackConnectors.components.serviceNow.confirmButtonText": "Mettre à jour", + "xpack.stackConnectors.components.serviceNow.correlationDisplay": "Affichage de la corrélation (facultatif)", + "xpack.stackConnectors.components.serviceNow.correlationID": "ID corrélation (facultatif)", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutCreate": "ou créez-en un nouveau.", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutMigrate": "Supprimez ce connecteur", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutTitle": "Ce type de connecteur est déclassé", + "xpack.stackConnectors.components.serviceNow.descriptionTextAreaFieldLabel": "Description", + "xpack.stackConnectors.components.serviceNow.eventClassTextAreaFieldLabel": "Instance source", + "xpack.stackConnectors.components.serviceNow.fetchErrorMsg": "Impossible de récupérer. Vérifiez l'URL ou la configuration CORS de votre instance ServiceNow.", + "xpack.stackConnectors.components.serviceNow.impactSelectFieldLabel": "Impact", + "xpack.stackConnectors.components.serviceNow.installationCalloutTitle": "Pour utiliser ce connecteur, installez d'abord l'application Elastic à partir de l'app store ServiceNow.", + "xpack.stackConnectors.components.serviceNow.invalidApiUrlTextField": "L'URL n'est pas valide.", + "xpack.stackConnectors.components.serviceNow.keyIdTextFieldLabel": "ID de clé du vérificateur JWT", + "xpack.stackConnectors.components.serviceNow.messageKeyTextAreaFieldLabel": "Clé de message", + "xpack.stackConnectors.components.serviceNow.metricNameTextAreaFieldLabel": "Nom de l'indicateur", + "xpack.stackConnectors.components.serviceNow.nodeTextAreaFieldLabel": "Nœud", + "xpack.stackConnectors.components.serviceNow.passwordTextFieldLabel": "Mot de passe", + "xpack.stackConnectors.components.serviceNow.prioritySelectFieldLabel": "Priorité", + "xpack.stackConnectors.components.serviceNow.privateKeyPassLabelHelpText": "Il est requis uniquement si vous avez défini un mot de passe sur votre clé privée", + "xpack.stackConnectors.components.serviceNow.privateKeyPassTextFieldLabel": "Mot de passe de clé privée", + "xpack.stackConnectors.components.serviceNow.privateKeyTextFieldLabel": "Clé privée", + "xpack.stackConnectors.components.serviceNow.requiredClientIdTextField": "L'ID client est requis.", + "xpack.stackConnectors.components.serviceNow.requiredKeyIdTextField": "L'ID de clé du vérificateur JWT est requis.", + "xpack.stackConnectors.components.serviceNow.requiredPrivateKeyTextField": "La clé privée est requise.", + "xpack.stackConnectors.components.serviceNow.requiredSeverityTextField": "La sévérité est requise.", + "xpack.stackConnectors.components.serviceNow.requiredUserIdentifierTextField": "L'identifiant de l'utilisateur est requis.", + "xpack.stackConnectors.components.serviceNow.requiredUsernameTextField": "Le nom d'utilisateur est requis.", + "xpack.stackConnectors.components.serviceNow.resourceTextAreaFieldLabel": "Ressource", + "xpack.stackConnectors.components.serviceNow.setupDevInstance": "configurer une instance de développeur", + "xpack.stackConnectors.components.serviceNow.severityRequiredSelectFieldLabel": "Sévérité (requise)", + "xpack.stackConnectors.components.serviceNow.severitySelectFieldLabel": "Sévérité", + "xpack.stackConnectors.components.serviceNow.snInstanceLabel": "Instance ServiceNow", + "xpack.stackConnectors.components.serviceNow.sourceTextAreaFieldLabel": "Source", + "xpack.stackConnectors.components.serviceNow.subcategoryTitle": "Sous-catégorie", + "xpack.stackConnectors.components.serviceNow.title": "Incident", + "xpack.stackConnectors.components.serviceNow.titleFieldLabel": "Brève description (requise)", + "xpack.stackConnectors.components.serviceNow.typeTextAreaFieldLabel": "Type", + "xpack.stackConnectors.components.serviceNow.unableToGetChoicesMessage": "Impossible d'obtenir les choix", + "xpack.stackConnectors.components.serviceNow.unknown": "INCONNU", + "xpack.stackConnectors.components.serviceNow.updateCalloutText": "Le connecteur a été mis à jour.", + "xpack.stackConnectors.components.serviceNow.updateFormCredentialsTitle": "Fournir les informations d'authentification", + "xpack.stackConnectors.components.serviceNow.updateFormInstallTitle": "Installer l'application Elastic ServiceNow", + "xpack.stackConnectors.components.serviceNow.updateFormTitle": "Mettre à jour le connecteur ServiceNow", + "xpack.stackConnectors.components.serviceNow.updateFormUrlTitle": "Entrer votre URL d'instance ServiceNow", + "xpack.stackConnectors.components.serviceNow.urgencySelectFieldLabel": "Urgence", + "xpack.stackConnectors.components.serviceNow.useOAuth": "Utiliser l'authentification OAuth", + "xpack.stackConnectors.components.serviceNow.userEmailTextFieldLabel": "Identifiant de l'utilisateur", + "xpack.stackConnectors.components.serviceNow.usernameTextFieldLabel": "Nom d'utilisateur", + "xpack.stackConnectors.components.serviceNow.visitSNStore": "Visiter l'app store ServiceNow", + "xpack.stackConnectors.components.serviceNow.warningMessage": "Cette action mettra à jour toutes les instances de ce connecteur et ne pourra pas être annulée.", + "xpack.stackConnectors.components.serviceNow.correlationIDHelpLabel": "Identificateur pour les incidents de mise à jour", + "xpack.stackConnectors.components.serviceNowITOM.connectorTypeTitle": "ServiceNow ITOM", + "xpack.stackConnectors.components.serviceNowITOM.event": "Événement", + "xpack.stackConnectors.components.serviceNowITOM.selectMessageText": "Créez un événement dans ServiceNow ITOM.", + "xpack.stackConnectors.components.serviceNowITSM.connectorTypeTitle": "ServiceNow ITSM", + "xpack.stackConnectors.components.serviceNowITSM.selectMessageText": "Créez un incident dans ServiceNow ITSM.", + "xpack.stackConnectors.components.serviceNowSIR.connectorTypeTitle": "ServiceNow SecOps", + "xpack.stackConnectors.components.serviceNowSIR.selectMessageText": "Créez un incident dans ServiceNow SecOps.", + "xpack.stackConnectors.components.serviceNowSIR.title": "Incident de sécurité", + "xpack.stackConnectors.components.serviceNowSIR.correlationIDHelpLabel": "Identificateur pour les incidents de mise à jour", + "xpack.stackConnectors.components.slack.connectorTypeTitle": "Envoyer vers Slack", + "xpack.stackConnectors.components.slack.error.invalidWebhookUrlText": "L'URL de webhook n'est pas valide.", + "xpack.stackConnectors.components.slack.messageTextAreaFieldLabel": "Message", + "xpack.stackConnectors.components.slack.selectMessageText": "Envoyez un message à un canal ou à un utilisateur Slack.", + "xpack.stackConnectors.components.slack.webhookUrlHelpLabel": "Créer une URL de webhook Slack", + "xpack.stackConnectors.components.slack.webhookUrlTextFieldLabel": "URL de webhook", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationFieldsMessage": "Impossible d'obtenir les champs de l'application", + "xpack.stackConnectors.components.swimlane.connectorTypeTitle": "Créer l'enregistrement Swimlane", + "xpack.stackConnectors.components.swimlane.alertIdFieldLabel": "ID de l'alerte", + "xpack.stackConnectors.components.swimlane.apiTokenNameHelpLabel": "Fournir un token d'API Swimlane", + "xpack.stackConnectors.components.swimlane.apiTokenTextFieldLabel": "Token d'API", + "xpack.stackConnectors.components.swimlane.apiUrlTextFieldLabel": "URL d'API", + "xpack.stackConnectors.components.swimlane.appIdTextFieldLabel": "ID d'application", + "xpack.stackConnectors.components.swimlane.caseIdFieldLabel": "ID de cas", + "xpack.stackConnectors.components.swimlane.caseNameFieldLabel": "Nom de cas", + "xpack.stackConnectors.components.swimlane.commentsFieldLabel": "Commentaires", + "xpack.stackConnectors.components.swimlane.configureConnectionLabel": "Configurer la connexion de l'API", + "xpack.stackConnectors.components.swimlane.connectorType": "Type de connecteur", + "xpack.stackConnectors.components.swimlane.descriptionFieldLabel": "Description", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningDesc": "Ce connecteur ne peut pas être sélectionné, car il ne possède pas les mappings de champs d'alerte requis. Vous pouvez modifier ce connecteur pour ajouter les mappings de champs requis ou sélectionner un connecteur de type Alertes.", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningTitle": "Ce connecteur ne possède pas de mappings de champs", + "xpack.stackConnectors.components.swimlane.error.requiredAlertID": "L'ID d'alerte est requis.", + "xpack.stackConnectors.components.swimlane.error.requiredAppIdText": "Un ID d'application est requis.", + "xpack.stackConnectors.components.swimlane.error.requiredCaseID": "L'ID de cas est requis.", + "xpack.stackConnectors.components.swimlane.error.requiredCaseName": "Le nom de cas est requis.", + "xpack.stackConnectors.components.swimlane.error.requiredComments": "Les commentaires sont requis.", + "xpack.stackConnectors.components.swimlane.error.requiredDescription": "La description est requise.", + "xpack.stackConnectors.components.swimlane.error.requiredRuleName": "Le nom de règle est requis.", + "xpack.stackConnectors.components.swimlane.error.requiredSeverity": "La sévérité est requise.", + "xpack.stackConnectors.components.swimlane.invalidApiUrlTextField": "L'URL n'est pas valide.", + "xpack.stackConnectors.components.swimlane.mappingTitleTextFieldLabel": "Configurer les mappings de champs", + "xpack.stackConnectors.components.swimlane.nextStep": "Suivant", + "xpack.stackConnectors.components.swimlane.nextStepHelpText": "Si les mappings de champs ne sont pas configurés, le type de connecteur Swimlane sera défini sur Tous.", + "xpack.stackConnectors.components.swimlane.prevStep": "Retour", + "xpack.stackConnectors.components.swimlane.ruleNameFieldLabel": "Nom de règle", + "xpack.stackConnectors.components.swimlane.selectMessageText": "Créer un enregistrement dans Swimlane", + "xpack.stackConnectors.components.swimlane.severityFieldLabel": "Sévérité", + "xpack.stackConnectors.components.teams.connectorTypeTitle": "Envoyer un message à un canal Microsoft Teams.", + "xpack.stackConnectors.components.teams.error.invalidWebhookUrlText": "L'URL de webhook n'est pas valide.", + "xpack.stackConnectors.components.teams.error.requiredMessageText": "Le message est requis.", + "xpack.stackConnectors.components.teams.error.webhookUrlTextLabel": "URL de webhook", + "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "Message", + "xpack.stackConnectors.components.teams.selectMessageText": "Envoyer un message à un canal Microsoft Teams.", + "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Créer une URL de webhook Microsoft Teams", + "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Données de webhook", + "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "Ajouter un en-tête", + "xpack.stackConnectors.components.webhook.authenticationLabel": "Authentification", + "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "Éditeur de code", + "xpack.stackConnectors.components.webhook.bodyFieldLabel": "Corps", + "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "L'URL n'est pas valide.", + "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", + "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "Clé", + "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "Valeur", + "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "Méthode", + "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "Mot de passe", + "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "Clé", + "xpack.stackConnectors.components.webhook.selectMessageText": "Envoyer une requête à un service Web.", + "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", + "xpack.stackConnectors.components.webhook.userTextFieldLabel": "Nom d'utilisateur", + "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "Ajouter un en-tête HTTP", + "xpack.stackConnectors.components.xmatters.connectorTypeTitle": "Données xMatters", + "xpack.stackConnectors.components.xmatters.authenticationLabel": "Authentification", + "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "Authentification de base", + "xpack.stackConnectors.components.xmatters.basicAuthLabel": "Authentification de base", + "xpack.stackConnectors.components.xmatters.connectorSettingsLabel": "Sélectionnez la méthode d'authentification utilisée pour la configuration du déclencheur xMatters.", + "xpack.stackConnectors.components.xmatters.error.invalidUrlTextField": "L'URL n'est pas valide.", + "xpack.stackConnectors.components.xmatters.error.invalidUsernameTextField": "Nom d'utilisateur non valide.", + "xpack.stackConnectors.components.xmatters.error.requiredUrlText": "L'URL est requise.", + "xpack.stackConnectors.components.xmatters.initiationUrlHelpText": "Spécifiez l'URL xMatters complète.", + "xpack.stackConnectors.components.xmatters.passwordTextFieldLabel": "Mot de passe", + "xpack.stackConnectors.components.xmatters.selectMessageText": "Déclenchez un workflow xMatters.", + "xpack.stackConnectors.components.xmatters.severity": "Sévérité", + "xpack.stackConnectors.components.xmatters.severitySelectCriticalOptionLabel": "Critique", + "xpack.stackConnectors.components.xmatters.severitySelectHighOptionLabel": "Élevé", + "xpack.stackConnectors.components.xmatters.severitySelectLowOptionLabel": "Bas", + "xpack.stackConnectors.components.xmatters.severitySelectMediumOptionLabel": "Moyenne", + "xpack.stackConnectors.components.xmatters.severitySelectMinimalOptionLabel": "Minimale", + "xpack.stackConnectors.components.xmatters.tags": "Balises", + "xpack.stackConnectors.components.xmatters.urlAuthLabel": "Authentification de l'URL", + "xpack.stackConnectors.components.xmatters.urlLabel": "URL d'initiation", + "xpack.stackConnectors.components.xmatters.userCredsLabel": "Identifiants d'utilisateur", + "xpack.stackConnectors.components.xmatters.userTextFieldLabel": "Nom d'utilisateur", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningDesc": "Configurez les champs Create Comment URL et Create Comment Objects pour que le connecteur puisse partager les commentaires.", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningTitle": "Impossible de partager les commentaires du cas", + "xpack.stackConnectors.components.serviceNow.deprecatedTooltipTitle": "Connecteur déclassé", + "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "La méthode de création de commentaire est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentResponseKeyText": "La clé d’ID de cas pour la réponse de création de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateMethodText": "La méthode de création de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseCreatedKeyText": "La clé de date de création de la réponse d’obtention de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseExternalTitleKeyText": "La clé de titre du cas externe pour la réponse d’obtention de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseUpdatedKeyText": "La clé de date de mise à jour de la réponse d’obtention de cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentViewUrlKeyText": "L'URL de visualisation du cas est requise.", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateMethodText": "La méthode de mise à jour du cas est requise.", + "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", + "xpack.stackConnectors.components.webhook.error.requiredMethodText": "La méthode est requise.", + "xpack.stackConnectors.components.email.addBccButton": "Cci", + "xpack.stackConnectors.components.email.addCcButton": "Cc", + "xpack.stackConnectors.components.email.authenticationLabel": "Authentification", + "xpack.stackConnectors.components.email.clientIdFieldLabel": "ID client", + "xpack.stackConnectors.components.email.clientSecretTextFieldLabel": "Identifiant client secret", + "xpack.stackConnectors.components.email.fromTextFieldLabel": "Expéditeur", + "xpack.stackConnectors.components.email.hasAuthSwitchLabel": "Demander une authentification pour ce serveur", + "xpack.stackConnectors.components.email.hostTextFieldLabel": "Hôte", + "xpack.stackConnectors.components.email.messageTextAreaFieldLabel": "Message", + "xpack.stackConnectors.components.email.passwordFieldLabel": "Mot de passe", + "xpack.stackConnectors.components.email.portTextFieldLabel": "Port", + "xpack.stackConnectors.components.email.recipientBccTextFieldLabel": "Cci", + "xpack.stackConnectors.components.email.recipientCopyTextFieldLabel": "Cc", + "xpack.stackConnectors.components.email.recipientTextFieldLabel": "À", + "xpack.stackConnectors.components.email.secureSwitchLabel": "Sécurisé", + "xpack.stackConnectors.components.email.serviceTextFieldLabel": "Service", + "xpack.stackConnectors.components.email.subjectTextFieldLabel": "Objet", + "xpack.stackConnectors.components.email.tenantIdFieldLabel": "ID locataire", + "xpack.stackConnectors.components.email.userTextFieldLabel": "Nom d'utilisateur", + "xpack.stackConnectors.components.index.resetDefaultIndexLabel": "Réinitialiser l'index par défaut", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "{fieldCandidatesCount, plural, one {# candidat de champ identifié} other {# candidats de champs identifiés}}.", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "{fieldValuePairsCount, plural, one {# paire significative champ/valeur identifiée} other {# paires significatives champ/valeur identifiées}}.", "xpack.aiops.index.dataLoader.internalServerErrorMessage": "Erreur lors du chargement des données dans l'index {index}. {message}. La requête a peut-être expiré. Essayez d'utiliser un échantillon d'une taille inférieure ou de réduire la plage temporelle.", @@ -31829,20 +32203,7 @@ "xpack.triggersActionsUI.actionVariables.legacyTagsLabel": "Cet élément a été déclassé au profit de {variable}.", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage": "Ce connecteur requiert une licence {minimumLicenseRequired}.", "xpack.triggersActionsUI.checkRuleTypeEnabled.ruleTypeDisabledByLicenseMessage": "Ce type de règle requiert une licence {minimumLicenseRequired}.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.missingVariables": "{variableCount, plural, one {Variable obligatoire manquante} other {Variables obligatoires manquantes}} : {variables}", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideValue": "L'index d'historique d'alertes doit commencer par \"{alertHistoryPrefix}\".", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidEmail": "L'adresse e-mail {email} n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.notAllowed": "L'adresse e-mail {email} n'est pas autorisée.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexHelpText": "Les documents sont indexés dans l'index {alertHistoryIndex}. ", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueMessage": "Impossible d'obtenir le problème ayant l'ID {id}", "xpack.triggersActionsUI.components.builtinActionTypes.missingSecretsValuesLabel": "Les informations sensibles ne sont pas importées. Veuillez entrer {encryptedFieldsLength, plural, one {la valeur} other {les valeurs}} pour {encryptedFieldsLength, plural, one {le champ suivant} other {les champs suivants}} {secretFieldsLabel}.", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestamp": "L'horodatage doit être une date valide, telle que {nowShortFormat} ou {nowLongFormat}.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiInfoError": "Statut reçu : {status} lors de la tentative d'obtention d'informations sur l'application", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.appInstallationInfo": "{update} {create} ", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateSuccessToastTitle": "Connecteur {connectorName} mis à jour", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.apiUrlHelpLabel": "Fournissez l'URL complète vers l'instance ServiceNow souhaitée. Si vous n'en avez pas, {instance}.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.serviceNowAppRunning": "L'application Elastic de l'app store ServiceNow doit être installée avant d'exécuter la mise à jour. {visitLink} pour installer l'application", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationMessage": "Impossible d'obtenir l'application avec l'ID {id}", "xpack.triggersActionsUI.components.buttonGroupField.error.requiredField": "{label} est obligatoire.", "xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText": "Impossible de supprimer {numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}", "xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText": "Suppression de {numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}} effectuée", @@ -31998,340 +32359,6 @@ "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "Ajouter une variable de règle", "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "Ajouter une variable", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "Une erreur s'est produite lors de la recherche des alertes", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.commentsTextAreaFieldLabel": "Commentaires supplémentaires", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.descriptionTextAreaFieldLabel": "Description", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.tagsFieldLabel": "Balises", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.titleFieldLabel": "Résumé (requis)", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.actionTypeTitle": "Webhook - Données de gestion des cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addHeaderButton": "Ajouter", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addVariable": "Ajouter une variable", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseCommentDesc": "Commentaire de cas Kibana", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseDescriptionDesc": "Description de cas Kibana", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTagsDesc": "Balises de cas Kibana", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTitleDesc": "Titre de cas Kibana", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonHelp": "Objet JSON pour créer un commentaire. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonTextFieldLabel": "Objet de création de commentaire", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentMethodTextFieldLabel": "Méthode de création de commentaire", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlHelp": "URL de l'API pour ajouter un commentaire au cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlTextFieldLabel": "URL de création de commentaire", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonHelpText": "Objet JSON pour créer un cas. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonTextFieldLabel": "Objet de création de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentMethodTextFieldLabel": "Méthode de création de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyHelpText": "Clé JSON dans la réponse de création de cas qui contient l'ID de cas externe", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyTextFieldLabel": "Clé de cas pour la réponse de création de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentUrlTextFieldLabel": "URL de création de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.deleteHeaderButton": "Supprimer", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.docLink": "Configuration de Webhook - Connecteur de gestion des cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentIncidentText": "L'objet de création de commentaire doit être un JSON valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentUrlText": "L'URL de création de commentaire doit être au format URL.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateIncidentText": "L'objet de création de cas est requis et doit être un JSON valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateUrlText": "L'URL de création de cas est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredGetIncidentUrlText": "L'URL d'obtention de cas est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateIncidentText": "L'objet de mise à jour de cas est requis et doit être un JSON valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateUrlText": "L'URL de mise à jour de cas est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalIdDesc": "ID du système externe", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalTitleDesc": "Titre du système externe", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyHelp": "Clé JSON dans la réponse d’obtention de cas qui contient le titre de cas externe", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyTextFieldLabel": "Clé de titre externe pour la réponse d’obtention de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlHelp": "URL d’API pour le JSON de détails d’obtention de cas provenant d’un système externe. Utilisez le sélecteur de variable pour ajouter à l’URL l'ID du système externe.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlTextFieldLabel": "URL d’obtention de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.httpHeadersTitle": "En-têtes utilisés", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonCodeEditorAriaLabel": "Éditeur de code", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonFieldLabel": "JSON", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.keyTextFieldLabel": "Clé", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.next": "Suivant", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.passwordTextFieldLabel": "Mot de passe", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.previous": "Précédent", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.selectMessageText": "Envoyer une requête à un service web de gestion de cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step1": "Configurer le connecteur", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2": "Créer un cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2Description": "Définissez les champs pour créer le cas dans le système externe. Consultez la documentation de l'API de votre service pour savoir quels sont les champs obligatoires", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3": "Informations sur l’obtention de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3Description": "Définissez les champs pour ajouter des commentaires au cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour la création de mises à jour dans les cas. Consultez la documentation de l'API de votre service pour savoir quels sont les champs obligatoires.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4": "Commentaires et mises à jour", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4a": "Créer une mise à jour dans le cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4aDescription": "Définissez les champs pour créer des mises à jour du cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour l’ajout de commentaires aux cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4b": "Ajouter un commentaire dans le cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4bDescription": "Définissez les champs pour ajouter des commentaires au cas dans le système externe. Pour certains systèmes, cela peut être la même méthode que pour la création de mises à jour dans les cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonHelpl": "Objet JSON pour mettre à jour le cas. Utilisez le sélecteur de variable pour ajouter des données de cas à la charge de travail.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonTextFieldLabel": "Objet de mise à jour de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentMethodTextFieldLabel": "Méthode de mise à jour de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlHelp": "URL d’API pour mettre à jour le cas.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlTextFieldLabel": "URL de mise à jour du cas", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.userTextFieldLabel": "Nom d'utilisateur", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.valueTextFieldLabel": "Valeur", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewHeadersSwitch": "Ajouter un en-tête HTTP", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlHelp": "URL pour voir le cas dans le système externe. Utilisez le sélecteur de variable pour ajouter à l’URL l'ID ou le titre du système externe.", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlTextFieldLabel": "URL de visualisation de cas externe", - "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField": "Une brève description est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientIdHelpLabel": "Configurer l'ID client", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientSecretHelpLabel": "Configurer l'identifiant client secret", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.tenantIdHelpLabel": "Configurer l'ID locataire", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "Envoyer vers la messagerie électronique", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.amazonSesServerTypeLabel": "Amazon SES", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.configureAccountsHelpLabel": "Configurer les comptes de messagerie électronique", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.elasticCloudServerTypeLabel": "Elastic Cloud", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.exchangeServerTypeLabel": "MS Exchange Server", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.gmailServerTypeLabel": "Gmail", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.otherServerTypeLabel": "Autre", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.outlookServerTypeLabel": "Outlook", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "Envoyez un e-mail à partir de votre serveur.", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.updateErrorNotificationText": "Impossible d’obtenir la configuration du service", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideSuffix": "L'index d'historique d'alertes doit contenir un suffixe valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidPortText": "Le port n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredClientIdText": "L'ID client est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredDocumentJson": "Le document est requis et doit être un objet JSON valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText": "Aucune entrée À, Cc ou Cci. Au moins une entrée est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText": "L'expéditeur est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText": "L'hôte est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText": "Le message est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText": "Le port est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServerLogMessageText": "Le message est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText": "Le service est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText": "Le message est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText": "Le sujet est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredTenantIdText": "L'ID locataire est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText": "Le corps est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookSummaryText": "Le titre est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.actionTypeTitle": "Données d'index", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "Choisir…", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.configureIndexHelpLabel": "Configuration du connecteur d'index.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.connectorSectionTitle": "Écrire dans l'index", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip": "Définissez ce champ temporel sur l'heure à laquelle le document a été indexé.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel": "Définissez l'heure pour chaque document", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel": "Document à indexer", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.error.notValidIndexText": "L’index n’est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.executionTimeFieldLabel": "Champ temporel", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.howToBroadenSearchQueryDescription": "Utilisez le caractère * pour élargir votre recherche.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indexDocumentHelpLabel": "Exemple de document d'index.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesAndIndexPatternsLabel": "Basé sur vos vues de données", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesToQueryLabel": "Index", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel": "Éditeur de code", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndex": "Index Elasticsearch", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexDocLink": "Affichez les documents.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshLabel": "Actualiser l'index", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip": "Actualisez les partitions affectées pour rendre cette opération visible pour la recherche.", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText": "Indexez les données dans Elasticsearch.", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.actionTypeTitle": "Jira", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiTokenTextFieldLabel": "Token d'API", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.commentsTextAreaFieldLabel": "Commentaires supplémentaires", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.descriptionTextAreaFieldLabel": "Description", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.emailTextFieldLabel": "Adresse e-mail", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.impactSelectFieldLabel": "Étiquettes", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.labelsSpacesErrorMessage": "Les étiquettes ne peuvent pas contenir d'espaces.", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.parentIssueSearchLabel": "Problème parent", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.projectKey": "Clé de projet", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredSummaryTextField": "Le résumé est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "Taper pour rechercher", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxPlaceholder": "Taper pour rechercher", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesLoading": "Chargement...", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.selectMessageText": "Créez un incident dans Jira.", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.severitySelectFieldLabel": "Priorité", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.summaryFieldLabel": "Résumé (requis)", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetFieldsMessage": "Impossible d'obtenir les champs", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssuesMessage": "Impossible d'obtenir les problèmes", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueTypesMessage": "Impossible d'obtenir les types d'erreurs", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.urgencySelectFieldLabel": "Type d'erreur", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle": "Envoyer à PagerDuty", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlInvalid": "URL d’API non valide", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlTextFieldLabel": "URL de l'API (facultative)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel": "Classe (facultative)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.componentTextFieldLabel": "Composant (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextFieldLabel": "DedupKey (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextRequiredFieldLabel": "DedupKey", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredDedupKeyText": "DedupKey est requis lors de la résolution ou de la reconnaissance d'un incident.", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredRoutingKeyText": "Une clé d'intégration / clé de routage est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredSummaryText": "Le résumé est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventActionSelectFieldLabel": "Action de l'événement", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectAcknowledgeOptionLabel": "Reconnaissance", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectResolveOptionLabel": "Résoudre", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectTriggerOptionLabel": "Déclencher", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.groupTextFieldLabel": "Regrouper (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyNameHelpLabel": "Configurer un compte PagerDuty", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyTextFieldLabel": "Clé d'intégration", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText": "Envoyez un événement dans PagerDuty.", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectCriticalOptionLabel": "Critique", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectErrorOptionLabel": "Erreur", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectFieldLabel": "Sévérité (facultative)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectInfoOptionLabel": "Info", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectWarningOptionLabel": "Avertissement", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.sourceTextFieldLabel": "Source (facultative)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel": "Résumé", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel": "Horodatage (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.actionTypeTitle": "Résilient", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeyId": "ID de clé d'API", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeySecret": "Secret de clé d'API", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.commentsTextAreaFieldLabel": "Commentaires supplémentaires", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.descriptionTextAreaFieldLabel": "Description", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.nameFieldLabel": "Nom (requis)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.orgId": "ID d'organisation", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField": "Un nom est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.selectMessageText": "Créez un incident dans IBM Resilient.", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.severity": "Sévérité", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetIncidentTypesMessage": "Impossible d'obtenir les types d'incidents", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetSeverityMessage": "Impossible d'obtenir la sévérité", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.urgencySelectFieldLabel": "Type d'incident", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle": "Envoyer vers le log de serveur", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "Niveau", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "Message", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "Ajouter un message au log Kibana.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel": "URL d'instance ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout": "Application Elastic ServiceNow non installée", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.content": "Veuillez vous rendre dans l'app store ServiceNow pour installer l'application", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.errorMessage": "Message d'erreur", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.cancelButtonText": "Annuler", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.categoryTitle": "Catégorie", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientIdTextFieldLabel": "ID client", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientSecretTextFieldLabel": "Identifiant client secret", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.commentsTextAreaFieldLabel": "Commentaires supplémentaires", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.confirmButtonText": "Mettre à jour", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationDisplay": "Affichage de la corrélation (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationID": "ID corrélation (facultatif)", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutCreate": "ou créez-en un nouveau.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutMigrate": "Supprimez ce connecteur", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutTitle": "Ce type de connecteur est déclassé", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.descriptionTextAreaFieldLabel": "Description", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.eventClassTextAreaFieldLabel": "Instance source", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.fetchErrorMsg": "Impossible de récupérer. Vérifiez l'URL ou la configuration CORS de votre instance ServiceNow.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.impactSelectFieldLabel": "Impact", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.installationCalloutTitle": "Pour utiliser ce connecteur, installez d'abord l'application Elastic à partir de l'app store ServiceNow.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField": "L'URL n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.keyIdTextFieldLabel": "ID de clé du vérificateur JWT", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.messageKeyTextAreaFieldLabel": "Clé de message", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.metricNameTextAreaFieldLabel": "Nom de l'indicateur", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.nodeTextAreaFieldLabel": "Nœud", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel": "Mot de passe", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.prioritySelectFieldLabel": "Priorité", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassLabelHelpText": "Il est requis uniquement si vous avez défini un mot de passe sur votre clé privée", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassTextFieldLabel": "Mot de passe de clé privée", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyTextFieldLabel": "Clé privée", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredClientIdTextField": "L'ID client est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredKeyIdTextField": "L'ID de clé du vérificateur JWT est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPrivateKeyTextField": "La clé privée est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredSeverityTextField": "La sévérité est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUserIdentifierTextField": "L'identifiant de l'utilisateur est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField": "Le nom d'utilisateur est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.resourceTextAreaFieldLabel": "Ressource", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.setupDevInstance": "configurer une instance de développeur", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severityRequiredSelectFieldLabel": "Sévérité (requise)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectFieldLabel": "Sévérité", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.snInstanceLabel": "Instance ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.sourceTextAreaFieldLabel": "Source", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.subcategoryTitle": "Sous-catégorie", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.title": "Incident", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.titleFieldLabel": "Brève description (requise)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.typeTextAreaFieldLabel": "Type", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unableToGetChoicesMessage": "Impossible d'obtenir les choix", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unknown": "INCONNU", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateCalloutText": "Le connecteur a été mis à jour.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormCredentialsTitle": "Fournir les informations d'authentification", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormInstallTitle": "Installer l'application Elastic ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormTitle": "Mettre à jour le connecteur ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormUrlTitle": "Entrer votre URL d'instance ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.urgencySelectFieldLabel": "Urgence", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.useOAuth": "Utiliser l'authentification OAuth", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.userEmailTextFieldLabel": "Identifiant de l'utilisateur", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel": "Nom d'utilisateur", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.visitSNStore": "Visiter l'app store ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.warningMessage": "Cette action mettra à jour toutes les instances de ce connecteur et ne pourra pas être annulée.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.correlationIDHelpLabel": "Identificateur pour les incidents de mise à jour", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.actionTypeTitle": "ServiceNow ITOM", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowITOM.event": "Événement", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.selectMessageText": "Créez un événement dans ServiceNow ITOM.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.actionTypeTitle": "ServiceNow ITSM", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.selectMessageText": "Créez un incident dans ServiceNow ITSM.", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.actionTypeTitle": "ServiceNow SecOps", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.selectMessageText": "Créez un incident dans ServiceNow SecOps.", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowSIR.title": "Incident de sécurité", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIRAction.correlationIDHelpLabel": "Identificateur pour les incidents de mise à jour", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "Envoyer vers Slack", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.invalidWebhookUrlText": "L'URL de webhook n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel": "Message", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText": "Envoyez un message à un canal ou à un utilisateur Slack.", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlHelpLabel": "Créer une URL de webhook Slack", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel": "URL de webhook", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationFieldsMessage": "Impossible d'obtenir les champs de l'application", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle": "Créer l'enregistrement Swimlane", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.alertIdFieldLabel": "ID de l'alerte", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenNameHelpLabel": "Fournir un token d'API Swimlane", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenTextFieldLabel": "Token d'API", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiUrlTextFieldLabel": "URL d'API", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.appIdTextFieldLabel": "ID d'application", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseIdFieldLabel": "ID de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseNameFieldLabel": "Nom de cas", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.commentsFieldLabel": "Commentaires", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.configureConnectionLabel": "Configurer la connexion de l'API", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.connectorType": "Type de connecteur", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.descriptionFieldLabel": "Description", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningDesc": "Ce connecteur ne peut pas être sélectionné, car il ne possède pas les mappings de champs d'alerte requis. Vous pouvez modifier ce connecteur pour ajouter les mappings de champs requis ou sélectionner un connecteur de type Alertes.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningTitle": "Ce connecteur ne possède pas de mappings de champs", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAlertID": "L'ID d'alerte est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAppIdText": "Un ID d'application est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseID": "L'ID de cas est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseName": "Le nom de cas est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredComments": "Les commentaires sont requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredDescription": "La description est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredRuleName": "Le nom de règle est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredSeverity": "La sévérité est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.invalidApiUrlTextField": "L'URL n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.mappingTitleTextFieldLabel": "Configurer les mappings de champs", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStep": "Suivant", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStepHelpText": "Si les mappings de champs ne sont pas configurés, le type de connecteur Swimlane sera défini sur Tous.", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.prevStep": "Retour", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.ruleNameFieldLabel": "Nom de règle", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText": "Créer un enregistrement dans Swimlane", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.severityFieldLabel": "Sévérité", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.actionTypeTitle": "Envoyer un message à un canal Microsoft Teams.", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.invalidWebhookUrlText": "L'URL de webhook n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requiredMessageText": "Le message est requis.", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.webhookUrlTextLabel": "URL de webhook", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.messageTextAreaFieldLabel": "Message", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.selectMessageText": "Envoyer un message à un canal Microsoft Teams.", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlHelpLabel": "Créer une URL de webhook Microsoft Teams", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle": "Données de webhook", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButtonLabel": "Ajouter un en-tête", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyCodeEditorAriaLabel": "Éditeur de code", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyFieldLabel": "Corps", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.invalidUrlTextField": "L'URL n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerKeyTextFieldLabel": "Clé", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerValueTextFieldLabel": "Valeur", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.methodTextFieldLabel": "Méthode", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.passwordTextFieldLabel": "Mot de passe", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.removeHeaderIconLabel": "Clé", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.selectMessageText": "Envoyer une requête à un service Web.", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.urlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.userTextFieldLabel": "Nom d'utilisateur", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch": "Ajouter un en-tête HTTP", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.actionTypeTitle": "Données xMatters", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthButtonGroupLegend": "Authentification de base", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthLabel": "Authentification de base", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.connectorSettingsLabel": "Sélectionnez la méthode d'authentification utilisée pour la configuration du déclencheur xMatters.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUrlTextField": "L'URL n'est pas valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUsernameTextField": "Nom d'utilisateur non valide.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.requiredUrlText": "L'URL est requise.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.initiationUrlHelpText": "Spécifiez l'URL xMatters complète.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.passwordTextFieldLabel": "Mot de passe", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.selectMessageText": "Déclenchez un workflow xMatters.", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severity": "Sévérité", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectCriticalOptionLabel": "Critique", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectHighOptionLabel": "Élevé", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectLowOptionLabel": "Bas", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMediumOptionLabel": "Moyenne", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMinimalOptionLabel": "Minimale", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.tags": "Balises", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlAuthLabel": "Authentification de l'URL", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlLabel": "URL d'initiation", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userCredsLabel": "Identifiants d'utilisateur", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userTextFieldLabel": "Nom d'utilisateur", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorButtonLabel": "Créer un connecteur", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyBody": "Configurer les services de messagerie électronique, Slack, Elasticsearch et tiers que Kibana exécute.", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyTitle": "Créer votre premier connecteur", @@ -32353,8 +32380,6 @@ "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorMessage": "L'éditeur est introuvable. Veuillez actualiser la page et réessayer", "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorTitle": "Impossible d'ajouter une variable de message", "xpack.triggersActionsUI.components.simpleConnectorForm.secrets.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningDesc": "Configurez les champs Create Comment URL et Create Comment Objects pour que le connecteur puisse partager les commentaires.", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningTitle": "Impossible de partager les commentaires du cas", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "Connecteurs", "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart] : est postérieure à [dateEnd]", "xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval] : doit être spécifié si [dateStart] n'est pas égale à [dateEnd]", @@ -32427,7 +32452,6 @@ "xpack.triggersActionsUI.sections.actionConnectorForm.loadingConnectorSettingsDescription": "Chargement des paramètres du connecteur…", "xpack.triggersActionsUI.sections.actionForm.actionSectionsTitle": "Actions", "xpack.triggersActionsUI.sections.actionForm.addActionButtonLabel": "Ajouter une action", - "xpack.triggersActionsUI.sections.actionForm.deprecatedTooltipTitle": "Connecteur déclassé", "xpack.triggersActionsUI.sections.actionForm.getMoreConnectorsTitle": "Obtenir davantage de connecteurs", "xpack.triggersActionsUI.sections.actionForm.getMoreRuleTypesTitle": "Obtenir davantage de types de règles", "xpack.triggersActionsUI.sections.actionForm.incidentManagementSystemLabel": "Système de gestion des incidents", @@ -32466,17 +32490,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.actionErrorToolTip": "L’action contient des erreurs.", "xpack.triggersActionsUI.sections.actionTypeForm.actionRunWhenInActionGroup": "Exécuter quand", "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "Ajouter un connecteur", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateCommentMethodText": "La méthode de création de commentaire est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateIncidentResponseKeyText": "La clé d’ID de cas pour la réponse de création de cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateMethodText": "La méthode de création de cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseCreatedKeyText": "La clé de date de création de la réponse d’obtention de cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseExternalTitleKeyText": "La clé de titre du cas externe pour la réponse d’obtention de cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseUpdatedKeyText": "La clé de date de mise à jour de la réponse d’obtention de cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentViewUrlKeyText": "L'URL de visualisation du cas est requise.", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredUpdateMethodText": "La méthode de mise à jour du cas est requise.", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText": "La méthode est requise.", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "Sélectionner un connecteur", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "Création de \"{connectorName}\" effectuée", "xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel": "Annuler", @@ -32486,25 +32499,6 @@ "xpack.triggersActionsUI.sections.alertsTable.alertsFlyout.reason": "Raison", "xpack.triggersActionsUI.sections.alertsTable.column.actions": "Actions", "xpack.triggersActionsUI.sections.alertsTable.leadingControl.viewDetails": "Afficher les détails", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addBccButton": "Cci", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addCcButton": "Cc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.authenticationLabel": "Authentification", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientIdFieldLabel": "ID client", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientSecretTextFieldLabel": "Identifiant client secret", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.fromTextFieldLabel": "Expéditeur", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hasAuthSwitchLabel": "Demander une authentification pour ce serveur", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hostTextFieldLabel": "Hôte", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.messageTextAreaFieldLabel": "Message", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel": "Mot de passe", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.portTextFieldLabel": "Port", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel": "Cci", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel": "Cc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientTextFieldLabel": "À", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.secureSwitchLabel": "Sécurisé", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.serviceTextFieldLabel": "Service", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel": "Objet", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.tenantIdFieldLabel": "ID locataire", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "Nom d'utilisateur", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseCancelButtonText": "Annuler", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseConfirmButtonText": "Abandonner les modifications", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseMessage": "Vous ne pouvez pas récupérer de modifications non enregistrées.", @@ -32694,7 +32688,6 @@ "xpack.triggersActionsUI.sections.rulesList.removeAllButton": "Tout supprimer", "xpack.triggersActionsUI.sections.rulesList.removeCancelButton": "Annuler", "xpack.triggersActionsUI.sections.rulesList.removeConfirmButton": "Tout supprimer", - "xpack.triggersActionsUI.sections.rulesList.resetDefaultIndexLabel": "Réinitialiser l'index par défaut", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDecrypting": "Une erreur s'est produite lors du déchiffrement de la règle.", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDisabled": "La règle n'a pas pu s'exécuter, car elle a été lancée après sa désactivation.", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonLicense": "Impossible d'exécuter la règle", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 672dcbd3d5ccd..a09bdfe578cb5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6371,9 +6371,383 @@ "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "usesBasicがtrueのときには、secretsUrlを指定しないでください", "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "usesBasicがfalseのときには、ユーザー名とパスワードを指定しないでください", "xpack.stackConnectors.xmatters.title": "xMatters", + "xpack.stackConnectors.components.index.error.badIndexOverrideValue": "アラート履歴インデックスの先頭は\"{alertHistoryPrefix}\"でなければなりません。", + "xpack.stackConnectors.components.email.error.invalidEmail": "電子メールアドレス{email}が無効です。", + "xpack.stackConnectors.components.email.error.notAllowed": "電子メールアドレス{email}が許可されていません。", + "xpack.stackConnectors.components.index.preconfiguredIndexHelpText": "ドキュメントは{alertHistoryIndex}インデックスにインデックスされます。", + "xpack.stackConnectors.components.jira.unableToGetIssueMessage": "ID {id}の問題を取得できません", + "xpack.stackConnectors.components.pagerDuty.error.invalidTimestamp": "タイムスタンプは、{nowShortFormat}や{nowLongFormat}などの有効な日付でなければなりません。", + "xpack.stackConnectors.components.serviceNow.apiInfoError": "アプリケーション情報の取得を試みるときの受信ステータス:{status}", + "xpack.stackConnectors.components.serviceNow.appInstallationInfo": "{update} {create} ", + "xpack.stackConnectors.components.serviceNow.updateSuccessToastTitle": "{connectorName}コネクターが更新されました", + "xpack.stackConnectors.components.serviceNow.apiUrlHelpLabel": "任意のServiceNowインスタンスへの完全なURLを入力します。ない場合は、{instance}。", + "xpack.stackConnectors.components.serviceNow.appRunning": "更新を実行する前に、ServiceNowアプリストアからElasticアプリをインストールする必要があります。アプリをインストールするには、{visitLink}", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationMessage": "id {id}のアプリケーションフィールドを取得できません", + "xpack.stackConnectors.components.casesWebhook.commentsTextAreaFieldLabel": "追加のコメント", + "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "説明", + "xpack.stackConnectors.components.casesWebhook.tagsFieldLabel": "タグ", + "xpack.stackConnectors.components.casesWebhook.titleFieldLabel": "概要(必須)", + "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "追加", + "xpack.stackConnectors.components.casesWebhook.addVariable": "変数を追加", + "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "認証", + "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibanaケースコメント", + "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibanaケース説明", + "xpack.stackConnectors.components.casesWebhook.caseTagsDesc": "Kibanaケースタグ", + "xpack.stackConnectors.components.casesWebhook.caseTitleDesc": "Kibanaケースタイトル", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonHelp": "コメントを作成するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonTextFieldLabel": "コメントオブジェクトを作成", + "xpack.stackConnectors.components.casesWebhook.createCommentMethodTextFieldLabel": "コメントメソッドを作成", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlHelp": "コメントをケースに追加するためのAPI URL。", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlTextFieldLabel": "コメントURLを作成", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonHelpText": "ケースを作成するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonTextFieldLabel": "ケースオブジェクトを作成", + "xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel": "ケースメソッドを作成", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyHelpText": "外部ケースIDを含むケース対応の作成のJSONキー", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "ケース対応ケースキーを作成", + "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "ケースURLを作成", + "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "削除", + "xpack.stackConnectors.components.casesWebhook.docLink": "Webフックの構成 - ケース管理コネクター。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "コメントオブジェクトの作成は有効なJSONでなければなりません。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "コメントURLの作成はURL形式でなければなりません。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentText": "ケースオブジェクトの作成は必須であり、有効なJSONでなければなりません。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateUrlText": "ケースURLの作成は必須です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentUrlText": "ケースURLの取得は必須です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateIncidentText": "ケースオブジェクトの更新は必須であり、有効なJSONでなければなりません。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateUrlText": "ケースURLの更新は必須です。", + "xpack.stackConnectors.components.casesWebhook.externalIdDesc": "外部システムID", + "xpack.stackConnectors.components.casesWebhook.externalTitleDesc": "外部システムタイトル", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyHelp": "外部ケースタイトルを含むケース対応の取得のJSONキー", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyTextFieldLabel": "ケース対応の取得の外部タイトルキー", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlHelp": "外部システムからケース詳細JSONを取得するAPI URL。変数セレクターを使用して、外部システムIDをURLに追加します。", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "ケースURLを取得", + "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "この Web フックの認証が必要です", + "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "使用中のヘッダー", + "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "コードエディター", + "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", + "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "キー", + "xpack.stackConnectors.components.casesWebhook.next": "次へ", + "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "パスワード", + "xpack.stackConnectors.components.casesWebhook.previous": "前へ", + "xpack.stackConnectors.components.casesWebhook.selectMessageText": "ケース管理Webサービスにリクエストを送信します。", + "xpack.stackConnectors.components.casesWebhook.step1": "コネクターを設定", + "xpack.stackConnectors.components.casesWebhook.step2": "ケースを作成", + "xpack.stackConnectors.components.casesWebhook.step2Description": "外部システムでケースを作成するフィールドを設定します。必要なフィールドを判断するには、サービスのAPIドキュメントを確認してください", + "xpack.stackConnectors.components.casesWebhook.step3": "ケース情報を取得", + "xpack.stackConnectors.components.casesWebhook.step3Description": "外部システムでコメントをケースに追加するフィールドを設定します。一部のシステムでは、ケースでの更新の作成と同じメソッドの場合があります。必要なフィールドを判断するには、サービスのAPIドキュメントを確認してください。", + "xpack.stackConnectors.components.casesWebhook.step4": "コメントと更新", + "xpack.stackConnectors.components.casesWebhook.step4a": "ケースで更新を作成", + "xpack.stackConnectors.components.casesWebhook.step4aDescription": "外部システムでケースの更新を作成するフィールドを設定します。一部のシステムでは、ケースへのコメントの追加と同じメソッドの場合があります。", + "xpack.stackConnectors.components.casesWebhook.step4b": "ケースでコメントを追加", + "xpack.stackConnectors.components.casesWebhook.step4bDescription": "外部システムでコメントをケースに追加するフィールドを設定します。一部のシステムでは、ケースでの更新の作成と同じメソッドの場合があります。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonHelpl": "ケースを更新するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonTextFieldLabel": "ケースオブジェクトを更新", + "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "ケースメソッドを更新", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "ケースを更新するAPI URL。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "ケースURLを更新", + "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "ユーザー名", + "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "値", + "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "HTTP ヘッダーを追加", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "外部システムでケースを表示するURL。変数セレクターを使用して、外部システムIDまたは外部システムタイトルをURLに追加します。", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部ケース表示URL", + "xpack.stackConnectors.components.serviceNow.requiredShortDescTextField": "短い説明が必要です。", + "xpack.stackConnectors.components.email.exchangeForm.clientIdHelpLabel": "クライアントIDの構成", + "xpack.stackConnectors.components.email.exchangeForm.clientSecretHelpLabel": "クライアントシークレットの構成", + "xpack.stackConnectors.components.email.exchangeForm.tenantIdHelpLabel": "テナントIDの構成", + "xpack.stackConnectors.components.email.connectorTypeTitle": "メールに送信", + "xpack.stackConnectors.components.email.amazonSesServerTypeLabel": "Amazon SES", + "xpack.stackConnectors.components.email.configureAccountsHelpLabel": "電子メールアカウントの構成", + "xpack.stackConnectors.components.email.elasticCloudServerTypeLabel": "Elastic Cloud", + "xpack.stackConnectors.components.email.exchangeServerTypeLabel": "MS Exchange Server", + "xpack.stackConnectors.components.email.gmailServerTypeLabel": "Gmail", + "xpack.stackConnectors.components.email.otherServerTypeLabel": "その他", + "xpack.stackConnectors.components.email.outlookServerTypeLabel": "Outlook", + "xpack.stackConnectors.components.email.selectMessageText": "サーバーからメールを送信します。", + "xpack.stackConnectors.components.email.updateErrorNotificationText": "サービス構成を取得できません", + "xpack.stackConnectors.components.index.error.badIndexOverrideSuffix": "アラート履歴インデックスには有効なサフィックスを含める必要があります。", + "xpack.stackConnectors.components.email.error.invalidPortText": "ポートが無効です。", + "xpack.stackConnectors.components.email.error.requiredAuthUserNameText": "ユーザー名が必要です。", + "xpack.stackConnectors.components.email.error.requiredClientIdText": "クライアントIDは必須です。", + "xpack.stackConnectors.components.index.error.requiredDocumentJson": "ドキュメントが必要です。有効なJSONオブジェクトにしてください。", + "xpack.stackConnectors.components.email.error.requiredEntryText": "To、Cc、または Bcc のエントリーがありません。 1 つ以上のエントリーが必要です。", + "xpack.stackConnectors.components.email.error.requiredFromText": "送信元が必要です。", + "xpack.stackConnectors.components.email.error.requiredHostText": "ホストが必要です。", + "xpack.stackConnectors.components.email.error.requiredMessageText": "メッセージが必要です。", + "xpack.stackConnectors.components.email.error.requiredPortText": "ポートが必要です。", + "xpack.stackConnectors.components.serverLog.error.requiredServerLogMessageText": "メッセージが必要です。", + "xpack.stackConnectors.components.email.error.requiredServiceText": "サービスは必須です。", + "xpack.stackConnectors.components.email.error.requiredSubjectText": "件名が必要です。", + "xpack.stackConnectors.components.email.error.requiredTenantIdText": "テナントIDは必須です。", + "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "本文が必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText": "タイトルが必要です。", + "xpack.stackConnectors.components.index.connectorTypeTitle": "データをインデックスする", + "xpack.stackConnectors.components.index.configureIndexHelpLabel": "インデックスコネクターを構成しています。", + "xpack.stackConnectors.components.index.connectorSectionTitle": "インデックスを書き出す", + "xpack.stackConnectors.components.index.definedateFieldTooltip": "この時間フィールドをドキュメントにインデックスが作成された時刻に設定します。", + "xpack.stackConnectors.components.index.defineTimeFieldLabel": "各ドキュメントの時刻フィールドを定義", + "xpack.stackConnectors.components.index.documentsFieldLabel": "インデックスするドキュメント", + "xpack.stackConnectors.components.index.error.notValidIndexText": "インデックスは有効ではありません。", + "xpack.stackConnectors.components.index.executionTimeFieldLabel": "時間フィールド", + "xpack.stackConnectors.components.index.howToBroadenSearchQueryDescription": "* で検索クエリの範囲を広げます。", + "xpack.stackConnectors.components.index.indexDocumentHelpLabel": "インデックスドキュメントの例。", + "xpack.stackConnectors.components.index.indicesToQueryLabel": "インデックス", + "xpack.stackConnectors.components.index.jsonDocAriaLabel": "コードエディター", + "xpack.stackConnectors.components.index.preconfiguredIndex": "Elasticsearchインデックス", + "xpack.stackConnectors.components.index.preconfiguredIndexDocLink": "ドキュメントを表示します。", + "xpack.stackConnectors.components.index.refreshLabel": "更新インデックス", + "xpack.stackConnectors.components.index.refreshTooltip": "影響を受けるシャードを更新し、この処理を検索できるようにします。", + "xpack.stackConnectors.components.index.selectMessageText": "データを Elasticsearch にインデックスしてください。", + "xpack.stackConnectors.components.jira.connectorTypeTitle": "Jira", + "xpack.stackConnectors.components.jira.apiTokenTextFieldLabel": "APIトークン", + "xpack.stackConnectors.components.jira.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel": "追加のコメント", + "xpack.stackConnectors.components.jira.descriptionTextAreaFieldLabel": "説明", + "xpack.stackConnectors.components.jira.emailTextFieldLabel": "メールアドレス", + "xpack.stackConnectors.components.jira.impactSelectFieldLabel": "ラベル", + "xpack.stackConnectors.components.jira.labelsSpacesErrorMessage": "ラベルにはスペースを使用できません。", + "xpack.stackConnectors.components.jira.parentIssueSearchLabel": "親問題", + "xpack.stackConnectors.components.jira.projectKey": "プロジェクトキー", + "xpack.stackConnectors.components.jira.requiredSummaryTextField": "概要が必要です。", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxAriaLabel": "入力して検索", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxPlaceholder": "入力して検索", + "xpack.stackConnectors.components.jira.searchIssuesLoading": "読み込み中...", + "xpack.stackConnectors.components.jira.selectMessageText": "Jira でインシデントを作成します。", + "xpack.stackConnectors.components.jira.severitySelectFieldLabel": "優先度", + "xpack.stackConnectors.components.jira.summaryFieldLabel": "概要(必須)", + "xpack.stackConnectors.components.jira.unableToGetFieldsMessage": "フィールドを取得できません", + "xpack.stackConnectors.components.jira.unableToGetIssuesMessage": "問題を取得できません", + "xpack.stackConnectors.components.jira.unableToGetIssueTypesMessage": "問題タイプを取得できません", + "xpack.stackConnectors.components.jira.urgencySelectFieldLabel": "問題タイプ", + "xpack.stackConnectors.components.pagerDuty.connectorTypeTitle": "PagerDuty に送信", + "xpack.stackConnectors.components.pagerDuty.apiUrlInvalid": "無効なAPI URL", + "xpack.stackConnectors.components.pagerDuty.apiUrlTextFieldLabel": "API URL(任意)", + "xpack.stackConnectors.components.pagerDuty.classFieldLabel": "クラス(任意)", + "xpack.stackConnectors.components.pagerDuty.componentTextFieldLabel": "コンポーネント(任意)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextFieldLabel": "DedupKey(任意)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextRequiredFieldLabel": "DedupKey", + "xpack.stackConnectors.components.pagerDuty.error.requiredDedupKeyText": "インシデントを解決または確認するときには、DedupKeyが必要です。", + "xpack.stackConnectors.components.pagerDuty.error.requiredRoutingKeyText": "統合キー/ルーティングキーが必要です。", + "xpack.stackConnectors.components.pagerDuty.error.requiredSummaryText": "概要が必要です。", + "xpack.stackConnectors.components.pagerDuty.eventActionSelectFieldLabel": "イベントアクション", + "xpack.stackConnectors.components.pagerDuty.eventSelectAcknowledgeOptionLabel": "承認", + "xpack.stackConnectors.components.pagerDuty.eventSelectResolveOptionLabel": "解決", + "xpack.stackConnectors.components.pagerDuty.eventSelectTriggerOptionLabel": "トリガー", + "xpack.stackConnectors.components.pagerDuty.groupTextFieldLabel": "グループ(任意)", + "xpack.stackConnectors.components.pagerDuty.routingKeyNameHelpLabel": "PagerDuty アカウントを構成します", + "xpack.stackConnectors.components.pagerDuty.routingKeyTextFieldLabel": "統合キー", + "xpack.stackConnectors.components.pagerDuty.selectMessageText": "PagerDuty でイベントを送信します。", + "xpack.stackConnectors.components.pagerDuty.severitySelectCriticalOptionLabel": "重大", + "xpack.stackConnectors.components.pagerDuty.severitySelectErrorOptionLabel": "エラー", + "xpack.stackConnectors.components.pagerDuty.severitySelectFieldLabel": "重要度(任意)", + "xpack.stackConnectors.components.pagerDuty.severitySelectInfoOptionLabel": "情報", + "xpack.stackConnectors.components.pagerDuty.severitySelectWarningOptionLabel": "警告", + "xpack.stackConnectors.components.pagerDuty.sourceTextFieldLabel": "ソース(任意)", + "xpack.stackConnectors.components.pagerDuty.summaryFieldLabel": "まとめ", + "xpack.stackConnectors.components.pagerDuty.timestampTextFieldLabel": "タイムスタンプ(任意)", + "xpack.stackConnectors.components.resilient.connectorTypeTitle": "Resilient", + "xpack.stackConnectors.components.resilient.apiKeyId": "APIキーID", + "xpack.stackConnectors.components.resilient.apiKeySecret": "APIキーシークレット", + "xpack.stackConnectors.components.resilient.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.resilient.commentsTextAreaFieldLabel": "追加のコメント", + "xpack.stackConnectors.components.resilient.descriptionTextAreaFieldLabel": "説明", + "xpack.stackConnectors.components.resilient.nameFieldLabel": "名前(必須)", + "xpack.stackConnectors.components.resilient.orgId": "組織 ID", + "xpack.stackConnectors.components.resilient.requiredNameTextField": "名前が必要です。", + "xpack.stackConnectors.components.resilient.selectMessageText": "IBM Resilient でインシデントを作成します。", + "xpack.stackConnectors.components.resilient.severity": "深刻度", + "xpack.stackConnectors.components.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません", + "xpack.stackConnectors.components.resilient.unableToGetSeverityMessage": "深刻度を取得できません", + "xpack.stackConnectors.components.resilient.urgencySelectFieldLabel": "インシデントタイプ", + "xpack.stackConnectors.components.serverLog.connectorTypeTitle": "サーバーログに送信", + "xpack.stackConnectors.components.serverLog.logLevelFieldLabel": "レベル", + "xpack.stackConnectors.components.serverLog.logMessageFieldLabel": "メッセージ", + "xpack.stackConnectors.components.serverLog.selectMessageText": "Kibana ログにメッセージを追加します。", + "xpack.stackConnectors.components.serviceNow.apiUrlTextFieldLabel": "ServiceNowインスタンスURL", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout": "Elastic ServiceNowアプリがインストールされていません", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.content": "ServiceNowアプリストアに移動し、アプリケーションをインストールしてください", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.errorMessage": "エラーメッセージ", + "xpack.stackConnectors.components.serviceNow.authenticationLabel": "認証", + "xpack.stackConnectors.components.serviceNow.cancelButtonText": "キャンセル", + "xpack.stackConnectors.components.serviceNow.categoryTitle": "カテゴリー", + "xpack.stackConnectors.components.serviceNow.clientIdTextFieldLabel": "クライアントID", + "xpack.stackConnectors.components.serviceNow.clientSecretTextFieldLabel": "クライアントシークレット", + "xpack.stackConnectors.components.serviceNow.commentsTextAreaFieldLabel": "追加のコメント", + "xpack.stackConnectors.components.serviceNow.confirmButtonText": "更新", + "xpack.stackConnectors.components.serviceNow.correlationDisplay": "相関関係表示(オプション)", + "xpack.stackConnectors.components.serviceNow.correlationID": "相関関係ID(オプション)", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutCreate": "または新規作成します。", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutMigrate": "このコネクターを更新します", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutTitle": "このコネクターは廃止予定です", + "xpack.stackConnectors.components.serviceNow.descriptionTextAreaFieldLabel": "説明", + "xpack.stackConnectors.components.serviceNow.eventClassTextAreaFieldLabel": "ソースインスタンス", + "xpack.stackConnectors.components.serviceNow.fetchErrorMsg": "取得できませんでした。ServiceNowインスタンスのURLまたはCORS公正を確認します。", + "xpack.stackConnectors.components.serviceNow.impactSelectFieldLabel": "インパクト", + "xpack.stackConnectors.components.serviceNow.installationCalloutTitle": "このコネクターを使用するには、まずServiceNowアプリストアからElasticアプリをインストールします。", + "xpack.stackConnectors.components.serviceNow.invalidApiUrlTextField": "URL が無効です。", + "xpack.stackConnectors.components.serviceNow.keyIdTextFieldLabel": "JWT VerifierキーID", + "xpack.stackConnectors.components.serviceNow.messageKeyTextAreaFieldLabel": "メッセージキー", + "xpack.stackConnectors.components.serviceNow.metricNameTextAreaFieldLabel": "メトリック名", + "xpack.stackConnectors.components.serviceNow.nodeTextAreaFieldLabel": "ノード", + "xpack.stackConnectors.components.serviceNow.passwordTextFieldLabel": "パスワード", + "xpack.stackConnectors.components.serviceNow.prioritySelectFieldLabel": "優先度", + "xpack.stackConnectors.components.serviceNow.privateKeyPassLabelHelpText": "これは、秘密鍵でパスワードを設定した場合にのみ必要です", + "xpack.stackConnectors.components.serviceNow.privateKeyPassTextFieldLabel": "秘密鍵パスワード", + "xpack.stackConnectors.components.serviceNow.privateKeyTextFieldLabel": "秘密鍵", + "xpack.stackConnectors.components.serviceNow.requiredClientIdTextField": "クライアントIDは必須です。", + "xpack.stackConnectors.components.serviceNow.requiredKeyIdTextField": "JWT VerifierキーIDは必須です。", + "xpack.stackConnectors.components.serviceNow.requiredPrivateKeyTextField": "秘密鍵は必須です。", + "xpack.stackConnectors.components.serviceNow.requiredSeverityTextField": "重要度は必須です。", + "xpack.stackConnectors.components.serviceNow.requiredUserIdentifierTextField": "ユーザーIDは必須です。", + "xpack.stackConnectors.components.serviceNow.requiredUsernameTextField": "ユーザー名が必要です。", + "xpack.stackConnectors.components.serviceNow.resourceTextAreaFieldLabel": "リソース", + "xpack.stackConnectors.components.serviceNow.setupDevInstance": "開発者インスタンスを設定", + "xpack.stackConnectors.components.serviceNow.severityRequiredSelectFieldLabel": "重要度(必須)", + "xpack.stackConnectors.components.serviceNow.severitySelectFieldLabel": "深刻度", + "xpack.stackConnectors.components.serviceNow.snInstanceLabel": "ServiceNowインスタンス", + "xpack.stackConnectors.components.serviceNow.sourceTextAreaFieldLabel": "送信元", + "xpack.stackConnectors.components.serviceNow.subcategoryTitle": "サブカテゴリー", + "xpack.stackConnectors.components.serviceNow.title": "インシデント", + "xpack.stackConnectors.components.serviceNow.titleFieldLabel": "短い説明(必須)", + "xpack.stackConnectors.components.serviceNow.typeTextAreaFieldLabel": "型", + "xpack.stackConnectors.components.serviceNow.unableToGetChoicesMessage": "選択肢を取得できません", + "xpack.stackConnectors.components.serviceNow.unknown": "不明", + "xpack.stackConnectors.components.serviceNow.updateCalloutText": "コネクターが更新されました。", + "xpack.stackConnectors.components.serviceNow.updateFormCredentialsTitle": "認証資格情報を入力", + "xpack.stackConnectors.components.serviceNow.updateFormInstallTitle": "Elastic ServiceNowアプリをインストール", + "xpack.stackConnectors.components.serviceNow.updateFormTitle": "ServiceNowコネクターを更新", + "xpack.stackConnectors.components.serviceNow.updateFormUrlTitle": "ServiceNowインスタンスURLを入力", + "xpack.stackConnectors.components.serviceNow.urgencySelectFieldLabel": "緊急", + "xpack.stackConnectors.components.serviceNow.useOAuth": "OAuth認証を使用", + "xpack.stackConnectors.components.serviceNow.userEmailTextFieldLabel": "ユーザーID", + "xpack.stackConnectors.components.serviceNow.usernameTextFieldLabel": "ユーザー名", + "xpack.stackConnectors.components.serviceNow.visitSNStore": "ServiceNowアプリストアにアクセス", + "xpack.stackConnectors.components.serviceNow.warningMessage": "このコネクターのすべてのインスタンスが更新され、元に戻せません。", + "xpack.stackConnectors.components.serviceNow.correlationIDHelpLabel": "インシデントを更新するID", + "xpack.stackConnectors.components.serviceNowITOM.connectorTypeTitle": "ServiceNow ITOM", + "xpack.stackConnectors.components.serviceNowITOM.event": "イベント", + "xpack.stackConnectors.components.serviceNowITOM.selectMessageText": "ServiceNow ITOMでイベントを作成します。", + "xpack.stackConnectors.components.serviceNowITSM.connectorTypeTitle": "ServiceNow ITSM", + "xpack.stackConnectors.components.serviceNowITSM.selectMessageText": "ServiceNow ITSMでインシデントを作成します。", + "xpack.stackConnectors.components.serviceNowSIR.connectorTypeTitle": "ServiceNow SecOps", + "xpack.stackConnectors.components.serviceNowSIR.selectMessageText": "ServiceNow SecOpsでインシデントを作成します。", + "xpack.stackConnectors.components.serviceNowSIR.title": "セキュリティインシデント", + "xpack.stackConnectors.components.serviceNowSIR.correlationIDHelpLabel": "インシデントを更新するID", + "xpack.stackConnectors.components.slack.connectorTypeTitle": "Slack に送信", + "xpack.stackConnectors.components.slack.error.invalidWebhookUrlText": "Web フック URL が無効です。", + "xpack.stackConnectors.components.slack.messageTextAreaFieldLabel": "メッセージ", + "xpack.stackConnectors.components.slack.selectMessageText": "Slack チャネルにメッセージを送信します。", + "xpack.stackConnectors.components.slack.webhookUrlHelpLabel": "Slack Web フック URL を作成", + "xpack.stackConnectors.components.slack.webhookUrlTextFieldLabel": "Web フック URL", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationFieldsMessage": "アプリケーションフィールドを取得できません", + "xpack.stackConnectors.components.swimlane.connectorTypeTitle": "Swimlaneレコードを作成", + "xpack.stackConnectors.components.swimlane.alertIdFieldLabel": "アラートID", + "xpack.stackConnectors.components.swimlane.apiTokenNameHelpLabel": "Swimlane APIトークンを指定", + "xpack.stackConnectors.components.swimlane.apiTokenTextFieldLabel": "APIトークン", + "xpack.stackConnectors.components.swimlane.apiUrlTextFieldLabel": "API Url", + "xpack.stackConnectors.components.swimlane.appIdTextFieldLabel": "アプリケーションID", + "xpack.stackConnectors.components.swimlane.caseIdFieldLabel": "ケースID", + "xpack.stackConnectors.components.swimlane.caseNameFieldLabel": "ケース名", + "xpack.stackConnectors.components.swimlane.commentsFieldLabel": "コメント", + "xpack.stackConnectors.components.swimlane.configureConnectionLabel": "API接続を構成", + "xpack.stackConnectors.components.swimlane.connectorType": "コネクタータイプ", + "xpack.stackConnectors.components.swimlane.descriptionFieldLabel": "説明", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningDesc": "このコネクターを選択できません。必要なアラートフィールドマッピングがありません。このコネクターを編集して、必要なフィールドマッピングを追加するか、タイプがアラートのコネクターを選択できます。", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningTitle": "このコネクターにはフィールドマッピングがありません。", + "xpack.stackConnectors.components.swimlane.error.requiredAlertID": "アラートIDは必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredAppIdText": "アプリIDは必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredCaseID": "ケースIDは必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredCaseName": "ケース名は必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredComments": "コメントは必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredDescription": "説明が必要です。", + "xpack.stackConnectors.components.swimlane.error.requiredRuleName": "ルール名は必須です。", + "xpack.stackConnectors.components.swimlane.error.requiredSeverity": "重要度は必須です。", + "xpack.stackConnectors.components.swimlane.invalidApiUrlTextField": "URL が無効です。", + "xpack.stackConnectors.components.swimlane.mappingTitleTextFieldLabel": "フィールドマッピングを構成", + "xpack.stackConnectors.components.swimlane.nextStep": "次へ", + "xpack.stackConnectors.components.swimlane.nextStepHelpText": "フィールドマッピングが構成されていない場合、Swimlaneコネクタータイプはすべてに設定されます。", + "xpack.stackConnectors.components.swimlane.prevStep": "戻る", + "xpack.stackConnectors.components.swimlane.ruleNameFieldLabel": "ルール名", + "xpack.stackConnectors.components.swimlane.selectMessageText": "Swimlaneでレコードを作成", + "xpack.stackConnectors.components.swimlane.severityFieldLabel": "深刻度", + "xpack.stackConnectors.components.teams.connectorTypeTitle": "メッセージを Microsoft Teams チャネルに送信します。", + "xpack.stackConnectors.components.teams.error.invalidWebhookUrlText": "Web フック URL が無効です。", + "xpack.stackConnectors.components.teams.error.requiredMessageText": "メッセージが必要です。", + "xpack.stackConnectors.components.teams.error.webhookUrlTextLabel": "Web フック URL", + "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "メッセージ", + "xpack.stackConnectors.components.teams.selectMessageText": "メッセージを Microsoft Teams チャネルに送信します。", + "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Microsoft Teams Web フック URL を作成", + "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Web フックデータ", + "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "ヘッダーを追加", + "xpack.stackConnectors.components.webhook.authenticationLabel": "認証", + "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "コードエディター", + "xpack.stackConnectors.components.webhook.bodyFieldLabel": "本文", + "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL が無効です。", + "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "この Web フックの認証が必要です", + "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "キー", + "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "値", + "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "メソド", + "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "パスワード", + "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "キー", + "xpack.stackConnectors.components.webhook.selectMessageText": "Web サービスにリクエストを送信してください。", + "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", + "xpack.stackConnectors.components.webhook.userTextFieldLabel": "ユーザー名", + "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "HTTP ヘッダーを追加", + "xpack.stackConnectors.components.xmatters.connectorTypeTitle": "xMattersデータ", + "xpack.stackConnectors.components.xmatters.authenticationLabel": "認証", + "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本認証", + "xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本認証", + "xpack.stackConnectors.components.xmatters.connectorSettingsLabel": "xMattersトリガーを設定するときに使用される認証方法を選択します。", + "xpack.stackConnectors.components.xmatters.error.invalidUrlTextField": "URL が無効です。", + "xpack.stackConnectors.components.xmatters.error.invalidUsernameTextField": "ユーザー名が無効です。", + "xpack.stackConnectors.components.xmatters.error.requiredUrlText": "URL が必要です。", + "xpack.stackConnectors.components.xmatters.initiationUrlHelpText": "完全なxMatters URLを含めます。", + "xpack.stackConnectors.components.xmatters.passwordTextFieldLabel": "パスワード", + "xpack.stackConnectors.components.xmatters.selectMessageText": "xMattersワークフローをトリガーします。", + "xpack.stackConnectors.components.xmatters.severity": "深刻度", + "xpack.stackConnectors.components.xmatters.severitySelectCriticalOptionLabel": "重大", + "xpack.stackConnectors.components.xmatters.severitySelectHighOptionLabel": "高", + "xpack.stackConnectors.components.xmatters.severitySelectLowOptionLabel": "低", + "xpack.stackConnectors.components.xmatters.severitySelectMediumOptionLabel": "中", + "xpack.stackConnectors.components.xmatters.severitySelectMinimalOptionLabel": "最小", + "xpack.stackConnectors.components.xmatters.tags": "タグ", + "xpack.stackConnectors.components.xmatters.urlAuthLabel": "URL認証", + "xpack.stackConnectors.components.xmatters.urlLabel": "開始URL", + "xpack.stackConnectors.components.xmatters.userCredsLabel": "ユーザー認証情報", + "xpack.stackConnectors.components.xmatters.userTextFieldLabel": "ユーザー名", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningDesc": "コメントを外部で共有するには、コネクターの[コメントURLを作成]および[コメントオブジェクトを作成]フィールドを構成します。", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningTitle": "ケースコメントを共有できません", + "xpack.stackConnectors.components.serviceNow.deprecatedTooltipTitle": "廃止予定のコネクター", + "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "ユーザー名が必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "コメントメソッドを作成は必須です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentResponseKeyText": "ケース対応の作成ケースIDキーが必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateMethodText": "ケースメソッドの作成は必須です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseCreatedKeyText": "ケース対応の取得の作成日キーが必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseExternalTitleKeyText": "ケース対応の取得の外部タイトルキーが必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseUpdatedKeyText": "ケース対応の取得の更新日キーが必要です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentViewUrlKeyText": "ケースURLの表示は必須です。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateMethodText": "ケースメソッドの更新は必須です。", + "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "ユーザー名が必要です。", + "xpack.stackConnectors.components.webhook.error.requiredMethodText": "メソッドが必要です。", + "xpack.stackConnectors.components.email.addBccButton": "Bcc", + "xpack.stackConnectors.components.email.addCcButton": "Cc", + "xpack.stackConnectors.components.email.authenticationLabel": "認証", + "xpack.stackConnectors.components.email.clientIdFieldLabel": "クライアントID", + "xpack.stackConnectors.components.email.clientSecretTextFieldLabel": "クライアントシークレット", + "xpack.stackConnectors.components.email.fromTextFieldLabel": "送信元", + "xpack.stackConnectors.components.email.hasAuthSwitchLabel": "このサーバーの認証が必要です", + "xpack.stackConnectors.components.email.hostTextFieldLabel": "ホスト", + "xpack.stackConnectors.components.email.messageTextAreaFieldLabel": "メッセージ", + "xpack.stackConnectors.components.email.passwordFieldLabel": "パスワード", + "xpack.stackConnectors.components.email.portTextFieldLabel": "ポート", + "xpack.stackConnectors.components.email.recipientBccTextFieldLabel": "Bcc", + "xpack.stackConnectors.components.email.recipientCopyTextFieldLabel": "Cc", + "xpack.stackConnectors.components.email.recipientTextFieldLabel": "終了:", + "xpack.stackConnectors.components.email.secureSwitchLabel": "セキュア", + "xpack.stackConnectors.components.email.serviceTextFieldLabel": "サービス", + "xpack.stackConnectors.components.email.subjectTextFieldLabel": "件名", + "xpack.stackConnectors.components.email.tenantIdFieldLabel": "テナントID", + "xpack.stackConnectors.components.email.userTextFieldLabel": "ユーザー名", + "xpack.stackConnectors.components.index.resetDefaultIndexLabel": "デフォルトのインデックスをリセット", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "{fieldCandidatesCount, plural, other {# 個のフィールド候補}}が特定されました。", - "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "{fieldValuePairsCount, plural, other {# 個の重要なフィールド/値のペア}}が特定されました。", - "xpack.aiops.index.dataLoader.internalServerErrorMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。リクエストがタイムアウトした可能性があります。小さなサンプルサイズを使うか、時間範囲を狭めてみてください。", "xpack.aiops.index.dataViewNotBasedOnTimeSeriesNotificationTitle": "データビュー{dataViewTitle}は時系列に基づいていません", "xpack.aiops.index.errorLoadingDataMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。", "xpack.aiops.progressTitle": "進行状況:{progress}% — {progressMessage}", @@ -31804,20 +32178,7 @@ "xpack.triggersActionsUI.actionVariables.legacyTagsLabel": "{variable}の導入により、これは廃止される予定です。", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage": "このコネクターには {minimumLicenseRequired} ライセンスが必要です。", "xpack.triggersActionsUI.checkRuleTypeEnabled.ruleTypeDisabledByLicenseMessage": "このルールタイプには{minimumLicenseRequired}ライセンスが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.missingVariables": "必須の{variableCount, plural, other {個の変数}}が見つかりません:{variables}", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideValue": "アラート履歴インデックスの先頭は\"{alertHistoryPrefix}\"でなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidEmail": "電子メールアドレス{email}が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.notAllowed": "電子メールアドレス{email}が許可されていません。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexHelpText": "ドキュメントは{alertHistoryIndex}インデックスにインデックスされます。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueMessage": "ID {id}の問題を取得できません", "xpack.triggersActionsUI.components.builtinActionTypes.missingSecretsValuesLabel": "機密情報はインポートされません。次のフィールド{encryptedFieldsLength, plural, other {}}の値{encryptedFieldsLength, plural, other {}} {secretFieldsLabel}を入力してください。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestamp": "タイムスタンプは、{nowShortFormat}や{nowLongFormat}などの有効な日付でなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiInfoError": "アプリケーション情報の取得を試みるときの受信ステータス:{status}", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.appInstallationInfo": "{update} {create} ", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateSuccessToastTitle": "{connectorName}コネクターが更新されました", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.apiUrlHelpLabel": "任意のServiceNowインスタンスへの完全なURLを入力します。ない場合は、{instance}。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.serviceNowAppRunning": "更新を実行する前に、ServiceNowアプリストアからElasticアプリをインストールする必要があります。アプリをインストールするには、{visitLink}", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationMessage": "id {id}のアプリケーションフィールドを取得できません", "xpack.triggersActionsUI.components.buttonGroupField.error.requiredField": "{label}が必要です。", "xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText": "{numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}を削除できませんでした", "xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText": "{numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}を削除しました", @@ -31972,340 +32333,6 @@ "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "ルール変数を追加", "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "変数を追加", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "アラート検索でエラーが発生しました", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.commentsTextAreaFieldLabel": "追加のコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.descriptionTextAreaFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.tagsFieldLabel": "タグ", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.titleFieldLabel": "概要(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.actionTypeTitle": "Webフック - ケース管理データ", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addHeaderButton": "追加", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addVariable": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseCommentDesc": "Kibanaケースコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseDescriptionDesc": "Kibanaケース説明", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTagsDesc": "Kibanaケースタグ", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTitleDesc": "Kibanaケースタイトル", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonHelp": "コメントを作成するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonTextFieldLabel": "コメントオブジェクトを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentMethodTextFieldLabel": "コメントメソッドを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlHelp": "コメントをケースに追加するためのAPI URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlTextFieldLabel": "コメントURLを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonHelpText": "ケースを作成するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonTextFieldLabel": "ケースオブジェクトを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentMethodTextFieldLabel": "ケースメソッドを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyHelpText": "外部ケースIDを含むケース対応の作成のJSONキー", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyTextFieldLabel": "ケース対応ケースキーを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentUrlTextFieldLabel": "ケースURLを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.deleteHeaderButton": "削除", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.docLink": "Webフックの構成 - ケース管理コネクター。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentIncidentText": "コメントオブジェクトの作成は有効なJSONでなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentUrlText": "コメントURLの作成はURL形式でなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateIncidentText": "ケースオブジェクトの作成は必須であり、有効なJSONでなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateUrlText": "ケースURLの作成は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredGetIncidentUrlText": "ケースURLの取得は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateIncidentText": "ケースオブジェクトの更新は必須であり、有効なJSONでなければなりません。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateUrlText": "ケースURLの更新は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalIdDesc": "外部システムID", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalTitleDesc": "外部システムタイトル", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyHelp": "外部ケースタイトルを含むケース対応の取得のJSONキー", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyTextFieldLabel": "ケース対応の取得の外部タイトルキー", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlHelp": "外部システムからケース詳細JSONを取得するAPI URL。変数セレクターを使用して、外部システムIDをURLに追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlTextFieldLabel": "ケースURLを取得", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.hasAuthSwitchLabel": "この Web フックの認証が必要です", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.httpHeadersTitle": "使用中のヘッダー", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonCodeEditorAriaLabel": "コードエディター", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonFieldLabel": "JSON", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.keyTextFieldLabel": "キー", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.next": "次へ", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.passwordTextFieldLabel": "パスワード", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.previous": "前へ", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.selectMessageText": "ケース管理Webサービスにリクエストを送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step1": "コネクターを設定", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2": "ケースを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2Description": "外部システムでケースを作成するフィールドを設定します。必要なフィールドを判断するには、サービスのAPIドキュメントを確認してください", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3": "ケース情報を取得", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3Description": "外部システムでコメントをケースに追加するフィールドを設定します。一部のシステムでは、ケースでの更新の作成と同じメソッドの場合があります。必要なフィールドを判断するには、サービスのAPIドキュメントを確認してください。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4": "コメントと更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4a": "ケースで更新を作成", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4aDescription": "外部システムでケースの更新を作成するフィールドを設定します。一部のシステムでは、ケースへのコメントの追加と同じメソッドの場合があります。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4b": "ケースでコメントを追加", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4bDescription": "外部システムでコメントをケースに追加するフィールドを設定します。一部のシステムでは、ケースでの更新の作成と同じメソッドの場合があります。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonHelpl": "ケースを更新するJSONオブジェクト。変数セレクターを使用して、ケースデータをペイロードに追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonTextFieldLabel": "ケースオブジェクトを更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentMethodTextFieldLabel": "ケースメソッドを更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlHelp": "ケースを更新するAPI URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlTextFieldLabel": "ケースURLを更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.userTextFieldLabel": "ユーザー名", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.valueTextFieldLabel": "値", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewHeadersSwitch": "HTTP ヘッダーを追加", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlHelp": "外部システムでケースを表示するURL。変数セレクターを使用して、外部システムIDまたは外部システムタイトルをURLに追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlTextFieldLabel": "外部ケース表示URL", - "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField": "短い説明が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientIdHelpLabel": "クライアントIDの構成", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientSecretHelpLabel": "クライアントシークレットの構成", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.tenantIdHelpLabel": "テナントIDの構成", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "メールに送信", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.amazonSesServerTypeLabel": "Amazon SES", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.configureAccountsHelpLabel": "電子メールアカウントの構成", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.elasticCloudServerTypeLabel": "Elastic Cloud", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.exchangeServerTypeLabel": "MS Exchange Server", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.gmailServerTypeLabel": "Gmail", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.otherServerTypeLabel": "その他", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.outlookServerTypeLabel": "Outlook", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "サーバーからメールを送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.updateErrorNotificationText": "サービス構成を取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideSuffix": "アラート履歴インデックスには有効なサフィックスを含める必要があります。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidPortText": "ポートが無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthUserNameText": "ユーザー名が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredClientIdText": "クライアントIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredDocumentJson": "ドキュメントが必要です。有効なJSONオブジェクトにしてください。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText": "To、Cc、または Bcc のエントリーがありません。 1 つ以上のエントリーが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText": "送信元が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText": "ホストが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText": "メッセージが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText": "ポートが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServerLogMessageText": "メッセージが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText": "サービスは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText": "メッセージが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText": "件名が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredTenantIdText": "テナントIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText": "本文が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookSummaryText": "タイトルが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.actionTypeTitle": "データをインデックスする", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "選択…", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.configureIndexHelpLabel": "インデックスコネクターを構成しています。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.connectorSectionTitle": "インデックスを書き出す", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip": "この時間フィールドをドキュメントにインデックスが作成された時刻に設定します。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel": "各ドキュメントの時刻フィールドを定義", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel": "インデックスするドキュメント", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.error.notValidIndexText": "インデックスは有効ではありません。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.executionTimeFieldLabel": "時間フィールド", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.howToBroadenSearchQueryDescription": "* で検索クエリの範囲を広げます。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indexDocumentHelpLabel": "インデックスドキュメントの例。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesAndIndexPatternsLabel": "データビューに基づく", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesToQueryLabel": "インデックス", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel": "コードエディター", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndex": "Elasticsearchインデックス", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexDocLink": "ドキュメントを表示します。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshLabel": "更新インデックス", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip": "影響を受けるシャードを更新し、この処理を検索できるようにします。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText": "データを Elasticsearch にインデックスしてください。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.actionTypeTitle": "Jira", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiTokenTextFieldLabel": "APIトークン", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.commentsTextAreaFieldLabel": "追加のコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.descriptionTextAreaFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.emailTextFieldLabel": "メールアドレス", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.impactSelectFieldLabel": "ラベル", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.labelsSpacesErrorMessage": "ラベルにはスペースを使用できません。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.parentIssueSearchLabel": "親問題", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.projectKey": "プロジェクトキー", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredSummaryTextField": "概要が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "入力して検索", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxPlaceholder": "入力して検索", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesLoading": "読み込み中...", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.selectMessageText": "Jira でインシデントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.severitySelectFieldLabel": "優先度", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.summaryFieldLabel": "概要(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetFieldsMessage": "フィールドを取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssuesMessage": "問題を取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueTypesMessage": "問題タイプを取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.urgencySelectFieldLabel": "問題タイプ", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle": "PagerDuty に送信", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlInvalid": "無効なAPI URL", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlTextFieldLabel": "API URL(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel": "クラス(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.componentTextFieldLabel": "コンポーネント(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextFieldLabel": "DedupKey(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextRequiredFieldLabel": "DedupKey", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredDedupKeyText": "インシデントを解決または確認するときには、DedupKeyが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredRoutingKeyText": "統合キー/ルーティングキーが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredSummaryText": "概要が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventActionSelectFieldLabel": "イベントアクション", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectAcknowledgeOptionLabel": "承認", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectResolveOptionLabel": "解決", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectTriggerOptionLabel": "トリガー", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.groupTextFieldLabel": "グループ(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyNameHelpLabel": "PagerDuty アカウントを構成します", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyTextFieldLabel": "統合キー", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText": "PagerDuty でイベントを送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectCriticalOptionLabel": "重大", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectErrorOptionLabel": "エラー", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectFieldLabel": "重要度(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectInfoOptionLabel": "情報", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectWarningOptionLabel": "警告", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.sourceTextFieldLabel": "ソース(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel": "まとめ", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel": "タイムスタンプ(任意)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.actionTypeTitle": "Resilient", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeyId": "APIキーID", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeySecret": "APIキーシークレット", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.commentsTextAreaFieldLabel": "追加のコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.descriptionTextAreaFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.nameFieldLabel": "名前(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.orgId": "組織 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField": "名前が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.selectMessageText": "IBM Resilient でインシデントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.severity": "深刻度", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetSeverityMessage": "深刻度を取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.urgencySelectFieldLabel": "インシデントタイプ", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle": "サーバーログに送信", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "レベル", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "メッセージ", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "Kibana ログにメッセージを追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel": "ServiceNowインスタンスURL", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout": "Elastic ServiceNowアプリがインストールされていません", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.content": "ServiceNowアプリストアに移動し、アプリケーションをインストールしてください", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.errorMessage": "エラーメッセージ", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.cancelButtonText": "キャンセル", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.categoryTitle": "カテゴリー", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientIdTextFieldLabel": "クライアントID", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientSecretTextFieldLabel": "クライアントシークレット", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.commentsTextAreaFieldLabel": "追加のコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.confirmButtonText": "更新", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationDisplay": "相関関係表示(オプション)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationID": "相関関係ID(オプション)", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutCreate": "または新規作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutMigrate": "このコネクターを更新します", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutTitle": "このコネクターは廃止予定です", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.descriptionTextAreaFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.eventClassTextAreaFieldLabel": "ソースインスタンス", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.fetchErrorMsg": "取得できませんでした。ServiceNowインスタンスのURLまたはCORS公正を確認します。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.impactSelectFieldLabel": "インパクト", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.installationCalloutTitle": "このコネクターを使用するには、まずServiceNowアプリストアからElasticアプリをインストールします。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField": "URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.keyIdTextFieldLabel": "JWT VerifierキーID", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.messageKeyTextAreaFieldLabel": "メッセージキー", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.metricNameTextAreaFieldLabel": "メトリック名", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.nodeTextAreaFieldLabel": "ノード", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel": "パスワード", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.prioritySelectFieldLabel": "優先度", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassLabelHelpText": "これは、秘密鍵でパスワードを設定した場合にのみ必要です", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassTextFieldLabel": "秘密鍵パスワード", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyTextFieldLabel": "秘密鍵", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredClientIdTextField": "クライアントIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredKeyIdTextField": "JWT VerifierキーIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPrivateKeyTextField": "秘密鍵は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredSeverityTextField": "重要度は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUserIdentifierTextField": "ユーザーIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField": "ユーザー名が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.resourceTextAreaFieldLabel": "リソース", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.setupDevInstance": "開発者インスタンスを設定", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severityRequiredSelectFieldLabel": "重要度(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectFieldLabel": "深刻度", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.snInstanceLabel": "ServiceNowインスタンス", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.sourceTextAreaFieldLabel": "送信元", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.subcategoryTitle": "サブカテゴリー", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.title": "インシデント", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.titleFieldLabel": "短い説明(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.typeTextAreaFieldLabel": "型", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unableToGetChoicesMessage": "選択肢を取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unknown": "不明", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateCalloutText": "コネクターが更新されました。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormCredentialsTitle": "認証資格情報を入力", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormInstallTitle": "Elastic ServiceNowアプリをインストール", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormTitle": "ServiceNowコネクターを更新", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormUrlTitle": "ServiceNowインスタンスURLを入力", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.urgencySelectFieldLabel": "緊急", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.useOAuth": "OAuth認証を使用", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.userEmailTextFieldLabel": "ユーザーID", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel": "ユーザー名", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.visitSNStore": "ServiceNowアプリストアにアクセス", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.warningMessage": "このコネクターのすべてのインスタンスが更新され、元に戻せません。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.correlationIDHelpLabel": "インシデントを更新するID", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.actionTypeTitle": "ServiceNow ITOM", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowITOM.event": "イベント", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.selectMessageText": "ServiceNow ITOMでイベントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.actionTypeTitle": "ServiceNow ITSM", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.selectMessageText": "ServiceNow ITSMでインシデントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.actionTypeTitle": "ServiceNow SecOps", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.selectMessageText": "ServiceNow SecOpsでインシデントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowSIR.title": "セキュリティインシデント", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIRAction.correlationIDHelpLabel": "インシデントを更新するID", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "Slack に送信", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.invalidWebhookUrlText": "Web フック URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel": "メッセージ", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText": "Slack チャネルにメッセージを送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlHelpLabel": "Slack Web フック URL を作成", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel": "Web フック URL", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationFieldsMessage": "アプリケーションフィールドを取得できません", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle": "Swimlaneレコードを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.alertIdFieldLabel": "アラートID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenNameHelpLabel": "Swimlane APIトークンを指定", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenTextFieldLabel": "APIトークン", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiUrlTextFieldLabel": "API Url", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.appIdTextFieldLabel": "アプリケーションID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseIdFieldLabel": "ケースID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseNameFieldLabel": "ケース名", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.commentsFieldLabel": "コメント", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.configureConnectionLabel": "API接続を構成", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.connectorType": "コネクタータイプ", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.descriptionFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningDesc": "このコネクターを選択できません。必要なアラートフィールドマッピングがありません。このコネクターを編集して、必要なフィールドマッピングを追加するか、タイプがアラートのコネクターを選択できます。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningTitle": "このコネクターにはフィールドマッピングがありません。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAlertID": "アラートIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAppIdText": "アプリIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseID": "ケースIDは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseName": "ケース名は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredComments": "コメントは必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredDescription": "説明が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredRuleName": "ルール名は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredSeverity": "重要度は必須です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.invalidApiUrlTextField": "URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.mappingTitleTextFieldLabel": "フィールドマッピングを構成", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStep": "次へ", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStepHelpText": "フィールドマッピングが構成されていない場合、Swimlaneコネクタータイプはすべてに設定されます。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.prevStep": "戻る", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.ruleNameFieldLabel": "ルール名", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText": "Swimlaneでレコードを作成", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.severityFieldLabel": "深刻度", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.actionTypeTitle": "メッセージを Microsoft Teams チャネルに送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.invalidWebhookUrlText": "Web フック URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requiredMessageText": "メッセージが必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.webhookUrlTextLabel": "Web フック URL", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.messageTextAreaFieldLabel": "メッセージ", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.selectMessageText": "メッセージを Microsoft Teams チャネルに送信します。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlHelpLabel": "Microsoft Teams Web フック URL を作成", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle": "Web フックデータ", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButtonLabel": "ヘッダーを追加", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyCodeEditorAriaLabel": "コードエディター", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyFieldLabel": "本文", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.invalidUrlTextField": "URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.hasAuthSwitchLabel": "この Web フックの認証が必要です", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerKeyTextFieldLabel": "キー", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerValueTextFieldLabel": "値", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.methodTextFieldLabel": "メソド", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.passwordTextFieldLabel": "パスワード", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.removeHeaderIconLabel": "キー", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.selectMessageText": "Web サービスにリクエストを送信してください。", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.urlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.userTextFieldLabel": "ユーザー名", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch": "HTTP ヘッダーを追加", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.actionTypeTitle": "xMattersデータ", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthButtonGroupLegend": "基本認証", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthLabel": "基本認証", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.connectorSettingsLabel": "xMattersトリガーを設定するときに使用される認証方法を選択します。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUrlTextField": "URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUsernameTextField": "ユーザー名が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.requiredUrlText": "URL が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.initiationUrlHelpText": "完全なxMatters URLを含めます。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.passwordTextFieldLabel": "パスワード", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.selectMessageText": "xMattersワークフローをトリガーします。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severity": "深刻度", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectCriticalOptionLabel": "重大", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectHighOptionLabel": "高", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectLowOptionLabel": "低", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMediumOptionLabel": "中", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMinimalOptionLabel": "最小", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.tags": "タグ", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlAuthLabel": "URL認証", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlLabel": "開始URL", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userCredsLabel": "ユーザー認証情報", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userTextFieldLabel": "ユーザー名", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorButtonLabel": "コネクターを作成", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyBody": "Kibanaで実行するメール、Slack、Elasticsearch、およびサードパーティサービスを構成します。", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyTitle": "初めてのコネクターを作成する", @@ -32327,8 +32354,6 @@ "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorMessage": "エディターが見つかりませんでした。ページを更新して再試行してください", "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorTitle": "メッセージ変数を追加できません", "xpack.triggersActionsUI.components.simpleConnectorForm.secrets.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningDesc": "コメントを外部で共有するには、コネクターの[コメントURLを作成]および[コメントオブジェクトを作成]フィールドを構成します。", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningTitle": "ケースコメントを共有できません", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "コネクター", "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]が[dateEnd]よりも大です", "xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval]:[dateStart]が[dateEnd]と等しくない場合に指定する必要があります", @@ -32401,7 +32426,6 @@ "xpack.triggersActionsUI.sections.actionConnectorForm.loadingConnectorSettingsDescription": "コネクター設定を読み込んでいます...", "xpack.triggersActionsUI.sections.actionForm.actionSectionsTitle": "アクション", "xpack.triggersActionsUI.sections.actionForm.addActionButtonLabel": "アクションの追加", - "xpack.triggersActionsUI.sections.actionForm.deprecatedTooltipTitle": "廃止予定のコネクター", "xpack.triggersActionsUI.sections.actionForm.getMoreConnectorsTitle": "その他のコネクターを取得", "xpack.triggersActionsUI.sections.actionForm.getMoreRuleTypesTitle": "その他のルールタイプを取得", "xpack.triggersActionsUI.sections.actionForm.incidentManagementSystemLabel": "インシデント管理システム", @@ -32440,17 +32464,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.actionErrorToolTip": "アクションにはエラーがあります。", "xpack.triggersActionsUI.sections.actionTypeForm.actionRunWhenInActionGroup": "次のときに実行", "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "コネクターの追加", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredAuthUserNameText": "ユーザー名が必要です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateCommentMethodText": "コメントメソッドを作成は必須です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateIncidentResponseKeyText": "ケース対応の作成ケースIDキーが必要です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateMethodText": "ケースメソッドの作成は必須です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseCreatedKeyText": "ケース対応の取得の作成日キーが必要です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseExternalTitleKeyText": "ケース対応の取得の外部タイトルキーが必要です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseUpdatedKeyText": "ケース対応の取得の更新日キーが必要です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentViewUrlKeyText": "ケースURLの表示は必須です。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredUpdateMethodText": "ケースメソッドの更新は必須です。", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthUserNameText": "ユーザー名が必要です。", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText": "メソッドが必要です。", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "コネクターを選択", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", "xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel": "キャンセル", @@ -32460,25 +32473,6 @@ "xpack.triggersActionsUI.sections.alertsTable.alertsFlyout.reason": "理由", "xpack.triggersActionsUI.sections.alertsTable.column.actions": "アクション", "xpack.triggersActionsUI.sections.alertsTable.leadingControl.viewDetails": "詳細を表示", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addBccButton": "Bcc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addCcButton": "Cc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.authenticationLabel": "認証", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientIdFieldLabel": "クライアントID", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientSecretTextFieldLabel": "クライアントシークレット", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.fromTextFieldLabel": "送信元", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hasAuthSwitchLabel": "このサーバーの認証が必要です", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hostTextFieldLabel": "ホスト", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.messageTextAreaFieldLabel": "メッセージ", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel": "パスワード", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.portTextFieldLabel": "ポート", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel": "Bcc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel": "Cc", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientTextFieldLabel": "終了:", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.secureSwitchLabel": "セキュア", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.serviceTextFieldLabel": "サービス", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel": "件名", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.tenantIdFieldLabel": "テナントID", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "ユーザー名", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseCancelButtonText": "キャンセル", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseConfirmButtonText": "変更を破棄", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseMessage": "保存されていない変更は回復できません。", @@ -32668,7 +32662,6 @@ "xpack.triggersActionsUI.sections.rulesList.removeAllButton": "すべて削除", "xpack.triggersActionsUI.sections.rulesList.removeCancelButton": "キャンセル", "xpack.triggersActionsUI.sections.rulesList.removeConfirmButton": "すべて削除", - "xpack.triggersActionsUI.sections.rulesList.resetDefaultIndexLabel": "デフォルトのインデックスをリセット", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDecrypting": "ルールの復号中にエラーが発生しました。", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDisabled": "ルールを実行できませんでした。ルールは無効化された後に実行されました。", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonLicense": "ルールを実行できません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0f15a16ae14ac..5ade45168ae73 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6378,6 +6378,383 @@ "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "usesBasic 为 true 时不得提供 secretsUrl", "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "usesBasic 为 false 时不得提供用户名和密码", "xpack.stackConnectors.xmatters.title": "xMatters", + "xpack.stackConnectors.components.casesWebhook.error.missingVariables": "缺少所需{variableCount, plural, other {变量}}:{variables}", + "xpack.stackConnectors.components.index.error.badIndexOverrideValue": "告警历史记录索引必须以“{alertHistoryPrefix}”开头。", + "xpack.stackConnectors.components.email.error.invalidEmail": "电子邮件地址 {email} 无效。", + "xpack.stackConnectors.components.email.error.notAllowed": "不允许使用电子邮件地址 {email}。", + "xpack.stackConnectors.components.index.preconfiguredIndexHelpText": "文档已索引到 {alertHistoryIndex} 索引中。", + "xpack.stackConnectors.components.jira.unableToGetIssueMessage": "无法获取 ID 为 {id} 的问题", + "xpack.stackConnectors.components.pagerDuty.error.invalidTimestamp": "时间戳必须是有效的日期,例如 {nowShortFormat} 或 {nowLongFormat}。", + "xpack.stackConnectors.components.serviceNow.apiInfoError": "尝试获取应用程序信息时收到的状态:{status}", + "xpack.stackConnectors.components.serviceNow.appInstallationInfo": "{update} {create} ", + "xpack.stackConnectors.components.serviceNow.updateSuccessToastTitle": "{connectorName} 连接器已更新", + "xpack.stackConnectors.components.serviceNow.apiUrlHelpLabel": "提供所需 ServiceNow 实例的完整 URL。如果没有,{instance}。", + "xpack.stackConnectors.components.serviceNow.appRunning": "在运行更新之前,必须从 ServiceNow 应用商店安装 Elastic 应用。{visitLink} 以安装该应用", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationMessage": "无法获取 ID 为 {id} 的应用程序", + "xpack.stackConnectors.components.casesWebhook.commentsTextAreaFieldLabel": "其他注释", + "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "描述", + "xpack.stackConnectors.components.casesWebhook.tagsFieldLabel": "标签", + "xpack.stackConnectors.components.casesWebhook.titleFieldLabel": "摘要(必填)", + "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "添加", + "xpack.stackConnectors.components.casesWebhook.addVariable": "添加变量", + "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "身份验证", + "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibana 案例注释", + "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibana 案例描述", + "xpack.stackConnectors.components.casesWebhook.caseTagsDesc": "Kibana 案例标签", + "xpack.stackConnectors.components.casesWebhook.caseTitleDesc": "Kibana 案例标题", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonHelp": "用于创建注释的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", + "xpack.stackConnectors.components.casesWebhook.createCommentJsonTextFieldLabel": "创建注释对象", + "xpack.stackConnectors.components.casesWebhook.createCommentMethodTextFieldLabel": "创建注释方法", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlHelp": "用于添加注释到案例的 API URL。", + "xpack.stackConnectors.components.casesWebhook.createCommentUrlTextFieldLabel": "创建注释 URL", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonHelpText": "用于创建案例的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", + "xpack.stackConnectors.components.casesWebhook.createIncidentJsonTextFieldLabel": "创建案例对象", + "xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel": "创建案例方法", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyHelpText": "创建案例响应中包含外部案例 ID 的 JSON 键", + "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "创建案例响应案例键", + "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "创建案例 URL", + "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "删除", + "xpack.stackConnectors.components.casesWebhook.docLink": "正在配置 Webhook - 案例管理连接器。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "创建注释对象必须为有效 JSON。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "创建注释 URL 必须为 URL 格式。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentText": "“创建案例对象”必填并且必须为有效 JSON。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateUrlText": "“创建案例 URL”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentUrlText": "“获取案例 URL”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateIncidentText": "“更新案例对象”必填并且必须为有效 JSON。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateUrlText": "“更新案例 URL”必填。", + "xpack.stackConnectors.components.casesWebhook.externalIdDesc": "外部系统 ID", + "xpack.stackConnectors.components.casesWebhook.externalTitleDesc": "外部系统标题", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyHelp": "获取案例响应中包含外部案例标题的 JSON 键", + "xpack.stackConnectors.components.casesWebhook.getIncidentResponseExternalTitleKeyTextFieldLabel": "获取案例响应外部标题键", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlHelp": "用于从外部系统中获取案例详情 JSON 的 API URL。使用变量选择器添加外部系统 ID 到 URL。", + "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "获取案例 URL", + "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证", + "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "在用的标头", + "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "代码编辑器", + "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", + "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "钥匙", + "xpack.stackConnectors.components.casesWebhook.next": "下一步", + "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "密码", + "xpack.stackConnectors.components.casesWebhook.previous": "上一步", + "xpack.stackConnectors.components.casesWebhook.selectMessageText": "发送请求到案例管理 Web 服务。", + "xpack.stackConnectors.components.casesWebhook.step1": "设置连接器", + "xpack.stackConnectors.components.casesWebhook.step2": "创建案例", + "xpack.stackConnectors.components.casesWebhook.step2Description": "设置字段以在外部系统中创建案例。查阅您服务的 API 文档以了解需要哪些字段", + "xpack.stackConnectors.components.casesWebhook.step3": "获取案例信息", + "xpack.stackConnectors.components.casesWebhook.step3Description": "设置字段以在外部系统中添加案例注释。对于某些系统,这可能采取与在案例中创建更新相同的方法。查阅您服务的 API 文档以了解需要哪些字段。", + "xpack.stackConnectors.components.casesWebhook.step4": "注释和更新", + "xpack.stackConnectors.components.casesWebhook.step4a": "在案例中创建更新", + "xpack.stackConnectors.components.casesWebhook.step4aDescription": "设置字段以在外部系统中创建案例更新。对于某些系统,这可能采取与添加案例注释相同的方法。", + "xpack.stackConnectors.components.casesWebhook.step4b": "在案例中添加注释", + "xpack.stackConnectors.components.casesWebhook.step4bDescription": "设置字段以在外部系统中添加案例注释。对于某些系统,这可能采取与在案例中创建更新相同的方法。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonHelpl": "用于更新案例的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentJsonTextFieldLabel": "更新案例对象", + "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "更新案例方法", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "用于更新案例的 API URL。", + "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "更新案例 URL", + "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "用户名", + "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "值", + "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "添加 HTTP 标头", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "用于查看外部系统中的案例的 URL。使用变量选择器添加外部系统 ID 或外部系统标题到 URL。", + "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部案例查看 URL", + "xpack.stackConnectors.components.serviceNow.requiredShortDescTextField": "“简短描述”必填。", + "xpack.stackConnectors.components.email.exchangeForm.clientIdHelpLabel": "配置客户端 ID", + "xpack.stackConnectors.components.email.exchangeForm.clientSecretHelpLabel": "配置客户端密钥", + "xpack.stackConnectors.components.email.exchangeForm.tenantIdHelpLabel": "配置租户 ID", + "xpack.stackConnectors.components.email.connectorTypeTitle": "发送到电子邮件", + "xpack.stackConnectors.components.email.amazonSesServerTypeLabel": "Amazon SES", + "xpack.stackConnectors.components.email.configureAccountsHelpLabel": "配置电子邮件帐户", + "xpack.stackConnectors.components.email.elasticCloudServerTypeLabel": "Elastic Cloud", + "xpack.stackConnectors.components.email.exchangeServerTypeLabel": "MS Exchange Server", + "xpack.stackConnectors.components.email.gmailServerTypeLabel": "Gmail", + "xpack.stackConnectors.components.email.otherServerTypeLabel": "其他", + "xpack.stackConnectors.components.email.outlookServerTypeLabel": "Outlook", + "xpack.stackConnectors.components.email.selectMessageText": "从您的服务器发送电子邮件。", + "xpack.stackConnectors.components.email.updateErrorNotificationText": "无法获取服务配置", + "xpack.stackConnectors.components.index.error.badIndexOverrideSuffix": "告警历史记录索引必须包含有效的后缀。", + "xpack.stackConnectors.components.email.error.invalidPortText": "端口无效。", + "xpack.stackConnectors.components.email.error.requiredAuthUserNameText": "“用户名”必填。", + "xpack.stackConnectors.components.email.error.requiredClientIdText": "“客户端 ID”必填。", + "xpack.stackConnectors.components.index.error.requiredDocumentJson": "“文档”必填,并且应为有效的 JSON 对象。", + "xpack.stackConnectors.components.email.error.requiredEntryText": "未输入收件人、抄送、密送。 至少需要输入一个。", + "xpack.stackConnectors.components.email.error.requiredFromText": "“发送者”必填。", + "xpack.stackConnectors.components.email.error.requiredHostText": "“主机”必填。", + "xpack.stackConnectors.components.email.error.requiredMessageText": "“消息”必填。", + "xpack.stackConnectors.components.email.error.requiredPortText": "“端口”必填。", + "xpack.stackConnectors.components.serverLog.error.requiredServerLogMessageText": "“消息”必填。", + "xpack.stackConnectors.components.email.error.requiredServiceText": "“服务”必填。", + "xpack.stackConnectors.components.email.error.requiredSubjectText": "“主题”必填。", + "xpack.stackConnectors.components.email.error.requiredTenantIdText": "“租户 ID”必填。", + "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "“正文”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText": "“标题”必填。", + "xpack.stackConnectors.components.index.connectorTypeTitle": "索引数据", + "xpack.stackConnectors.components.index.configureIndexHelpLabel": "配置索引连接器。", + "xpack.stackConnectors.components.index.connectorSectionTitle": "写入到索引", + "xpack.stackConnectors.components.index.definedateFieldTooltip": "将此时间字段设置为索引文档的时间。", + "xpack.stackConnectors.components.index.defineTimeFieldLabel": "为每个文档定义时间字段", + "xpack.stackConnectors.components.index.documentsFieldLabel": "要索引的文档", + "xpack.stackConnectors.components.index.error.notValidIndexText": "索引无效。", + "xpack.stackConnectors.components.index.executionTimeFieldLabel": "时间字段", + "xpack.stackConnectors.components.index.howToBroadenSearchQueryDescription": "使用 * 可扩大您的查询范围。", + "xpack.stackConnectors.components.index.indexDocumentHelpLabel": "索引文档示例。", + "xpack.stackConnectors.components.index.indicesToQueryLabel": "索引", + "xpack.stackConnectors.components.index.jsonDocAriaLabel": "代码编辑器", + "xpack.stackConnectors.components.index.preconfiguredIndex": "Elasticsearch 索引", + "xpack.stackConnectors.components.index.preconfiguredIndexDocLink": "查看文档。", + "xpack.stackConnectors.components.index.refreshLabel": "刷新索引", + "xpack.stackConnectors.components.index.refreshTooltip": "刷新影响的分片以使此操作对搜索可见。", + "xpack.stackConnectors.components.index.selectMessageText": "将数据索引到 Elasticsearch 中。", + "xpack.stackConnectors.components.jira.connectorTypeTitle": "Jira", + "xpack.stackConnectors.components.jira.apiTokenTextFieldLabel": "API 令牌", + "xpack.stackConnectors.components.jira.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel": "其他注释", + "xpack.stackConnectors.components.jira.descriptionTextAreaFieldLabel": "描述", + "xpack.stackConnectors.components.jira.emailTextFieldLabel": "电子邮件地址", + "xpack.stackConnectors.components.jira.impactSelectFieldLabel": "标签", + "xpack.stackConnectors.components.jira.labelsSpacesErrorMessage": "标签不能包含空格。", + "xpack.stackConnectors.components.jira.parentIssueSearchLabel": "父问题", + "xpack.stackConnectors.components.jira.projectKey": "项目键", + "xpack.stackConnectors.components.jira.requiredSummaryTextField": "“摘要”必填。", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxAriaLabel": "键入内容进行搜索", + "xpack.stackConnectors.components.jira.searchIssuesComboBoxPlaceholder": "键入内容进行搜索", + "xpack.stackConnectors.components.jira.searchIssuesLoading": "正在加载……", + "xpack.stackConnectors.components.jira.selectMessageText": "在 Jira 创建事件。", + "xpack.stackConnectors.components.jira.severitySelectFieldLabel": "优先级", + "xpack.stackConnectors.components.jira.summaryFieldLabel": "摘要(必填)", + "xpack.stackConnectors.components.jira.unableToGetFieldsMessage": "无法获取字段", + "xpack.stackConnectors.components.jira.unableToGetIssuesMessage": "无法获取问题", + "xpack.stackConnectors.components.jira.unableToGetIssueTypesMessage": "无法获取问题类型", + "xpack.stackConnectors.components.jira.urgencySelectFieldLabel": "问题类型", + "xpack.stackConnectors.components.pagerDuty.connectorTypeTitle": "发送到 PagerDuty", + "xpack.stackConnectors.components.pagerDuty.apiUrlInvalid": "API URL 无效", + "xpack.stackConnectors.components.pagerDuty.apiUrlTextFieldLabel": "API URL(可选)", + "xpack.stackConnectors.components.pagerDuty.classFieldLabel": "类(可选)", + "xpack.stackConnectors.components.pagerDuty.componentTextFieldLabel": "组件(可选)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextFieldLabel": "DedupKey(可选)", + "xpack.stackConnectors.components.pagerDuty.dedupKeyTextRequiredFieldLabel": "DedupKey", + "xpack.stackConnectors.components.pagerDuty.error.requiredDedupKeyText": "解决或确认事件时需要 DedupKey。", + "xpack.stackConnectors.components.pagerDuty.error.requiredRoutingKeyText": "需要集成密钥/路由密钥。", + "xpack.stackConnectors.components.pagerDuty.error.requiredSummaryText": "“摘要”必填。", + "xpack.stackConnectors.components.pagerDuty.eventActionSelectFieldLabel": "事件操作", + "xpack.stackConnectors.components.pagerDuty.eventSelectAcknowledgeOptionLabel": "确认", + "xpack.stackConnectors.components.pagerDuty.eventSelectResolveOptionLabel": "解决", + "xpack.stackConnectors.components.pagerDuty.eventSelectTriggerOptionLabel": "触发", + "xpack.stackConnectors.components.pagerDuty.groupTextFieldLabel": "组(可选)", + "xpack.stackConnectors.components.pagerDuty.routingKeyNameHelpLabel": "配置 PagerDuty 帐户", + "xpack.stackConnectors.components.pagerDuty.routingKeyTextFieldLabel": "集成密钥", + "xpack.stackConnectors.components.pagerDuty.selectMessageText": "在 PagerDuty 中发送事件。", + "xpack.stackConnectors.components.pagerDuty.severitySelectCriticalOptionLabel": "紧急", + "xpack.stackConnectors.components.pagerDuty.severitySelectErrorOptionLabel": "错误", + "xpack.stackConnectors.components.pagerDuty.severitySelectFieldLabel": "严重性(可选)", + "xpack.stackConnectors.components.pagerDuty.severitySelectInfoOptionLabel": "信息", + "xpack.stackConnectors.components.pagerDuty.severitySelectWarningOptionLabel": "警告", + "xpack.stackConnectors.components.pagerDuty.sourceTextFieldLabel": "源(可选)", + "xpack.stackConnectors.components.pagerDuty.summaryFieldLabel": "摘要", + "xpack.stackConnectors.components.pagerDuty.timestampTextFieldLabel": "时间戳(可选)", + "xpack.stackConnectors.components.resilient.connectorTypeTitle": "Resilient", + "xpack.stackConnectors.components.resilient.apiKeyId": "API 密钥 ID", + "xpack.stackConnectors.components.resilient.apiKeySecret": "API 密钥密码", + "xpack.stackConnectors.components.resilient.apiUrlTextFieldLabel": "URL", + "xpack.stackConnectors.components.resilient.commentsTextAreaFieldLabel": "其他注释", + "xpack.stackConnectors.components.resilient.descriptionTextAreaFieldLabel": "描述", + "xpack.stackConnectors.components.resilient.nameFieldLabel": "名称(必填)", + "xpack.stackConnectors.components.resilient.orgId": "组织 ID", + "xpack.stackConnectors.components.resilient.requiredNameTextField": "“名称”必填。", + "xpack.stackConnectors.components.resilient.selectMessageText": "在 IBM Resilient 中创建事件。", + "xpack.stackConnectors.components.resilient.severity": "严重性", + "xpack.stackConnectors.components.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型", + "xpack.stackConnectors.components.resilient.unableToGetSeverityMessage": "无法获取严重性", + "xpack.stackConnectors.components.resilient.urgencySelectFieldLabel": "事件类型", + "xpack.stackConnectors.components.serverLog.connectorTypeTitle": "发送到服务器日志", + "xpack.stackConnectors.components.serverLog.logLevelFieldLabel": "级别", + "xpack.stackConnectors.components.serverLog.logMessageFieldLabel": "消息", + "xpack.stackConnectors.components.serverLog.selectMessageText": "将消息添加到 Kibana 日志。", + "xpack.stackConnectors.components.serviceNow.apiUrlTextFieldLabel": "ServiceNow 实例 URL", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout": "未安装 Elastic ServiceNow 应用", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.content": "请前往 ServiceNow 应用商店并安装该应用程序", + "xpack.stackConnectors.components.serviceNow.applicationRequiredCallout.errorMessage": "错误消息", + "xpack.stackConnectors.components.serviceNow.authenticationLabel": "身份验证", + "xpack.stackConnectors.components.serviceNow.cancelButtonText": "取消", + "xpack.stackConnectors.components.serviceNow.categoryTitle": "类别", + "xpack.stackConnectors.components.serviceNow.clientIdTextFieldLabel": "客户端 ID", + "xpack.stackConnectors.components.serviceNow.clientSecretTextFieldLabel": "客户端密钥", + "xpack.stackConnectors.components.serviceNow.commentsTextAreaFieldLabel": "其他注释", + "xpack.stackConnectors.components.serviceNow.confirmButtonText": "更新", + "xpack.stackConnectors.components.serviceNow.correlationDisplay": "相关性显示(可选)", + "xpack.stackConnectors.components.serviceNow.correlationID": "相关性 ID(可选)", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutCreate": "或新建一个。", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutMigrate": "更新此连接器,", + "xpack.stackConnectors.components.serviceNow.deprecatedCalloutTitle": "此连接器类型已过时", + "xpack.stackConnectors.components.serviceNow.descriptionTextAreaFieldLabel": "描述", + "xpack.stackConnectors.components.serviceNow.eventClassTextAreaFieldLabel": "源实例", + "xpack.stackConnectors.components.serviceNow.fetchErrorMsg": "无法提取。检查 ServiceNow 实例的 URL 或 CORS 配置。", + "xpack.stackConnectors.components.serviceNow.impactSelectFieldLabel": "影响", + "xpack.stackConnectors.components.serviceNow.installationCalloutTitle": "要使用此连接器,请先从 ServiceNow 应用商店安装 Elastic 应用。", + "xpack.stackConnectors.components.serviceNow.invalidApiUrlTextField": "URL 无效。", + "xpack.stackConnectors.components.serviceNow.keyIdTextFieldLabel": "JWT Verifier 密钥 ID", + "xpack.stackConnectors.components.serviceNow.messageKeyTextAreaFieldLabel": "消息密钥", + "xpack.stackConnectors.components.serviceNow.metricNameTextAreaFieldLabel": "指标名称", + "xpack.stackConnectors.components.serviceNow.nodeTextAreaFieldLabel": "节点", + "xpack.stackConnectors.components.serviceNow.passwordTextFieldLabel": "密码", + "xpack.stackConnectors.components.serviceNow.prioritySelectFieldLabel": "优先级", + "xpack.stackConnectors.components.serviceNow.privateKeyPassLabelHelpText": "仅在设置了私钥密码时才需要此项", + "xpack.stackConnectors.components.serviceNow.privateKeyPassTextFieldLabel": "私钥密码", + "xpack.stackConnectors.components.serviceNow.privateKeyTextFieldLabel": "私钥", + "xpack.stackConnectors.components.serviceNow.requiredClientIdTextField": "“客户端 ID”必填。", + "xpack.stackConnectors.components.serviceNow.requiredKeyIdTextField": "JWT Verifier 密钥 ID 必填。", + "xpack.stackConnectors.components.serviceNow.requiredPrivateKeyTextField": "“私钥”必填。", + "xpack.stackConnectors.components.serviceNow.requiredSeverityTextField": "“严重性”必填。", + "xpack.stackConnectors.components.serviceNow.requiredUserIdentifierTextField": "“用户标识符”必填。", + "xpack.stackConnectors.components.serviceNow.requiredUsernameTextField": "“用户名”必填。", + "xpack.stackConnectors.components.serviceNow.resourceTextAreaFieldLabel": "资源", + "xpack.stackConnectors.components.serviceNow.setupDevInstance": "设置开发者实例", + "xpack.stackConnectors.components.serviceNow.severityRequiredSelectFieldLabel": "严重性(必需)", + "xpack.stackConnectors.components.serviceNow.severitySelectFieldLabel": "严重性", + "xpack.stackConnectors.components.serviceNow.snInstanceLabel": "ServiceNow 实例", + "xpack.stackConnectors.components.serviceNow.sourceTextAreaFieldLabel": "源", + "xpack.stackConnectors.components.serviceNow.subcategoryTitle": "子类别", + "xpack.stackConnectors.components.serviceNow.title": "事件", + "xpack.stackConnectors.components.serviceNow.titleFieldLabel": "简短描述(必填)", + "xpack.stackConnectors.components.serviceNow.typeTextAreaFieldLabel": "类型", + "xpack.stackConnectors.components.serviceNow.unableToGetChoicesMessage": "无法获取选项", + "xpack.stackConnectors.components.serviceNow.unknown": "未知", + "xpack.stackConnectors.components.serviceNow.updateCalloutText": "连接器已更新。", + "xpack.stackConnectors.components.serviceNow.updateFormCredentialsTitle": "提供身份验证凭据", + "xpack.stackConnectors.components.serviceNow.updateFormInstallTitle": "安装 Elastic ServiceNow 应用", + "xpack.stackConnectors.components.serviceNow.updateFormTitle": "更新 ServiceNow 连接器", + "xpack.stackConnectors.components.serviceNow.updateFormUrlTitle": "输入 ServiceNow 实例 URL", + "xpack.stackConnectors.components.serviceNow.urgencySelectFieldLabel": "紧急性", + "xpack.stackConnectors.components.serviceNow.useOAuth": "使用 OAuth 身份验证", + "xpack.stackConnectors.components.serviceNow.userEmailTextFieldLabel": "用户标识符", + "xpack.stackConnectors.components.serviceNow.usernameTextFieldLabel": "用户名", + "xpack.stackConnectors.components.serviceNow.visitSNStore": "访问 ServiceNow 应用商店", + "xpack.stackConnectors.components.serviceNow.warningMessage": "这会更新此连接器的所有实例并且无法恢复。", + "xpack.stackConnectors.components.serviceNow.correlationIDHelpLabel": "用于更新事件的标识符", + "xpack.stackConnectors.components.serviceNowITOM.connectorTypeTitle": "ServiceNow ITOM", + "xpack.stackConnectors.components.serviceNowITOM.event": "事件", + "xpack.stackConnectors.components.serviceNowITOM.selectMessageText": "在 ServiceNow ITOM 中创建事件。", + "xpack.stackConnectors.components.serviceNowITSM.connectorTypeTitle": "ServiceNow ITSM", + "xpack.stackConnectors.components.serviceNowITSM.selectMessageText": "在 ServiceNow ITSM 中创建事件。", + "xpack.stackConnectors.components.serviceNowSIR.connectorTypeTitle": "ServiceNow SecOps", + "xpack.stackConnectors.components.serviceNowSIR.selectMessageText": "在 ServiceNow SecOps 中创建事件。", + "xpack.stackConnectors.components.serviceNowSIR.title": "安全事件", + "xpack.stackConnectors.components.serviceNowSIR.correlationIDHelpLabel": "用于更新事件的标识符", + "xpack.stackConnectors.components.slack.connectorTypeTitle": "发送到 Slack", + "xpack.stackConnectors.components.slack.error.invalidWebhookUrlText": "Webhook URL 无效。", + "xpack.stackConnectors.components.slack.messageTextAreaFieldLabel": "消息", + "xpack.stackConnectors.components.slack.selectMessageText": "向 Slack 频道或用户发送消息。", + "xpack.stackConnectors.components.slack.webhookUrlHelpLabel": "创建 Slack webhook URL", + "xpack.stackConnectors.components.slack.webhookUrlTextFieldLabel": "Webhook URL", + "xpack.stackConnectors.components.swimlane.unableToGetApplicationFieldsMessage": "无法获取应用程序字段", + "xpack.stackConnectors.components.swimlane.connectorTypeTitle": "创建泳道记录", + "xpack.stackConnectors.components.swimlane.alertIdFieldLabel": "告警 ID", + "xpack.stackConnectors.components.swimlane.apiTokenNameHelpLabel": "提供泳道 API 令牌", + "xpack.stackConnectors.components.swimlane.apiTokenTextFieldLabel": "API 令牌", + "xpack.stackConnectors.components.swimlane.apiUrlTextFieldLabel": "API URL", + "xpack.stackConnectors.components.swimlane.appIdTextFieldLabel": "应用程序 ID", + "xpack.stackConnectors.components.swimlane.caseIdFieldLabel": "案例 ID", + "xpack.stackConnectors.components.swimlane.caseNameFieldLabel": "案例名称", + "xpack.stackConnectors.components.swimlane.commentsFieldLabel": "注释", + "xpack.stackConnectors.components.swimlane.configureConnectionLabel": "配置 API 连接", + "xpack.stackConnectors.components.swimlane.connectorType": "连接器类型", + "xpack.stackConnectors.components.swimlane.descriptionFieldLabel": "描述", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningDesc": "无法选择此连接器,因为其缺失所需的告警字段映射。您可以编辑此连接器以添加所需的字段映射或选择告警类型的连接器。", + "xpack.stackConnectors.components.swimlane.emptyMappingWarningTitle": "此连接器缺失字段映射", + "xpack.stackConnectors.components.swimlane.error.requiredAlertID": "“告警 ID”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredAppIdText": "“应用 ID”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredCaseID": "“案例 ID”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredCaseName": "“案例名称”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredComments": "“注释”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredDescription": "“描述”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredRuleName": "“规则名称”必填。", + "xpack.stackConnectors.components.swimlane.error.requiredSeverity": "“严重性”必填。", + "xpack.stackConnectors.components.swimlane.invalidApiUrlTextField": "URL 无效。", + "xpack.stackConnectors.components.swimlane.mappingTitleTextFieldLabel": "配置字段映射", + "xpack.stackConnectors.components.swimlane.nextStep": "下一步", + "xpack.stackConnectors.components.swimlane.nextStepHelpText": "如果未配置字段映射,泳道连接器类型将设置为 all。", + "xpack.stackConnectors.components.swimlane.prevStep": "返回", + "xpack.stackConnectors.components.swimlane.ruleNameFieldLabel": "规则名称", + "xpack.stackConnectors.components.swimlane.selectMessageText": "在泳道中创建记录", + "xpack.stackConnectors.components.swimlane.severityFieldLabel": "严重性", + "xpack.stackConnectors.components.teams.connectorTypeTitle": "向 Microsoft Teams 频道发送消息。", + "xpack.stackConnectors.components.teams.error.invalidWebhookUrlText": "Webhook URL 无效。", + "xpack.stackConnectors.components.teams.error.requiredMessageText": "“消息”必填。", + "xpack.stackConnectors.components.teams.error.webhookUrlTextLabel": "Webhook URL", + "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "消息", + "xpack.stackConnectors.components.teams.selectMessageText": "向 Microsoft Teams 频道发送消息。", + "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "创建 Microsoft Teams Webhook URL", + "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Webhook 数据", + "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "添加标头", + "xpack.stackConnectors.components.webhook.authenticationLabel": "身份验证", + "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "代码编辑器", + "xpack.stackConnectors.components.webhook.bodyFieldLabel": "正文", + "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL 无效。", + "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证", + "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "钥匙", + "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "值", + "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "方法", + "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "密码", + "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "钥匙", + "xpack.stackConnectors.components.webhook.selectMessageText": "将请求发送到 Web 服务。", + "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", + "xpack.stackConnectors.components.webhook.userTextFieldLabel": "用户名", + "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "添加 HTTP 标头", + "xpack.stackConnectors.components.xmatters.connectorTypeTitle": "xMatters 数据", + "xpack.stackConnectors.components.xmatters.authenticationLabel": "身份验证", + "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本身份验证", + "xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本身份验证", + "xpack.stackConnectors.components.xmatters.connectorSettingsLabel": "选择在设置 xMatters 触发器时使用的身份验证方法。", + "xpack.stackConnectors.components.xmatters.error.invalidUrlTextField": "URL 无效。", + "xpack.stackConnectors.components.xmatters.error.invalidUsernameTextField": "用户名无效。", + "xpack.stackConnectors.components.xmatters.error.requiredUrlText": "“URL”必填。", + "xpack.stackConnectors.components.xmatters.initiationUrlHelpText": "包括完整 xMatters url。", + "xpack.stackConnectors.components.xmatters.passwordTextFieldLabel": "密码", + "xpack.stackConnectors.components.xmatters.selectMessageText": "触发 xMatters 工作流。", + "xpack.stackConnectors.components.xmatters.severity": "严重性", + "xpack.stackConnectors.components.xmatters.severitySelectCriticalOptionLabel": "紧急", + "xpack.stackConnectors.components.xmatters.severitySelectHighOptionLabel": "高", + "xpack.stackConnectors.components.xmatters.severitySelectLowOptionLabel": "低", + "xpack.stackConnectors.components.xmatters.severitySelectMediumOptionLabel": "中", + "xpack.stackConnectors.components.xmatters.severitySelectMinimalOptionLabel": "最小", + "xpack.stackConnectors.components.xmatters.tags": "标签", + "xpack.stackConnectors.components.xmatters.urlAuthLabel": "URL 身份验证", + "xpack.stackConnectors.components.xmatters.urlLabel": "发起 URL", + "xpack.stackConnectors.components.xmatters.userCredsLabel": "用户凭据", + "xpack.stackConnectors.components.xmatters.userTextFieldLabel": "用户名", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningDesc": "为连接器配置“创建注释 URL”和“创建注释对象”字段以在外部共享注释。", + "xpack.stackConnectors.components.casesWebhook.createCommentWarningTitle": "无法共享案例注释", + "xpack.stackConnectors.components.serviceNow.deprecatedTooltipTitle": "过时连接器", + "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "“用户名”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "“创建注释方法”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateIncidentResponseKeyText": "“创建案例响应案例 ID 键”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredCreateMethodText": "“创建案例方法”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseCreatedKeyText": "“获取案例响应创建日期键”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseExternalTitleKeyText": "“获取案例响应外部案例标题键”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentResponseUpdatedKeyText": "“获取案例响应更新日期键”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentViewUrlKeyText": "“查看案例 URL”必填。", + "xpack.stackConnectors.components.casesWebhook.error.requiredUpdateMethodText": "“更新案例方法”必填。", + "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "“用户名”必填。", + "xpack.stackConnectors.components.webhook.error.requiredMethodText": "“方法”必填", + "xpack.stackConnectors.components.email.addBccButton": "密送", + "xpack.stackConnectors.components.email.addCcButton": "抄送", + "xpack.stackConnectors.components.email.authenticationLabel": "身份验证", + "xpack.stackConnectors.components.email.clientIdFieldLabel": "客户端 ID", + "xpack.stackConnectors.components.email.clientSecretTextFieldLabel": "客户端密钥", + "xpack.stackConnectors.components.email.fromTextFieldLabel": "发送者", + "xpack.stackConnectors.components.email.hasAuthSwitchLabel": "需要对此服务器进行身份验证", + "xpack.stackConnectors.components.email.hostTextFieldLabel": "主机", + "xpack.stackConnectors.components.email.messageTextAreaFieldLabel": "消息", + "xpack.stackConnectors.components.email.passwordFieldLabel": "密码", + "xpack.stackConnectors.components.email.portTextFieldLabel": "端口", + "xpack.stackConnectors.components.email.recipientBccTextFieldLabel": "密送", + "xpack.stackConnectors.components.email.recipientCopyTextFieldLabel": "抄送", + "xpack.stackConnectors.components.email.recipientTextFieldLabel": "至", + "xpack.stackConnectors.components.email.secureSwitchLabel": "安全", + "xpack.stackConnectors.components.email.serviceTextFieldLabel": "服务", + "xpack.stackConnectors.components.email.subjectTextFieldLabel": "主题", + "xpack.stackConnectors.components.email.tenantIdFieldLabel": "租户 ID", + "xpack.stackConnectors.components.email.userTextFieldLabel": "用户名", + "xpack.stackConnectors.components.index.resetDefaultIndexLabel": "重置默认索引", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "已识别 {fieldCandidatesCount, plural, other {# 个字段候选项}}。", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "已识别 {fieldValuePairsCount, plural, other {# 个重要的字段/值对}}。", "xpack.aiops.index.dataLoader.internalServerErrorMessage": "加载索引 {index} 中的数据时出错。{message}。请求可能已超时。请尝试使用较小的样例大小或缩小时间范围。", @@ -31837,20 +32214,7 @@ "xpack.triggersActionsUI.actionVariables.legacyTagsLabel": "其已弃用,将由 {variable} 替代。", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage": "此连接器需要{minimumLicenseRequired}许可证。", "xpack.triggersActionsUI.checkRuleTypeEnabled.ruleTypeDisabledByLicenseMessage": "此规则类型需要{minimumLicenseRequired}许可证。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.missingVariables": "缺少所需{variableCount, plural, other {变量}}:{variables}", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideValue": "告警历史记录索引必须以“{alertHistoryPrefix}”开头。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidEmail": "电子邮件地址 {email} 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.notAllowed": "不允许使用电子邮件地址 {email}。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexHelpText": "文档已索引到 {alertHistoryIndex} 索引中。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueMessage": "无法获取 ID 为 {id} 的问题", "xpack.triggersActionsUI.components.builtinActionTypes.missingSecretsValuesLabel": "未导入敏感信息。请为以下字段{encryptedFieldsLength, plural, other {}} {secretFieldsLabel}输入值{encryptedFieldsLength, plural, other {}}。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestamp": "时间戳必须是有效的日期,例如 {nowShortFormat} 或 {nowLongFormat}。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiInfoError": "尝试获取应用程序信息时收到的状态:{status}", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.appInstallationInfo": "{update} {create} ", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateSuccessToastTitle": "{connectorName} 连接器已更新", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.apiUrlHelpLabel": "提供所需 ServiceNow 实例的完整 URL。如果没有,{instance}。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.serviceNowAppRunning": "在运行更新之前,必须从 ServiceNow 应用商店安装 Elastic 应用。{visitLink} 以安装该应用", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationMessage": "无法获取 ID 为 {id} 的应用程序", "xpack.triggersActionsUI.components.buttonGroupField.error.requiredField": "{label} 必填。", "xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText": "无法删除 {numErrors, number} 个{numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}", "xpack.triggersActionsUI.components.passwordField.error.requiredNameText": "{label} 必填。", @@ -32006,340 +32370,6 @@ "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "添加规则变量", "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "添加变量", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "搜索告警时发生错误", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.commentsTextAreaFieldLabel": "其他注释", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.descriptionTextAreaFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.tagsFieldLabel": "标签", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhook.titleFieldLabel": "摘要(必填)", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.actionTypeTitle": "Webhook - 案例管理数据", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addHeaderButton": "添加", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addVariable": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseCommentDesc": "Kibana 案例注释", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseDescriptionDesc": "Kibana 案例描述", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTagsDesc": "Kibana 案例标签", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTitleDesc": "Kibana 案例标题", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonHelp": "用于创建注释的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonTextFieldLabel": "创建注释对象", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentMethodTextFieldLabel": "创建注释方法", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlHelp": "用于添加注释到案例的 API URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlTextFieldLabel": "创建注释 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonHelpText": "用于创建案例的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonTextFieldLabel": "创建案例对象", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentMethodTextFieldLabel": "创建案例方法", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyHelpText": "创建案例响应中包含外部案例 ID 的 JSON 键", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyTextFieldLabel": "创建案例响应案例键", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentUrlTextFieldLabel": "创建案例 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.deleteHeaderButton": "删除", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.docLink": "正在配置 Webhook - 案例管理连接器。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentIncidentText": "创建注释对象必须为有效 JSON。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentUrlText": "创建注释 URL 必须为 URL 格式。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateIncidentText": "“创建案例对象”必填并且必须为有效 JSON。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateUrlText": "“创建案例 URL”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredGetIncidentUrlText": "“获取案例 URL”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateIncidentText": "“更新案例对象”必填并且必须为有效 JSON。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateUrlText": "“更新案例 URL”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalIdDesc": "外部系统 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalTitleDesc": "外部系统标题", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyHelp": "获取案例响应中包含外部案例标题的 JSON 键", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyTextFieldLabel": "获取案例响应外部标题键", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlHelp": "用于从外部系统中获取案例详情 JSON 的 API URL。使用变量选择器添加外部系统 ID 到 URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlTextFieldLabel": "获取案例 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.hasAuthSwitchLabel": "此 Webhook 需要身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.httpHeadersTitle": "在用的标头", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonCodeEditorAriaLabel": "代码编辑器", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonFieldLabel": "JSON", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.keyTextFieldLabel": "钥匙", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.next": "下一步", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.passwordTextFieldLabel": "密码", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.previous": "上一步", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.selectMessageText": "发送请求到案例管理 Web 服务。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step1": "设置连接器", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2": "创建案例", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2Description": "设置字段以在外部系统中创建案例。查阅您服务的 API 文档以了解需要哪些字段", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3": "获取案例信息", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3Description": "设置字段以在外部系统中添加案例注释。对于某些系统,这可能采取与在案例中创建更新相同的方法。查阅您服务的 API 文档以了解需要哪些字段。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4": "注释和更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4a": "在案例中创建更新", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4aDescription": "设置字段以在外部系统中创建案例更新。对于某些系统,这可能采取与添加案例注释相同的方法。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4b": "在案例中添加注释", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4bDescription": "设置字段以在外部系统中添加案例注释。对于某些系统,这可能采取与在案例中创建更新相同的方法。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonHelpl": "用于更新案例的 JSON 对象。使用变量选择器将案例数据添加到有效负载。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonTextFieldLabel": "更新案例对象", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentMethodTextFieldLabel": "更新案例方法", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlHelp": "用于更新案例的 API URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlTextFieldLabel": "更新案例 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.userTextFieldLabel": "用户名", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.valueTextFieldLabel": "值", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewHeadersSwitch": "添加 HTTP 标头", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlHelp": "用于查看外部系统中的案例的 URL。使用变量选择器添加外部系统 ID 或外部系统标题到 URL。", - "xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlTextFieldLabel": "外部案例查看 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField": "“简短描述”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientIdHelpLabel": "配置客户端 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.clientSecretHelpLabel": "配置客户端密钥", - "xpack.triggersActionsUI.components.builtinActionTypes.email.exchangeForm.tenantIdHelpLabel": "配置租户 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "发送到电子邮件", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.amazonSesServerTypeLabel": "Amazon SES", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.configureAccountsHelpLabel": "配置电子邮件帐户", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.elasticCloudServerTypeLabel": "Elastic Cloud", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.exchangeServerTypeLabel": "MS Exchange Server", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.gmailServerTypeLabel": "Gmail", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.otherServerTypeLabel": "其他", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.outlookServerTypeLabel": "Outlook", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "从您的服务器发送电子邮件。", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.updateErrorNotificationText": "无法获取服务配置", - "xpack.triggersActionsUI.components.builtinActionTypes.error.badIndexOverrideSuffix": "告警历史记录索引必须包含有效的后缀。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.invalidPortText": "端口无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthUserNameText": "“用户名”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredClientIdText": "“客户端 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredDocumentJson": "“文档”必填,并且应为有效的 JSON 对象。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText": "未输入收件人、抄送、密送。 至少需要输入一个。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText": "“发送者”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText": "“主机”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText": "“消息”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText": "“端口”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServerLogMessageText": "“消息”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText": "“服务”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText": "“消息”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText": "“主题”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredTenantIdText": "“租户 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText": "“正文”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookSummaryText": "“标题”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.actionTypeTitle": "索引数据", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "选择……", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.configureIndexHelpLabel": "配置索引连接器。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.connectorSectionTitle": "写入到索引", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip": "将此时间字段设置为索引文档的时间。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel": "为每个文档定义时间字段", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel": "要索引的文档", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.error.notValidIndexText": "索引无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.executionTimeFieldLabel": "时间字段", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.howToBroadenSearchQueryDescription": "使用 * 可扩大您的查询范围。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indexDocumentHelpLabel": "索引文档示例。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesAndIndexPatternsLabel": "基于您的数据视图", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indicesToQueryLabel": "索引", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel": "代码编辑器", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndex": "Elasticsearch 索引", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.preconfiguredIndexDocLink": "查看文档。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshLabel": "刷新索引", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip": "刷新影响的分片以使此操作对搜索可见。", - "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText": "将数据索引到 Elasticsearch 中。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.actionTypeTitle": "Jira", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiTokenTextFieldLabel": "API 令牌", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.commentsTextAreaFieldLabel": "其他注释", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.descriptionTextAreaFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.emailTextFieldLabel": "电子邮件地址", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.impactSelectFieldLabel": "标签", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.labelsSpacesErrorMessage": "标签不能包含空格。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.parentIssueSearchLabel": "父问题", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.projectKey": "项目键", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredSummaryTextField": "“摘要”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "键入内容进行搜索", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxPlaceholder": "键入内容进行搜索", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesLoading": "正在加载……", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.selectMessageText": "在 Jira 创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.severitySelectFieldLabel": "优先级", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.summaryFieldLabel": "摘要(必填)", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetFieldsMessage": "无法获取字段", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssuesMessage": "无法获取问题", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueTypesMessage": "无法获取问题类型", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.urgencySelectFieldLabel": "问题类型", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle": "发送到 PagerDuty", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlInvalid": "API URL 无效", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlTextFieldLabel": "API URL(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel": "类(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.componentTextFieldLabel": "组件(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextFieldLabel": "DedupKey(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextRequiredFieldLabel": "DedupKey", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredDedupKeyText": "解决或确认事件时需要 DedupKey。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredRoutingKeyText": "需要集成密钥/路由密钥。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredSummaryText": "“摘要”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventActionSelectFieldLabel": "事件操作", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectAcknowledgeOptionLabel": "确认", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectResolveOptionLabel": "解决", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.eventSelectTriggerOptionLabel": "触发", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.groupTextFieldLabel": "组(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyNameHelpLabel": "配置 PagerDuty 帐户", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyTextFieldLabel": "集成密钥", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText": "在 PagerDuty 中发送事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectCriticalOptionLabel": "紧急", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectErrorOptionLabel": "错误", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectFieldLabel": "严重性(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectInfoOptionLabel": "信息", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.severitySelectWarningOptionLabel": "警告", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.sourceTextFieldLabel": "源(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel": "摘要", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel": "时间戳(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.actionTypeTitle": "Resilient", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeyId": "API 密钥 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeySecret": "API 密钥密码", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiUrlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.commentsTextAreaFieldLabel": "其他注释", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.descriptionTextAreaFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.nameFieldLabel": "名称(必填)", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.orgId": "组织 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField": "“名称”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.selectMessageText": "在 IBM Resilient 中创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.severity": "严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.unableToGetSeverityMessage": "无法获取严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.resilient.urgencySelectFieldLabel": "事件类型", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle": "发送到服务器日志", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "级别", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "消息", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "将消息添加到 Kibana 日志。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel": "ServiceNow 实例 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout": "未安装 Elastic ServiceNow 应用", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.content": "请前往 ServiceNow 应用商店并安装该应用程序", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.applicationRequiredCallout.errorMessage": "错误消息", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.cancelButtonText": "取消", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.categoryTitle": "类别", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientIdTextFieldLabel": "客户端 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.clientSecretTextFieldLabel": "客户端密钥", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.commentsTextAreaFieldLabel": "其他注释", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.confirmButtonText": "更新", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationDisplay": "相关性显示(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.correlationID": "相关性 ID(可选)", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutCreate": "或新建一个。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutMigrate": "更新此连接器,", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.deprecatedCalloutTitle": "此连接器类型已过时", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.descriptionTextAreaFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.eventClassTextAreaFieldLabel": "源实例", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.fetchErrorMsg": "无法提取。检查 ServiceNow 实例的 URL 或 CORS 配置。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.impactSelectFieldLabel": "影响", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.installationCalloutTitle": "要使用此连接器,请先从 ServiceNow 应用商店安装 Elastic 应用。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField": "URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.keyIdTextFieldLabel": "JWT Verifier 密钥 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.messageKeyTextAreaFieldLabel": "消息密钥", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.metricNameTextAreaFieldLabel": "指标名称", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.nodeTextAreaFieldLabel": "节点", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel": "密码", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.prioritySelectFieldLabel": "优先级", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassLabelHelpText": "仅在设置了私钥密码时才需要此项", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyPassTextFieldLabel": "私钥密码", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.privateKeyTextFieldLabel": "私钥", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredClientIdTextField": "“客户端 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredKeyIdTextField": "JWT Verifier 密钥 ID 必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPrivateKeyTextField": "“私钥”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredSeverityTextField": "“严重性”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUserIdentifierTextField": "“用户标识符”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField": "“用户名”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.resourceTextAreaFieldLabel": "资源", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.setupDevInstance": "设置开发者实例", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severityRequiredSelectFieldLabel": "严重性(必需)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectFieldLabel": "严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.snInstanceLabel": "ServiceNow 实例", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.sourceTextAreaFieldLabel": "源", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.subcategoryTitle": "子类别", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.title": "事件", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.titleFieldLabel": "简短描述(必填)", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.typeTextAreaFieldLabel": "类型", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unableToGetChoicesMessage": "无法获取选项", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unknown": "未知", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.updateCalloutText": "连接器已更新。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormCredentialsTitle": "提供身份验证凭据", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormInstallTitle": "安装 Elastic ServiceNow 应用", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormTitle": "更新 ServiceNow 连接器", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormUrlTitle": "输入 ServiceNow 实例 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.urgencySelectFieldLabel": "紧急性", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.useOAuth": "使用 OAuth 身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.userEmailTextFieldLabel": "用户标识符", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel": "用户名", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.visitSNStore": "访问 ServiceNow 应用商店", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.warningMessage": "这会更新此连接器的所有实例并且无法恢复。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.correlationIDHelpLabel": "用于更新事件的标识符", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.actionTypeTitle": "ServiceNow ITOM", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowITOM.event": "事件", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITOM.selectMessageText": "在 ServiceNow ITOM 中创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.actionTypeTitle": "ServiceNow ITSM", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.selectMessageText": "在 ServiceNow ITSM 中创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.actionTypeTitle": "ServiceNow SecOps", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.selectMessageText": "在 ServiceNow SecOps 中创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenowSIR.title": "安全事件", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIRAction.correlationIDHelpLabel": "用于更新事件的标识符", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "发送到 Slack", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.invalidWebhookUrlText": "Webhook URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel": "消息", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText": "向 Slack 频道或用户发送消息。", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlHelpLabel": "创建 Slack webhook URL", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel": "Webhook URL", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlane.unableToGetApplicationFieldsMessage": "无法获取应用程序字段", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle": "创建泳道记录", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.alertIdFieldLabel": "告警 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenNameHelpLabel": "提供泳道 API 令牌", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiTokenTextFieldLabel": "API 令牌", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.apiUrlTextFieldLabel": "API URL", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.appIdTextFieldLabel": "应用程序 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseIdFieldLabel": "案例 ID", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.caseNameFieldLabel": "案例名称", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.commentsFieldLabel": "注释", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.configureConnectionLabel": "配置 API 连接", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.connectorType": "连接器类型", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.descriptionFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningDesc": "无法选择此连接器,因为其缺失所需的告警字段映射。您可以编辑此连接器以添加所需的字段映射或选择告警类型的连接器。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningTitle": "此连接器缺失字段映射", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAlertID": "“告警 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredAppIdText": "“应用 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseID": "“案例 ID”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredCaseName": "“案例名称”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredComments": "“注释”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredDescription": "“描述”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredRuleName": "“规则名称”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredSeverity": "“严重性”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.invalidApiUrlTextField": "URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.mappingTitleTextFieldLabel": "配置字段映射", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStep": "下一步", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStepHelpText": "如果未配置字段映射,泳道连接器类型将设置为 all。", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.prevStep": "返回", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.ruleNameFieldLabel": "规则名称", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText": "在泳道中创建记录", - "xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.severityFieldLabel": "严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.actionTypeTitle": "向 Microsoft Teams 频道发送消息。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.invalidWebhookUrlText": "Webhook URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requiredMessageText": "“消息”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.webhookUrlTextLabel": "Webhook URL", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.messageTextAreaFieldLabel": "消息", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.selectMessageText": "向 Microsoft Teams 频道发送消息。", - "xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlHelpLabel": "创建 Microsoft Teams Webhook URL", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle": "Webhook 数据", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButtonLabel": "添加标头", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyCodeEditorAriaLabel": "代码编辑器", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyFieldLabel": "正文", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.invalidUrlTextField": "URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.hasAuthSwitchLabel": "此 Webhook 需要身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerKeyTextFieldLabel": "钥匙", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.headerValueTextFieldLabel": "值", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.methodTextFieldLabel": "方法", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.passwordTextFieldLabel": "密码", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.removeHeaderIconLabel": "钥匙", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.selectMessageText": "将请求发送到 Web 服务。", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.urlTextFieldLabel": "URL", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.userTextFieldLabel": "用户名", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch": "添加 HTTP 标头", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.actionTypeTitle": "xMatters 数据", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthButtonGroupLegend": "基本身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.basicAuthLabel": "基本身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.connectorSettingsLabel": "选择在设置 xMatters 触发器时使用的身份验证方法。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUrlTextField": "URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.invalidUsernameTextField": "用户名无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.error.requiredUrlText": "“URL”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.initiationUrlHelpText": "包括完整 xMatters url。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.passwordTextFieldLabel": "密码", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.selectMessageText": "触发 xMatters 工作流。", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severity": "严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectCriticalOptionLabel": "紧急", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectHighOptionLabel": "高", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectLowOptionLabel": "低", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMediumOptionLabel": "中", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.severitySelectMinimalOptionLabel": "最小", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.tags": "标签", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlAuthLabel": "URL 身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.urlLabel": "发起 URL", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userCredsLabel": "用户凭据", - "xpack.triggersActionsUI.components.builtinActionTypes.xmattersAction.userTextFieldLabel": "用户名", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorButtonLabel": "创建连接器", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyBody": "配置电子邮件、Slack、Elasticsearch,以及 Kibana 运行的第三方服务。", "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addConnectorEmptyTitle": "创建您的首个连接器", @@ -32361,8 +32391,6 @@ "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorMessage": "找不到编辑器,请刷新页面并重试", "xpack.triggersActionsUI.components.jsonEditorWithMessageVariable.noEditorErrorTitle": "无法添加消息变量", "xpack.triggersActionsUI.components.simpleConnectorForm.secrets.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningDesc": "为连接器配置“创建注释 URL”和“创建注释对象”字段以在外部共享注释。", - "xpack.triggersActionsUI.components.textAreaWithMessageVariable.createCommentWarningTitle": "无法共享案例注释", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "连接器", "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]:晚于 [dateEnd]", "xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval]:如果 [dateStart] 不等于 [dateEnd],则必须指定", @@ -32435,7 +32463,6 @@ "xpack.triggersActionsUI.sections.actionConnectorForm.loadingConnectorSettingsDescription": "正在加载连接器设置……", "xpack.triggersActionsUI.sections.actionForm.actionSectionsTitle": "操作", "xpack.triggersActionsUI.sections.actionForm.addActionButtonLabel": "添加操作", - "xpack.triggersActionsUI.sections.actionForm.deprecatedTooltipTitle": "过时连接器", "xpack.triggersActionsUI.sections.actionForm.getMoreConnectorsTitle": "获取更多连接器", "xpack.triggersActionsUI.sections.actionForm.getMoreRuleTypesTitle": "获取更多规则类型", "xpack.triggersActionsUI.sections.actionForm.incidentManagementSystemLabel": "事件管理系统", @@ -32474,17 +32501,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.actionErrorToolTip": "操作包含错误。", "xpack.triggersActionsUI.sections.actionTypeForm.actionRunWhenInActionGroup": "运行条件", "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "添加连接器", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredAuthUserNameText": "“用户名”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateCommentMethodText": "“创建注释方法”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateIncidentResponseKeyText": "“创建案例响应案例 ID 键”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateMethodText": "“创建案例方法”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseCreatedKeyText": "“获取案例响应创建日期键”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseExternalTitleKeyText": "“获取案例响应外部案例标题键”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseUpdatedKeyText": "“获取案例响应更新日期键”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentViewUrlKeyText": "“查看案例 URL”必填。", - "xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredUpdateMethodText": "“更新案例方法”必填。", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthUserNameText": "“用户名”必填。", - "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText": "“方法”必填", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "选择连接器", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", "xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel": "取消", @@ -32494,25 +32510,6 @@ "xpack.triggersActionsUI.sections.alertsTable.alertsFlyout.reason": "原因", "xpack.triggersActionsUI.sections.alertsTable.column.actions": "操作", "xpack.triggersActionsUI.sections.alertsTable.leadingControl.viewDetails": "查看详情", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addBccButton": "密送", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addCcButton": "抄送", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientIdFieldLabel": "客户端 ID", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.clientSecretTextFieldLabel": "客户端密钥", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.fromTextFieldLabel": "发送者", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hasAuthSwitchLabel": "需要对此服务器进行身份验证", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hostTextFieldLabel": "主机", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.messageTextAreaFieldLabel": "消息", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel": "密码", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.portTextFieldLabel": "端口", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel": "密送", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel": "抄送", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientTextFieldLabel": "至", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.secureSwitchLabel": "安全", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.serviceTextFieldLabel": "服务", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel": "主题", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.tenantIdFieldLabel": "租户 ID", - "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "用户名", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseCancelButtonText": "取消", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseConfirmButtonText": "放弃更改", "xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseMessage": "您无法恢复未保存更改。", @@ -32702,7 +32699,6 @@ "xpack.triggersActionsUI.sections.rulesList.removeAllButton": "全部删除", "xpack.triggersActionsUI.sections.rulesList.removeCancelButton": "取消", "xpack.triggersActionsUI.sections.rulesList.removeConfirmButton": "全部删除", - "xpack.triggersActionsUI.sections.rulesList.resetDefaultIndexLabel": "重置默认索引", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDecrypting": "解密规则时发生错误。", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonDisabled": "无法执行规则,因为在规则禁用之后已运行。", "xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonLicense": "无法运行规则", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index 999b7b00a13aa..0643a7d266a88 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -917,13 +917,13 @@ export function getActionType(): ActionTypeModel { id: '.email', iconClass: 'email', selectMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText', + 'xpack.stackConnectors.components.email.selectMessageText', { defaultMessage: 'Send email from your server.', } ), actionTypeTitle: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle', + 'xpack.stackConnectors.components.email.connectorTypeTitle', { defaultMessage: 'Send to email', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/translations.ts deleted file mode 100644 index 785840477af68..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/translations.ts +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const CREATE_URL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateUrlText', - { - defaultMessage: 'Create case URL is required.', - } -); -export const CREATE_INCIDENT_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateIncidentText', - { - defaultMessage: 'Create case object is required and must be valid JSON.', - } -); - -export const CREATE_METHOD_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateMethodText', - { - defaultMessage: 'Create case method is required.', - } -); - -export const CREATE_RESPONSE_KEY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateIncidentResponseKeyText', - { - defaultMessage: 'Create case response case id key is required.', - } -); - -export const UPDATE_URL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateUrlText', - { - defaultMessage: 'Update case URL is required.', - } -); -export const UPDATE_INCIDENT_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredUpdateIncidentText', - { - defaultMessage: 'Update case object is required and must be valid JSON.', - } -); - -export const UPDATE_METHOD_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredUpdateMethodText', - { - defaultMessage: 'Update case method is required.', - } -); - -export const CREATE_COMMENT_URL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentUrlText', - { - defaultMessage: 'Create comment URL must be URL format.', - } -); -export const CREATE_COMMENT_MESSAGE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredCreateCommentIncidentText', - { - defaultMessage: 'Create comment object must be valid JSON.', - } -); - -export const CREATE_COMMENT_METHOD_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredCreateCommentMethodText', - { - defaultMessage: 'Create comment method is required.', - } -); - -export const GET_INCIDENT_URL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.requiredGetIncidentUrlText', - { - defaultMessage: 'Get case URL is required.', - } -); -export const GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseExternalTitleKeyText', - { - defaultMessage: 'Get case response external case title key is re quired.', - } -); -export const GET_RESPONSE_EXTERNAL_CREATED_KEY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseCreatedKeyText', - { - defaultMessage: 'Get case response created date key is required.', - } -); -export const GET_RESPONSE_EXTERNAL_UPDATED_KEY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentResponseUpdatedKeyText', - { - defaultMessage: 'Get case response updated date key is required.', - } -); -export const GET_INCIDENT_VIEW_URL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredGetIncidentViewUrlKeyText', - { - defaultMessage: 'View case URL is required.', - } -); - -export const MISSING_VARIABLES = (variables: string[]) => - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.error.missingVariables', - { - defaultMessage: - 'Missing required {variableCount, plural, one {variable} other {variables}}: {variables}', - values: { variableCount: variables.length, variables: variables.join(', ') }, - } - ); - -export const USERNAME_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.sections.addAction.casesWebhookAction.error.requiredAuthUserNameText', - { - defaultMessage: 'Username is required.', - } -); - -export const SUMMARY_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookSummaryText', - { - defaultMessage: 'Title is required.', - } -); - -export const KEY_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.keyTextFieldLabel', - { - defaultMessage: 'Key', - } -); - -export const VALUE_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.valueTextFieldLabel', - { - defaultMessage: 'Value', - } -); - -export const ADD_BUTTON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addHeaderButton', - { - defaultMessage: 'Add', - } -); - -export const DELETE_BUTTON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.deleteHeaderButton', - { - defaultMessage: 'Delete', - description: 'Delete HTTP header', - } -); - -export const CREATE_INCIDENT_METHOD = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentMethodTextFieldLabel', - { - defaultMessage: 'Create Case Method', - } -); - -export const CREATE_INCIDENT_URL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentUrlTextFieldLabel', - { - defaultMessage: 'Create Case URL', - } -); - -export const CREATE_INCIDENT_JSON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonTextFieldLabel', - { - defaultMessage: 'Create Case Object', - } -); - -export const CREATE_INCIDENT_JSON_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentJsonHelpText', - { - defaultMessage: - 'JSON object to create case. Use the variable selector to add Cases data to the payload.', - } -); - -export const JSON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonFieldLabel', - { - defaultMessage: 'JSON', - } -); -export const CODE_EDITOR = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.jsonCodeEditorAriaLabel', - { - defaultMessage: 'Code editor', - } -); - -export const CREATE_INCIDENT_RESPONSE_KEY = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyTextFieldLabel', - { - defaultMessage: 'Create Case Response Case Key', - } -); - -export const CREATE_INCIDENT_RESPONSE_KEY_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createIncidentResponseKeyHelpText', - { - defaultMessage: 'JSON key in create case response that contains the external case id', - } -); - -export const ADD_CASES_VARIABLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.addVariable', - { - defaultMessage: 'Add variable', - } -); - -export const GET_INCIDENT_URL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlTextFieldLabel', - { - defaultMessage: 'Get Case URL', - } -); -export const GET_INCIDENT_URL_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentUrlHelp', - { - defaultMessage: - 'API URL to GET case details JSON from external system. Use the variable selector to add external system id to the url.', - } -); - -export const GET_INCIDENT_TITLE_KEY = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyTextFieldLabel', - { - defaultMessage: 'Get Case Response External Title Key', - } -); -export const GET_INCIDENT_TITLE_KEY_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.getIncidentResponseExternalTitleKeyHelp', - { - defaultMessage: 'JSON key in get case response that contains the external case title', - } -); - -export const EXTERNAL_INCIDENT_VIEW_URL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlTextFieldLabel', - { - defaultMessage: 'External Case View URL', - } -); -export const EXTERNAL_INCIDENT_VIEW_URL_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewIncidentUrlHelp', - { - defaultMessage: - 'URL to view case in external system. Use the variable selector to add external system id or external system title to the url.', - } -); - -export const UPDATE_INCIDENT_METHOD = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentMethodTextFieldLabel', - { - defaultMessage: 'Update Case Method', - } -); - -export const UPDATE_INCIDENT_URL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlTextFieldLabel', - { - defaultMessage: 'Update Case URL', - } -); -export const UPDATE_INCIDENT_URL_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentUrlHelp', - { - defaultMessage: 'API URL to update case.', - } -); - -export const UPDATE_INCIDENT_JSON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonTextFieldLabel', - { - defaultMessage: 'Update Case Object', - } -); -export const UPDATE_INCIDENT_JSON_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.updateIncidentJsonHelpl', - { - defaultMessage: - 'JSON object to update case. Use the variable selector to add Cases data to the payload.', - } -); - -export const CREATE_COMMENT_METHOD = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentMethodTextFieldLabel', - { - defaultMessage: 'Create Comment Method', - } -); -export const CREATE_COMMENT_URL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlTextFieldLabel', - { - defaultMessage: 'Create Comment URL', - } -); - -export const CREATE_COMMENT_URL_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentUrlHelp', - { - defaultMessage: 'API URL to add comment to case.', - } -); - -export const CREATE_COMMENT_JSON = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonTextFieldLabel', - { - defaultMessage: 'Create Comment Object', - } -); -export const CREATE_COMMENT_JSON_HELP = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.createCommentJsonHelp', - { - defaultMessage: - 'JSON object to create a comment. Use the variable selector to add Cases data to the payload.', - } -); - -export const HAS_AUTH = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.hasAuthSwitchLabel', - { - defaultMessage: 'Require authentication for this webhook', - } -); - -export const USERNAME = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.userTextFieldLabel', - { - defaultMessage: 'Username', - } -); - -export const PASSWORD = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.passwordTextFieldLabel', - { - defaultMessage: 'Password', - } -); - -export const HEADERS_SWITCH = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.viewHeadersSwitch', - { - defaultMessage: 'Add HTTP header', - } -); - -export const HEADERS_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.httpHeadersTitle', - { - defaultMessage: 'Headers in use', - } -); - -export const AUTH_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.authenticationLabel', - { - defaultMessage: 'Authentication', - } -); - -export const STEP_1 = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step1', - { - defaultMessage: 'Set up connector', - } -); - -export const STEP_2 = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2', - { - defaultMessage: 'Create case', - } -); - -export const STEP_2_DESCRIPTION = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step2Description', - { - defaultMessage: - 'Set fields to create the case in the external system. Check your service’s API documentation to understand what fields are required', - } -); - -export const STEP_3 = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3', - { - defaultMessage: 'Get case information', - } -); - -export const STEP_3_DESCRIPTION = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step3Description', - { - defaultMessage: - 'Set fields to add comments to the case in external system. For some systems, this may be the same method as creating updates in cases. Check your service’s API documentation to understand what fields are required.', - } -); - -export const STEP_4 = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4', - { - defaultMessage: 'Comments and updates', - } -); - -export const STEP_4A = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4a', - { - defaultMessage: 'Create update in case', - } -); - -export const STEP_4A_DESCRIPTION = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4aDescription', - { - defaultMessage: - 'Set fields to create updates to the case in external system. For some systems, this may be the same method as adding comments to cases.', - } -); - -export const STEP_4B = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4b', - { - defaultMessage: 'Add comment in case', - } -); - -export const STEP_4B_DESCRIPTION = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.step4bDescription', - { - defaultMessage: - 'Set fields to add comments to the case in external system. For some systems, this may be the same method as creating updates in cases.', - } -); - -export const NEXT = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.next', - { - defaultMessage: 'Next', - } -); - -export const PREVIOUS = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.previous', - { - defaultMessage: 'Previous', - } -); - -export const CASE_TITLE_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTitleDesc', - { - defaultMessage: 'Kibana case title', - } -); - -export const CASE_DESCRIPTION_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseDescriptionDesc', - { - defaultMessage: 'Kibana case description', - } -); - -export const CASE_TAGS_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseTagsDesc', - { - defaultMessage: 'Kibana case tags', - } -); - -export const CASE_COMMENT_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.caseCommentDesc', - { - defaultMessage: 'Kibana case comment', - } -); - -export const EXTERNAL_ID_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalIdDesc', - { - defaultMessage: 'External system id', - } -); - -export const EXTERNAL_TITLE_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.externalTitleDesc', - { - defaultMessage: 'External system title', - } -); - -export const DOC_LINK = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.casesWebhookAction.docLink', - { - defaultMessage: 'Configuring Webhook - Case Management connector.', - } -); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.test.tsx deleted file mode 100644 index 45169e0fcb032..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/webhook.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.cases-webhook'; -let actionTypeModel: ActionTypeModel; - -beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('logoWebhook'); - }); -}); - -describe('webhook action params validation', () => { - test('action params validation succeeds when action params is valid', async () => { - const actionParams = { - subActionParams: { incident: { title: 'some title {{test}}' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { 'subActionParams.incident.title': [] }, - }); - }); - - test('params validation fails when body is not valid', async () => { - const actionParams = { - subActionParams: { incident: { title: '' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - 'subActionParams.incident.title': ['Title is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts deleted file mode 100644 index e5e5da50eca0b..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ValidatedEmail, ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common'; -import { getServerLogActionType } from './server_log'; -import { getSlackActionType } from './slack'; -import { getEmailActionType } from './email'; -import { getIndexActionType } from './es_index'; -import { getPagerDutyActionType } from './pagerduty'; -import { getSwimlaneActionType } from './swimlane'; -import { getCasesWebhookActionType } from './cases_webhook'; -import { getWebhookActionType } from './webhook'; -import { getXmattersActionType } from './xmatters'; -import { TypeRegistry } from '../../type_registry'; -import { ActionTypeModel } from '../../../types'; -import { - getServiceNowITSMActionType, - getServiceNowSIRActionType, - getServiceNowITOMActionType, -} from './servicenow'; -import { getJiraActionType } from './jira'; -import { getResilientActionType } from './resilient'; -import { getTeamsActionType } from './teams'; - -export interface RegistrationServices { - validateEmailAddresses: ( - addresses: string[], - options?: ValidateEmailAddressesOptions - ) => ValidatedEmail[]; -} - -export function registerBuiltInActionTypes({ - actionTypeRegistry, - services, -}: { - actionTypeRegistry: TypeRegistry; - services: RegistrationServices; -}) { - actionTypeRegistry.register(getServerLogActionType()); - actionTypeRegistry.register(getSlackActionType()); - actionTypeRegistry.register(getEmailActionType(services)); - actionTypeRegistry.register(getIndexActionType()); - actionTypeRegistry.register(getPagerDutyActionType()); - actionTypeRegistry.register(getSwimlaneActionType()); - actionTypeRegistry.register(getCasesWebhookActionType()); - actionTypeRegistry.register(getWebhookActionType()); - actionTypeRegistry.register(getXmattersActionType()); - actionTypeRegistry.register(getServiceNowITSMActionType()); - actionTypeRegistry.register(getServiceNowITOMActionType()); - actionTypeRegistry.register(getServiceNowSIRActionType()); - actionTypeRegistry.register(getJiraActionType()); - actionTypeRegistry.register(getResilientActionType()); - actionTypeRegistry.register(getTeamsActionType()); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/index.ts deleted file mode 100644 index 3db3aeaaaa66c..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getPagerDutyActionType } from './pagerduty'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/index.ts deleted file mode 100644 index 2794a63d4a8b0..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getResilientActionType } from './resilient'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx deleted file mode 100644 index c46bcd6a02c71..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.resilient'; -let actionTypeModel: ActionTypeModel; - -beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - }); -}); - -describe('resilient action params validation', () => { - test('action params validation succeeds when action params is valid', async () => { - const actionParams = { - subActionParams: { incident: { name: 'some title {{test}}' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { 'subActionParams.incident.name': [] }, - }); - }); - - test('params validation fails when body is not valid', async () => { - const actionParams = { - subActionParams: { incident: { name: '' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - 'subActionParams.incident.name': ['Name is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/index.ts deleted file mode 100644 index 0edbef22ee9cb..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getServerLogActionType } from './server_log'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx deleted file mode 100644 index 098c65f294560..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.server-log'; -let actionTypeModel: ActionTypeModel; - -beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('logsApp'); - }); -}); - -describe('action params validation', () => { - test('action params validation succeeds when action params is valid', async () => { - const actionParams = { - message: 'test message', - level: 'trace', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { message: [] }, - }); - }); - - test('params validation fails when message is not valid', async () => { - const actionParams = { - message: '', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - message: ['Message is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx deleted file mode 100644 index 9e786a189ec42..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const SERVICENOW_ITSM_ACTION_TYPE_ID = '.servicenow'; -const SERVICENOW_SIR_ACTION_TYPE_ID = '.servicenow-sir'; -const SERVICENOW_ITOM_ACTION_TYPE_ID = '.servicenow-itom'; -let actionTypeRegistry: TypeRegistry; - -beforeAll(() => { - actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); -}); - -describe('actionTypeRegistry.get() works', () => { - [ - SERVICENOW_ITSM_ACTION_TYPE_ID, - SERVICENOW_SIR_ACTION_TYPE_ID, - SERVICENOW_ITOM_ACTION_TYPE_ID, - ].forEach((id) => { - test(`${id}: action type static data is as expected`, () => { - const actionTypeModel = actionTypeRegistry.get(id); - expect(actionTypeModel.id).toEqual(id); - }); - }); -}); - -describe('servicenow action params validation', () => { - [SERVICENOW_ITSM_ACTION_TYPE_ID, SERVICENOW_SIR_ACTION_TYPE_ID].forEach((id) => { - test(`${id}: action params validation succeeds when action params is valid`, async () => { - const actionTypeModel = actionTypeRegistry.get(id); - const actionParams = { - subActionParams: { incident: { short_description: 'some title {{test}}' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['subActionParams.incident.short_description']: [] }, - }); - }); - - test(`${id}: params validation fails when short_description is not valid`, async () => { - const actionTypeModel = actionTypeRegistry.get(id); - const actionParams = { - subActionParams: { incident: { short_description: '' }, comments: [] }, - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - ['subActionParams.incident.short_description']: ['Short description is required.'], - }, - }); - }); - }); - - test(`${SERVICENOW_ITOM_ACTION_TYPE_ID}: action params validation succeeds when action params is valid`, async () => { - const actionTypeModel = actionTypeRegistry.get(SERVICENOW_ITOM_ACTION_TYPE_ID); - const actionParams = { subActionParams: { severity: 'Critical' } }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['severity']: [] }, - }); - }); - - test(`${SERVICENOW_ITOM_ACTION_TYPE_ID}: params validation fails when severity is not valid`, async () => { - const actionTypeModel = actionTypeRegistry.get(SERVICENOW_ITOM_ACTION_TYPE_ID); - const actionParams = { subActionParams: { severity: null } }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['severity']: ['Severity is required.'] }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx deleted file mode 100644 index 45689fbd50264..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.slack'; -let actionTypeModel: ActionTypeModel; - -beforeAll(async () => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('logoSlack'); - }); -}); - -describe('slack action params validation', () => { - test('if action params validation succeeds when action params is valid', async () => { - const actionParams = { - message: 'message {test}', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { message: [] }, - }); - }); - - test('params validation fails when message is not valid', async () => { - const actionParams = { - message: '', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - message: ['Message is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/index.ts deleted file mode 100644 index 39a57e1bccb61..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getSwimlaneActionType } from './swimlane'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/index.ts deleted file mode 100644 index 84af06ff53bfc..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getTeamsActionType } from './teams'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx deleted file mode 100644 index e08853abf9c12..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.teams'; -let actionTypeModel: ActionTypeModel; - -beforeAll(async () => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - }); -}); - -describe('teams action params validation', () => { - test('if action params validation succeeds when action params is valid', async () => { - const actionParams = { - message: 'message {test}', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { message: [] }, - }); - }); - - test('params validation fails when message is not valid', async () => { - const actionParams = { - message: '', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - message: ['Message is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx deleted file mode 100644 index dfc3aae39586d..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.webhook'; -let actionTypeModel: ActionTypeModel; - -beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('logoWebhook'); - }); -}); - -describe('webhook action params validation', () => { - test('action params validation succeeds when action params is valid', async () => { - const actionParams = { - body: 'message {test}', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { body: [] }, - }); - }); - - test('params validation fails when body is not valid', async () => { - const actionParams = { - body: '', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - body: ['Body is required.'], - }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/index.ts deleted file mode 100644 index 54bc4fd06acd4..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { getActionType as getXmattersActionType } from './xmatters'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx deleted file mode 100644 index 980fa90caf4bb..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TypeRegistry } from '../../../type_registry'; -import { registerBuiltInActionTypes } from '..'; -import { ActionTypeModel } from '../../../../types'; -import { registrationServicesMock } from '../../../../mocks'; - -const ACTION_TYPE_ID = '.xmatters'; -let actionTypeModel: ActionTypeModel; - -beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); - const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); - if (getResult !== null) { - actionTypeModel = getResult; - } -}); - -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.actionTypeTitle).toEqual('xMatters data'); - }); -}); - -describe('xmatters action params validation', () => { - test('action params validation succeeds when action params is valid', async () => { - const actionParams = { - alertActionGroupName: 'Small t-shirt', - signalId: 'c9437cab-6a5b-45e8-bc8a-f4a8af440e97', - ruleName: 'Test xMatters', - date: '2022-01-18T19:01:08.818Z', - severity: 'high', - spaceId: 'default', - tags: 'test1, test2', - }; - - expect(await actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { alertActionGroupName: [], signalId: [] }, - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts new file mode 100644 index 0000000000000..3bc994313093b --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { JsonEditorWithMessageVariables } from './json_editor_with_message_variables'; +export { HiddenField } from './hidden_field'; +export { PasswordField } from './password_field'; +export { TextFieldWithMessageVariables } from './text_field_with_message_variables'; +export { TextAreaWithMessageVariables } from './text_area_with_message_variables'; +export { SimpleConnectorForm } from './simple_connector_form'; +export type { ConfigFieldSchema, SecretsFieldSchema } from './simple_connector_form'; +export { ButtonGroupField } from './button_group_field'; +export { JsonFieldWrapper } from './json_field_wrapper'; +export { MustacheTextFieldWrapper } from './mustache_text_field_wrapper'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.test.tsx index 62745a6eb0995..36b3bf2cb153e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act, render, RenderResult } from '@testing-library/react'; -import { FormTestProvider } from './builtin_action_types/test_utils'; +import { FormTestProvider } from './test_utils'; import { ConfigFieldSchema, SecretsFieldSchema, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/test_utils.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/test_utils.tsx new file mode 100644 index 0000000000000..39cff673908bd --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/test_utils.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { of } from 'rxjs'; +import { I18nProvider } from '@kbn/i18n-react'; +import { EuiButton } from '@elastic/eui'; +import { Form, useForm, FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + +import { ConnectorServices } from '../../types'; +import { TriggersAndActionsUiServices } from '../..'; +import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; +import { ConnectorProvider } from '../context/connector_context'; + +interface FormTestProviderProps { + children: React.ReactNode; + defaultValue?: Record; + onSubmit?: ({ data, isValid }: { data: FormData; isValid: boolean }) => Promise; + connectorServices?: ConnectorServices; +} + +const FormTestProviderComponent: React.FC = ({ + children, + defaultValue, + onSubmit, + connectorServices = { validateEmailAddresses: jest.fn() }, +}) => { + const { form } = useForm({ defaultValue }); + const { submit } = form; + + const onClick = useCallback(async () => { + const res = await submit(); + if (onSubmit) { + onSubmit(res); + } + }, [onSubmit, submit]); + + return ( + + +
{children}
+ +
+
+ ); +}; + +FormTestProviderComponent.displayName = 'FormTestProvider'; +export const FormTestProvider = React.memo(FormTestProviderComponent); + +type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; +export interface AppMockRenderer { + render: UiRender; + coreStart: TriggersAndActionsUiServices; +} + +export const createAppMockRenderer = (): AppMockRenderer => { + const services = createStartServicesMock(); + const theme$ = of({ darkMode: false }); + + const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( + + + {children} + + + ); + AppWrapper.displayName = 'AppWrapper'; + const render: UiRender = (ui, options) => { + return reactRender(ui, { + wrapper: AppWrapper, + ...options, + }); + }; + return { + coreStart: services, + render, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 113d274c7aeb9..19d3b038c6350 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -12,7 +12,6 @@ export { INTERNAL_BASE_ALERTING_API_PATH, } from '@kbn/alerting-plugin/common'; export { BASE_ACTION_API_PATH, INTERNAL_BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; -export { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '@kbn/stack-connectors-plugin/common'; export type Section = 'connectors' | 'rules' | 'alerts' | 'logs'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/index.ts index 0e1a20c666ef6..8bb75e3cc4dbc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/index.ts @@ -6,4 +6,7 @@ */ export { templateActionVariable } from './template_action_variable'; +export { hasMustacheTokens } from './has_mustache_tokens'; +export { AlertProvidedActionVariables } from './action_variables'; +export { updateActionConnector } from './action_connector_api'; export { isRuleSnoozed } from './is_rule_snoozed'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx index b4cd8d1b979ac..5c2f9db816893 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx @@ -6,10 +6,7 @@ */ import React, { lazy } from 'react'; -import { - AppMockRenderer, - createAppMockRenderer, -} from '../../components/builtin_action_types/test_utils'; +import { AppMockRenderer, createAppMockRenderer } from '../../components/test_utils'; import { ConnectorForm } from './connector_form'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx index 747a4925d35f8..dd99bbdbabed5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx @@ -11,7 +11,7 @@ import { AppMockRenderer, createAppMockRenderer, FormTestProvider, -} from '../../components/builtin_action_types/test_utils'; +} from '../../components/test_utils'; import { ConnectorFormFields } from './connector_form_fields'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { waitFor } from '@testing-library/dom'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields_global.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields_global.test.tsx index 9df345c45f1e1..04cbad726ead7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields_global.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields_global.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, act } from '@testing-library/react'; -import { FormTestProvider } from '../../components/builtin_action_types/test_utils'; +import { FormTestProvider } from '../../components/test_utils'; import { ConnectorFormFieldsGlobal } from './connector_form_fields_global'; describe('ConnectorFormFieldsGlobal', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx index a0ca1a501dc9c..6256ee29aee40 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx @@ -11,10 +11,7 @@ import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; import { waitFor } from '@testing-library/dom'; import { act } from '@testing-library/react'; -import { - AppMockRenderer, - createAppMockRenderer, -} from '../../../components/builtin_action_types/test_utils'; +import { AppMockRenderer, createAppMockRenderer } from '../../../components/test_utils'; import CreateConnectorFlyout from '.'; import { betaBadgeProps } from '../beta_badge_props'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx index 23793d3c5766b..715279fa67b1a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx @@ -11,10 +11,7 @@ import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; import { waitFor } from '@testing-library/dom'; import { act } from '@testing-library/react'; -import { - AppMockRenderer, - createAppMockRenderer, -} from '../../../components/builtin_action_types/test_utils'; +import { AppMockRenderer, createAppMockRenderer } from '../../../components/test_utils'; import EditConnectorFlyout from '.'; import { ActionConnector, EditConnectorTabs, GenericValidationResult } from '../../../../types'; import { betaBadgeProps } from '../beta_badge_props'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/encrypted_fields_callout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/encrypted_fields_callout.test.tsx index 6c55cd34c0655..41893e785f5bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/encrypted_fields_callout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/encrypted_fields_callout.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { FormTestProvider } from '../../components/builtin_action_types/test_utils'; +import { FormTestProvider } from '../../components/test_utils'; import { EncryptedFieldsCallout } from './encrypted_fields_callout'; import { render, RenderResult } from '@testing-library/react'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts index 993139926b696..d832351538601 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts @@ -26,3 +26,5 @@ export const ConnectorAddModal = suspendedComponentWithProps import('./connector_add_inline')) ); + +export type { ConnectorFormSchema } from './types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/index.ts index fd43a6baf3f05..67677fa83b51d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/index.ts @@ -21,7 +21,8 @@ export { builtInAggregationTypes, builtInGroupByTypes, } from './constants'; +export { connectorDeprecatedMessage, deprecatedMessage } from './connectors_selection'; export type { IOption } from './index_controls'; export { getFields, getIndexOptions, firstFieldOption } from './index_controls'; -export { getTimeFieldOptions } from './lib'; +export { getTimeFieldOptions, useKibana } from './lib'; export type { Comparator, AggregationType, GroupByType, RuleStatus } from './types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts index c8f7a6e468595..f7af1fbbde257 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts @@ -6,3 +6,4 @@ */ export { getTimeFieldOptions, getTimeOptions } from './get_time_options'; +export { useKibana } from './kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 12f45dcc31e41..7500a66b70f16 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -43,12 +43,51 @@ export type { RulesListVisibleColumns, } from './types'; +export type { + ActionConnectorFieldsProps, + ActionParamsProps, + ActionTypeModel, + GenericValidationResult, +} from './types'; + +export { + AlertHistoryDefaultIndexName, + ALERT_HISTORY_PREFIX, + AlertHistoryDocumentTemplate, + AlertHistoryEsIndexConnectorId, +} from './types'; + +export { useConnectorContext } from './application/context/use_connector_context'; + export { ActionForm, CreateConnectorFlyout, EditConnectorFlyout, } from './application/sections/action_connector_form'; +export type { ConnectorFormSchema } from './application/sections/action_connector_form'; + +export type { ConfigFieldSchema, SecretsFieldSchema } from './application/components'; + +export { + ButtonGroupField, + HiddenField, + JsonEditorWithMessageVariables, + JsonFieldWrapper, + MustacheTextFieldWrapper, + PasswordField, + SimpleConnectorForm, + TextAreaWithMessageVariables, + TextFieldWithMessageVariables, +} from './application/components'; + +export { + AlertProvidedActionVariables, + hasMustacheTokens, + templateActionVariable, + updateActionConnector, +} from './application/lib'; + export type { ActionGroupWithCondition } from './application/sections'; export { AlertConditions, AlertConditionsGroup } from './application/sections'; @@ -57,6 +96,7 @@ export function plugin(context: PluginInitializerContext) { return new Plugin(context); } +export { useKibana } from './common'; export type { AggregationType, Comparator } from './common'; export { @@ -74,6 +114,8 @@ export { getTimeFieldOptions, GroupByExpression, COMPARATORS, + connectorDeprecatedMessage, + deprecatedMessage, } from './common'; export type { diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index b44ef00c5f425..02722bc0ee73b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -5,14 +5,12 @@ * 2.0. */ -import type { ValidatedEmail } from '@kbn/actions-plugin/common'; import type { TriggersAndActionsUIPublicPluginStart } from './plugin'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout'; import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout'; -import { RegistrationServices } from './application/components/builtin_action_types'; import { TypeRegistry } from './application/type_registry'; import { ActionTypeModel, @@ -132,9 +130,3 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { export const triggersActionsUiMock = { createStart: createStartMock, }; - -function validateEmailAddresses(addresses: string[]): ValidatedEmail[] { - return addresses.map((address) => ({ address, valid: true })); -} - -export const registrationServicesMock: RegistrationServices = { validateEmailAddresses }; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 5cc4dbb0ece86..10c5e5637f159 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -23,7 +23,6 @@ import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; import { TypeRegistry } from './application/type_registry'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; @@ -248,13 +247,6 @@ export class Plugin }, }); - registerBuiltInActionTypes({ - actionTypeRegistry: this.actionTypeRegistry, - services: { - validateEmailAddresses: plugins.actions.validateEmailAddresses, - }, - }); - if (this.experimentalFeatures.internalAlertsTable) { registerAlertsTableConfiguration({ alertsTableConfigurationRegistry: this.alertsTableConfigurationRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index 991cbc5b01216..8618be6c9c285 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -16,7 +16,6 @@ "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, - { "path": "../stack_connectors/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../rule_registry/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, From 7f5c804b5cfcc176d676a516808881ef594389fd Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Tue, 27 Sep 2022 09:48:56 -0500 Subject: [PATCH 077/172] [TIP] Reorganize flyout folder within indicators module (#141848) --- .../empty_prompt/empty_prompt.stories.tsx} | 4 ++-- .../empty_prompt/empty_prompt.tsx} | 3 +-- .../index.tsx => flyout/empty_prompt/index.ts} | 2 +- .../fields_table/fields_table.stories.tsx} | 10 +++++----- .../fields_table/fields_table.tsx} | 6 +++--- .../index.tsx => flyout/fields_table/index.ts} | 2 +- .../flyout.stories.tsx} | 2 +- .../flyout.test.tsx} | 2 +- .../indicators_flyout.tsx => flyout/flyout.tsx} | 6 +++--- .../index.tsx => flyout/index.ts} | 2 +- .../indicator_value_actions/index.ts} | 0 .../indicator_value_actions.tsx | 12 ++++++------ .../components/flyout/json_tab/index.ts | 8 ++++++++ .../json_tab/json_tab.stories.tsx} | 4 ++-- .../json_tab/json_tab.test.tsx} | 8 ++++---- .../json_tab/json_tab.tsx} | 4 ++-- .../overview_tab/block/block.stories.tsx} | 8 ++++---- .../overview_tab/block/block.tsx} | 10 +++++----- .../components/flyout/overview_tab/block/index.ts | 8 ++++++++ .../highlighted_values_table.tsx | 6 +++--- .../highlighted_values_table/index.ts} | 0 .../components/flyout/overview_tab/index.ts | 8 ++++++++ .../overview_tab/overview_tab.stories.tsx} | 8 ++++---- .../overview_tab/overview_tab.test.tsx} | 8 ++++---- .../overview_tab/overview_tab.tsx} | 15 +++++++-------- .../block => flyout/table_tab}/index.tsx | 2 +- .../table_tab/table_tab.stories.tsx} | 12 ++++++------ .../table_tab/table_tab.test.tsx} | 10 +++++----- .../table_tab/table_tab.tsx} | 6 +++--- .../components/indicator_empty_prompt/index.tsx | 8 -------- .../tabs/indicators_flyout_overview/index.tsx | 8 -------- .../tabs/indicators_flyout_table/index.tsx | 8 -------- .../components/indicators_table/cell_renderer.tsx | 2 +- .../indicators_table/indicators_table.test.tsx | 4 ++-- .../indicators_table/indicators_table.tsx | 2 +- 35 files changed, 103 insertions(+), 105 deletions(-) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx => flyout/empty_prompt/empty_prompt.stories.tsx} (78%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx => flyout/empty_prompt/empty_prompt.tsx} (94%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/index.tsx => flyout/empty_prompt/index.ts} (87%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx => flyout/fields_table/fields_table.stories.tsx} (65%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx => flyout/fields_table/fields_table.tsx} (89%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_json/index.tsx => flyout/fields_table/index.ts} (85%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/indicators_flyout.stories.tsx => flyout/flyout.stories.tsx} (97%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/indicators_flyout.test.tsx => flyout/flyout.test.tsx} (98%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/indicators_flyout.tsx => flyout/flyout.tsx} (94%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/components/indicator_fields_table/index.tsx => flyout/index.ts} (85%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_value_actions/index.tsx => flyout/indicator_value_actions/index.ts} (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => flyout}/indicator_value_actions/indicator_value_actions.tsx (86%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx => flyout/json_tab/json_tab.stories.tsx} (88%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx => flyout/json_tab/json_tab.test.tsx} (82%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx => flyout/json_tab/json_tab.tsx} (86%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx => flyout/overview_tab/block/block.stories.tsx} (70%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx => flyout/overview_tab/block/block.tsx} (82%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/components => flyout/overview_tab}/highlighted_values_table/highlighted_values_table.tsx (88%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx => flyout/overview_tab/highlighted_values_table/index.ts} (100%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx => flyout/overview_tab/overview_tab.stories.tsx} (79%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx => flyout/overview_tab/overview_tab.test.tsx} (86%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx => flyout/overview_tab/overview_tab.tsx} (87%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_overview/components/block => flyout/table_tab}/index.tsx (87%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx => flyout/table_tab/table_tab.stories.tsx} (72%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx => flyout/table_tab/table_tab.test.tsx} (82%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx => flyout/table_tab/table_tab.tsx} (82%) delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.stories.tsx similarity index 78% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.stories.tsx index 56d66781d187d..3f30e6c223cda 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.stories.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { Story } from '@storybook/react'; -import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; -import { IndicatorEmptyPrompt } from './indicator_empty_prompt'; +import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; +import { IndicatorEmptyPrompt } from '.'; export default { component: IndicatorEmptyPrompt, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.tsx similarity index 94% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.tsx index 0edf3e67f3c03..7242989ad86a4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/empty_prompt.tsx @@ -7,8 +7,7 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React from 'react'; -import { VFC } from 'react'; +import React, { VFC } from 'react'; export const EMPTY_PROMPT_TEST_ID = 'indicatorEmptyPrompt'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/index.ts similarity index 87% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/index.ts index 9e055caf749a6..3c3d20d367b82 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/empty_prompt/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicators_flyout'; +export * from './empty_prompt'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx similarity index 65% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx index c867eda97389f..8d3f13ae01af2 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { mockIndicatorsFiltersContext } from '../../../../../../common/mocks/mock_indicators_filters_context'; -import { IndicatorFieldsTable } from './indicator_fields_table'; -import { generateMockIndicator } from '../../../../../../../common/types/indicator'; -import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; -import { IndicatorsFiltersContext } from '../../../../context'; +import { mockIndicatorsFiltersContext } from '../../../../../common/mocks/mock_indicators_filters_context'; +import { IndicatorFieldsTable } from '.'; +import { generateMockIndicator } from '../../../../../../common/types/indicator'; +import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; +import { IndicatorsFiltersContext } from '../../../context'; export default { component: IndicatorFieldsTable, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx similarity index 89% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx index 13bb919009bae..e670d6b90e02a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx @@ -8,9 +8,9 @@ import { EuiBasicTableColumn, EuiInMemoryTable, EuiInMemoryTableProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo, VFC } from 'react'; -import { Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorFieldValue } from '../../../indicator_field_value'; -import { IndicatorValueActions } from '../../../indicator_value_actions'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorFieldValue } from '../../indicator_field_value'; +import { IndicatorValueActions } from '../indicator_value_actions'; export interface IndicatorFieldsTableProps { fields: string[]; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/index.ts similarity index 85% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/index.ts index 7e927ef8fab0c..aea9d041e18b8 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicators_flyout_json'; +export * from './fields_table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx similarity index 97% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx index ec87259c90a58..12345056c7a9d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx @@ -13,7 +13,7 @@ import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indi import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { IndicatorsFlyout } from './indicators_flyout'; +import { IndicatorsFlyout } from '.'; import { IndicatorsFiltersContext } from '../../context'; export default { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.test.tsx similarity index 98% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.test.tsx index 08add53bafcb1..a50cf08b3f2b5 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { cleanup, render, screen } from '@testing-library/react'; -import { IndicatorsFlyout, SUBTITLE_TEST_ID, TITLE_TEST_ID } from './indicators_flyout'; +import { IndicatorsFlyout, SUBTITLE_TEST_ID, TITLE_TEST_ID } from '.'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx similarity index 94% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx index 86949da21a814..24fe1cc0082ec 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx @@ -24,10 +24,10 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { InvestigateInTimelineButton } from '../../../timeline/components/investigate_in_timeline_button'; import { DateFormatter } from '../../../../components/date_formatter/date_formatter'; import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; -import { IndicatorsFlyoutJson } from './tabs/indicators_flyout_json/indicators_flyout_json'; -import { IndicatorsFlyoutTable } from './tabs/indicators_flyout_table/indicators_flyout_table'; +import { IndicatorsFlyoutJson } from './json_tab'; +import { IndicatorsFlyoutTable } from './table_tab'; import { unwrapValue } from '../../utils/unwrap_value'; -import { IndicatorsFlyoutOverview } from './tabs/indicators_flyout_overview'; +import { IndicatorsFlyoutOverview } from './overview_tab'; export const TITLE_TEST_ID = 'tiIndicatorFlyoutTitle'; export const SUBTITLE_TEST_ID = 'tiIndicatorFlyoutSubtitle'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/index.ts similarity index 85% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/index.ts index 9252945c8f552..6a2c75f0054a7 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicator_fields_table'; +export * from './flyout'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/index.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/index.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx similarity index 86% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx index 42fd697ec5395..919b39da28c31 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx @@ -6,13 +6,13 @@ */ import type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; -import React, { VFC } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; -import { Indicator } from '../../../../../common/types/indicator'; -import { FilterIn } from '../../../query_bar/components/filter_in'; -import { FilterOut } from '../../../query_bar/components/filter_out'; -import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../utils/field_value'; +import React, { VFC } from 'react'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { FilterIn } from '../../../../query_bar/components/filter_in'; +import { FilterOut } from '../../../../query_bar/components/filter_out'; +import { AddToTimeline } from '../../../../timeline/components/add_to_timeline'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils/field_value'; export const TIMELINE_BUTTON_TEST_ID = 'TimelineButton'; export const FILTER_IN_BUTTON_TEST_ID = 'FilterInButton'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/index.ts new file mode 100644 index 0000000000000..2d4825141142d --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/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 * from './json_tab'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.stories.tsx similarity index 88% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.stories.tsx index 1e40c23a26d4d..8d2eead239f4e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.stories.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { Story } from '@storybook/react'; -import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorsFlyoutJson } from './indicators_flyout_json'; +import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorsFlyoutJson } from '.'; export default { component: IndicatorsFlyoutJson, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.test.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.test.tsx index a468db60a023e..d56b328c61597 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.test.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; -import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; -import { CODE_BLOCK_TEST_ID, IndicatorsFlyoutJson } from './indicators_flyout_json'; +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; +import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; +import { CODE_BLOCK_TEST_ID, IndicatorsFlyoutJson } from '.'; +import { EMPTY_PROMPT_TEST_ID } from '../empty_prompt'; const mockIndicator: Indicator = generateMockIndicator(); -import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; describe('', () => { it('should render code block component on valid indicator', () => { const { getByTestId } = render( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.tsx similarity index 86% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.tsx index 99c4bcfb0d50f..f7dc6ad59de00 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/json_tab/json_tab.tsx @@ -7,8 +7,8 @@ import React, { VFC } from 'react'; import { EuiCodeBlock } from '@elastic/eui'; -import { Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorEmptyPrompt } from '../empty_prompt'; export const CODE_BLOCK_TEST_ID = 'tiFlyoutJsonCodeBlock'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx similarity index 70% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx index 32966e72c2eec..1049518f4620f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; -import { IndicatorsFiltersContext } from '../../../../../../context'; -import { StoryProvidersComponent } from '../../../../../../../../common/mocks/story_providers'; -import { generateMockIndicator } from '../../../../../../../../../common/types/indicator'; -import { IndicatorBlock } from './indicator_block'; +import { IndicatorsFiltersContext } from '../../../../context'; +import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; +import { generateMockIndicator } from '../../../../../../../common/types/indicator'; +import { IndicatorBlock } from '.'; export default { component: IndicatorBlock, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx index 9537131a574a3..dd8d4335feca1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx @@ -7,11 +7,11 @@ import { EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import React, { VFC } from 'react'; -import { euiStyled, css } from '@kbn/kibana-react-plugin/common'; -import { Indicator } from '../../../../../../../../../common/types/indicator'; -import { IndicatorFieldValue } from '../../../../../indicator_field_value'; -import { IndicatorFieldLabel } from '../../../../../indicator_field_label'; -import { IndicatorValueActions } from '../../../../../indicator_value_actions'; +import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; +import { Indicator } from '../../../../../../../common/types/indicator'; +import { IndicatorFieldValue } from '../../../indicator_field_value'; +import { IndicatorFieldLabel } from '../../../indicator_field_label'; +import { IndicatorValueActions } from '../../indicator_value_actions'; /** * Show actions wrapper on hover. This is a helper component, limited only to Block diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/index.ts new file mode 100644 index 0000000000000..e8b564b29af94 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/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 * from './block'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx similarity index 88% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx index d9af696065db1..6ce9c332d6323 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx @@ -6,9 +6,9 @@ */ import React, { useMemo, VFC } from 'react'; -import { Indicator, RawIndicatorFieldId } from '../../../../../../../../../common/types/indicator'; -import { unwrapValue } from '../../../../../../utils/unwrap_value'; -import { IndicatorFieldsTable } from '../../../../components/indicator_fields_table'; +import { Indicator, RawIndicatorFieldId } from '../../../../../../../common/types/indicator'; +import { unwrapValue } from '../../../../utils/unwrap_value'; +import { IndicatorFieldsTable } from '../../fields_table'; /** * Pick indicator fields starting with the indicator type diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/index.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/index.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/index.ts new file mode 100644 index 0000000000000..4f58be52f6ba6 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/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 * from './overview_tab'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx similarity index 79% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx index 72b20f769575b..d543b6b6d1125 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { Story } from '@storybook/react'; -import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; -import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorsFlyoutOverview } from './indicators_flyout_overview'; -import { IndicatorsFiltersContext } from '../../../../context'; +import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; +import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorsFlyoutOverview } from '.'; +import { IndicatorsFiltersContext } from '../../../context'; export default { component: IndicatorsFlyoutOverview, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.test.tsx similarity index 86% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.test.tsx index 580534e5668c2..df4201761a98e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.test.tsx @@ -5,16 +5,16 @@ * 2.0. */ -import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; import { IndicatorsFlyoutOverview, TI_FLYOUT_OVERVIEW_HIGH_LEVEL_BLOCKS, TI_FLYOUT_OVERVIEW_TABLE, -} from './indicators_flyout_overview'; -import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; +} from '.'; +import { EMPTY_PROMPT_TEST_ID } from '../empty_prompt'; describe('', () => { describe('invalid indicator', () => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx similarity index 87% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx index a7551398b62c4..7abbc1508fb58 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx @@ -15,14 +15,13 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useMemo } from 'react'; -import { VFC } from 'react'; -import { EMPTY_VALUE } from '../../../../../../../common/constants'; -import { Indicator, RawIndicatorFieldId } from '../../../../../../../common/types/indicator'; -import { unwrapValue } from '../../../../utils/unwrap_value'; -import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; -import { IndicatorBlock } from './components/block'; -import { HighlightedValuesTable } from './components/highlighted_values_table'; +import React, { useMemo, VFC } from 'react'; +import { EMPTY_VALUE } from '../../../../../../common/constants'; +import { Indicator, RawIndicatorFieldId } from '../../../../../../common/types/indicator'; +import { unwrapValue } from '../../../utils/unwrap_value'; +import { IndicatorEmptyPrompt } from '../empty_prompt'; +import { IndicatorBlock } from './block'; +import { HighlightedValuesTable } from './highlighted_values_table'; const highLevelFields = [ RawIndicatorFieldId.Feed, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/index.tsx similarity index 87% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/index.tsx index dbad4c02ee5dc..73571446eeef7 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicator_block'; +export * from './table_tab'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx similarity index 72% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx index 3a3aa1fa788ee..014d57b8ec113 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { Story } from '@storybook/react'; import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { mockIndicatorsFiltersContext } from '../../../../../../common/mocks/mock_indicators_filters_context'; -import { mockUiSettingsService } from '../../../../../../common/mocks/mock_kibana_ui_settings_service'; -import { mockKibanaTimelinesService } from '../../../../../../common/mocks/mock_kibana_timelines_service'; -import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorsFlyoutTable } from './indicators_flyout_table'; -import { IndicatorsFiltersContext } from '../../../../context'; +import { mockIndicatorsFiltersContext } from '../../../../../common/mocks/mock_indicators_filters_context'; +import { mockUiSettingsService } from '../../../../../common/mocks/mock_kibana_ui_settings_service'; +import { mockKibanaTimelinesService } from '../../../../../common/mocks/mock_kibana_timelines_service'; +import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorsFlyoutTable } from '.'; +import { IndicatorsFiltersContext } from '../../../context'; export default { component: IndicatorsFlyoutTable, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx index 5e1264b1b2e4a..8503bcdace2cc 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; import { generateMockIndicator, Indicator, RawIndicatorFieldId, -} from '../../../../../../../common/types/indicator'; -import { IndicatorsFlyoutTable, TABLE_TEST_ID } from './indicators_flyout_table'; -import { unwrapValue } from '../../../../utils/unwrap_value'; -import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; +} from '../../../../../../common/types/indicator'; +import { IndicatorsFlyoutTable, TABLE_TEST_ID } from '.'; +import { unwrapValue } from '../../../utils/unwrap_value'; +import { EMPTY_PROMPT_TEST_ID } from '../empty_prompt'; const mockIndicator: Indicator = generateMockIndicator(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.tsx index 8bb7956afc170..0f0a699733ccb 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.tsx @@ -6,9 +6,9 @@ */ import React, { VFC } from 'react'; -import { Indicator } from '../../../../../../../common/types/indicator'; -import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; -import { IndicatorFieldsTable } from '../../components/indicator_fields_table'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorEmptyPrompt } from '../empty_prompt'; +import { IndicatorFieldsTable } from '../fields_table'; export const TABLE_TEST_ID = 'tiFlyoutTableTabRow'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx deleted file mode 100644 index a2b896781739c..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './indicator_empty_prompt'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx deleted file mode 100644 index 71fcb871adf42..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './indicators_flyout_overview'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx deleted file mode 100644 index fa8190bee8364..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './indicators_flyout_table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx index b95a378a35a5b..394d996d0ce9a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx @@ -11,7 +11,7 @@ import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-th import React from 'react'; import { useKibana } from '../../../../hooks/use_kibana'; import { Indicator } from '../../../../../common/types/indicator'; -import { IndicatorFieldValue } from '../indicator_field_value/indicator_field_value'; +import { IndicatorFieldValue } from '../indicator_field_value'; import { IndicatorsTableContext } from './context'; import { ActionsRowCell } from './actions_row_cell'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx index b110c0f91c319..027033ae47710 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { IndicatorsTable, IndicatorsTableProps } from './indicators_table'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { BUTTON_TEST_ID } from '../open_indicator_flyout_button/open_indicator_flyout_button'; -import { TITLE_TEST_ID } from '../indicators_flyout/indicators_flyout'; +import { BUTTON_TEST_ID } from '../open_indicator_flyout_button'; +import { TITLE_TEST_ID } from '../flyout'; import { SecuritySolutionDataViewBase } from '../../../../types'; const stub = () => {}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx index f12e080b000b9..d1888431f7d82 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx @@ -23,7 +23,7 @@ import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indi import { cellRendererFactory } from './cell_renderer'; import { EmptyState } from '../../../../components/empty_state'; import { IndicatorsTableContext, IndicatorsTableContextValue } from './context'; -import { IndicatorsFlyout } from '../indicators_flyout/indicators_flyout'; +import { IndicatorsFlyout } from '../flyout'; import { useToolbarOptions } from './hooks/use_toolbar_options'; import { ColumnSettingsValue } from './hooks/use_column_settings'; import { useFieldTypes } from '../../../../hooks/use_field_types'; From c23b7fd44590d9c2b4262664b8280b5fc5c8b4b4 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 27 Sep 2022 16:55:54 +0200 Subject: [PATCH 078/172] revert upgrade_status change (#141937) --- x-pack/plugins/fleet/common/services/agent_status.ts | 6 +++--- .../fleet/common/services/is_agent_upgradeable.ts | 2 +- x-pack/plugins/fleet/common/types/models/agent.ts | 9 ++------- x-pack/plugins/fleet/server/services/agents/actions.ts | 2 +- .../plugins/fleet/server/services/agents/upgrade.test.ts | 6 +++--- x-pack/plugins/fleet/server/services/agents/upgrade.ts | 2 +- .../server/services/agents/upgrade_action_runner.ts | 2 +- .../server/endpoint/routes/metadata/metadata.test.ts | 4 +--- .../endpoint/routes/metadata/query_builders.fixtures.ts | 6 +----- .../routes/metadata/support/agent_status.test.ts | 4 ++-- 10 files changed, 16 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/fleet/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts index 72b912e573af3..55f93fca48d6f 100644 --- a/x-pack/plugins/fleet/common/services/agent_status.ts +++ b/x-pack/plugins/fleet/common/services/agent_status.ts @@ -47,7 +47,7 @@ export function getAgentStatus(agent: Agent | FleetServerAgent): AgentStatus { ? agent.policy_revision_idx : undefined; - if (!policyRevision || (agent.upgrade_started_at && agent.upgrade_status !== 'completed')) { + if (!policyRevision || (agent.upgrade_started_at && !agent.upgraded_at)) { return 'updating'; } @@ -75,7 +75,7 @@ export function getPreviousAgentStatusForOfflineAgents( ? agent.policy_revision_idx : undefined; - if (!policyRevision || (agent.upgrade_started_at && agent.upgrade_status !== 'completed')) { + if (!policyRevision || (agent.upgrade_started_at && !agent.upgraded_at)) { return 'updating'; } } @@ -109,7 +109,7 @@ export function buildKueryForOfflineAgents(path: string = ''): string { } export function buildKueryForUpgradingAgents(path: string = ''): string { - return `(${path}upgrade_started_at:*) and not (${path}upgrade_status:completed)`; + return `(${path}upgrade_started_at:*) and not (${path}upgraded_at:*)`; } export function buildKueryForUpdatingAgents(path: string = ''): string { diff --git a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts index c4c9fa7e75a69..2a523d1a2eabb 100644 --- a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts +++ b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts @@ -25,7 +25,7 @@ export function isAgentUpgradeable(agent: Agent, kibanaVersion: string, versionT return false; } // check that the agent is not already in the process of updating - if (agent.upgrade_started_at && agent.upgrade_status !== 'completed') { + if (agent.upgrade_started_at && !agent.upgraded_at) { return false; } if (versionToUpgrade !== undefined) { diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 9ca69b5100625..5def12287b4fd 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -75,9 +75,8 @@ interface AgentBase { enrolled_at: string; unenrolled_at?: string; unenrollment_started_at?: string; - upgraded_at?: string; + upgraded_at?: string | null; upgrade_started_at?: string | null; - upgrade_status?: 'started' | 'completed'; access_api_key_id?: string; default_api_key?: string; default_api_key_id?: string; @@ -188,15 +187,11 @@ export interface FleetServerAgent { /** * Date/time the Elastic Agent was last upgraded */ - upgraded_at?: string; + upgraded_at?: string | null; /** * Date/time the Elastic Agent started the current upgrade */ upgrade_started_at?: string | null; - /** - * Upgrade status - */ - upgrade_status?: 'started' | 'completed'; /** * ID of the API key the Elastic Agent must used to contact Fleet Server */ diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index 20cb2fb94e51d..4a6c772b69b94 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -242,8 +242,8 @@ export async function cancelAgentAction(esClient: ElasticsearchClient, actionId: hit._source.agents.map((agentId) => ({ agentId, data: { + upgraded_at: null, upgrade_started_at: null, - upgrade_status: 'completed', }, })) ); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts index 9692f05822879..db880f56ef474 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts @@ -38,7 +38,7 @@ describe('sendUpgradeAgentsActions (plural)', () => { expect(ids).toEqual(idsToAction); for (const doc of docs!) { expect(doc).toHaveProperty('upgrade_started_at'); - expect(doc.upgrade_status).toEqual('started'); + expect(doc.upgraded_at).toEqual(null); } }); it('cannot upgrade from a hosted agent policy by default', async () => { @@ -60,7 +60,7 @@ describe('sendUpgradeAgentsActions (plural)', () => { expect(ids).toEqual(onlyRegular); for (const doc of docs!) { expect(doc).toHaveProperty('upgrade_started_at'); - expect(doc.upgrade_status).toEqual('started'); + expect(doc.upgraded_at).toEqual(null); } // hosted policy is updated in action results with error @@ -98,7 +98,7 @@ describe('sendUpgradeAgentsActions (plural)', () => { expect(ids).toEqual(idsToAction); for (const doc of docs!) { expect(doc).toHaveProperty('upgrade_started_at'); - expect(doc.upgrade_status).toEqual('started'); + expect(doc.upgraded_at).toEqual(null); } }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index cf298ecb5997d..605aa896de59a 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -57,8 +57,8 @@ export async function sendUpgradeAgentAction({ type: 'UPGRADE', }); await updateAgent(esClient, agentId, { + upgraded_at: null, upgrade_started_at: now, - upgrade_status: 'started', }); } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index c757a9f1b5482..a34f189871a39 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -146,8 +146,8 @@ export async function upgradeBatch( agentsToUpdate.map((agent) => ({ agentId: agent.id, data: { + upgraded_at: null, upgrade_started_at: now, - upgrade_status: 'started', }, })) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 88d0617c18382..d03117225279a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -285,9 +285,7 @@ describe('test endpoint routes', () => { bool: { must_not: { bool: { - should: [ - { match: { 'united.agent.upgrade_status': 'completed' } }, - ], + should: [{ exists: { field: 'united.agent.upgraded_at' } }], minimum_should_match: 1, }, }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.fixtures.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.fixtures.ts index edc5d681b138e..fb01bd994f0d9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.fixtures.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.fixtures.ts @@ -71,11 +71,7 @@ export const expectedCompleteUnitedIndexQuery = { must_not: { bool: { should: [ - { - match: { - 'united.agent.upgrade_status': 'completed', - }, - }, + { exists: { field: 'united.agent.upgraded_at' } }, ], minimum_should_match: 1, }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts index 875f3927ee34c..6a7febe393db3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts @@ -93,7 +93,7 @@ describe('test filtering endpoint hosts by agent status', () => { const status = ['healthy']; const kuery = buildStatusesKuery(status); expect(kuery).toMatchInlineSnapshot( - `"(united.agent.last_checkin:* AND not ((united.agent.last_checkin < now-300s) or ((((united.agent.upgrade_started_at:*) and not (united.agent.upgrade_status:completed)) or (not (united.agent.last_checkin:*)) or (united.agent.unenrollment_started_at:*) or (not united.agent.policy_revision_idx:*)) AND not ((united.agent.last_checkin < now-300s) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*))))) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*)))))"` + `"(united.agent.last_checkin:* AND not ((united.agent.last_checkin < now-300s) or ((((united.agent.upgrade_started_at:*) and not (united.agent.upgraded_at:*)) or (not (united.agent.last_checkin:*)) or (united.agent.unenrollment_started_at:*) or (not united.agent.policy_revision_idx:*)) AND not ((united.agent.last_checkin < now-300s) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*))))) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*)))))"` ); }); @@ -115,7 +115,7 @@ describe('test filtering endpoint hosts by agent status', () => { const status = ['updating']; const kuery = buildStatusesKuery(status); expect(kuery).toMatchInlineSnapshot( - `"((((united.agent.upgrade_started_at:*) and not (united.agent.upgrade_status:completed)) or (not (united.agent.last_checkin:*)) or (united.agent.unenrollment_started_at:*) or (not united.agent.policy_revision_idx:*)) AND not ((united.agent.last_checkin < now-300s) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*)))))"` + `"((((united.agent.upgrade_started_at:*) and not (united.agent.upgraded_at:*)) or (not (united.agent.last_checkin:*)) or (united.agent.unenrollment_started_at:*) or (not united.agent.policy_revision_idx:*)) AND not ((united.agent.last_checkin < now-300s) or ((united.agent.last_checkin_status:error or united.agent.last_checkin_status:degraded) AND not ((united.agent.last_checkin < now-300s) or (united.agent.unenrollment_started_at:*)))))"` ); }); From 1289226cac76b55eaf839a502f4588c8790e190b Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Tue, 27 Sep 2022 07:58:30 -0700 Subject: [PATCH 079/172] [8.5][Elastic Defend onboarding] Fixing Typos (#141652) --- .../endpoint_policy_create_extension.tsx | 34 +++++++++---------- .../translations.ts | 18 ++++------ 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx index 9a4dc273740e5..d1223ff14826e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx @@ -28,7 +28,7 @@ import { EDR_ESSENTIAL, ENDPOINT, INTERACTIVE_ONLY, - PREVENT_MALICIOUS_BEHAVIOUR, + PREVENT_MALICIOUS_BEHAVIOR, } from './translations'; const PREFIX = 'endpoint_policy_create_extension'; @@ -75,12 +75,12 @@ export const EndpointPolicyCreateExtension = memo('NGAV'); const [behaviorProtectionChecked, setBehaviorProtectionChecked] = useState(false); - const [selectedCloudEvent, setSelectedCloudEvent] = useState('INTERACTIVE_ONLY'); + const [selectedCloudEvent, setSelectedCloudEvent] = useState('ALL_EVENTS'); const [selectedEnvironment, setSelectedEnvironment] = useState('endpoint'); const initialRender = useRef(true); - // Fleet will initialize the create form with a default name for the integratin policy, however, - // for endpoint security, we want the user to explicitely type in a name, so we blank it out + // Fleet will initialize the create form with a default name for the integrating policy, however, + // for endpoint security, we want the user to explicitly type in a name, so we blank it out // only during 1st component render (thus why the eslint disabled rule below). // Default values for config are endpoint + NGAV useEffect(() => { @@ -113,7 +113,7 @@ export const EndpointPolicyCreateExtension = memo { - // Skip trigerring this onChange on the initial render + // Skip triggering this onChange on the initial render if (initialRender.current) { initialRender.current = false; } else { @@ -217,7 +217,7 @@ export const EndpointPolicyCreateExtension = memo ), @@ -251,7 +251,7 @@ export const EndpointPolicyCreateExtension = memo @@ -265,7 +265,7 @@ export const EndpointPolicyCreateExtension = memo @@ -279,7 +279,7 @@ export const EndpointPolicyCreateExtension = memo @@ -305,13 +305,13 @@ export const EndpointPolicyCreateExtension = memo } > - +
} > - + {isPlatinumPlus && ( <> @@ -350,8 +350,8 @@ export const EndpointPolicyCreateExtension = memo diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts index b50835b36995b..66688371b68de 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts @@ -30,13 +30,13 @@ export const EDR_COMPLETE = i18n.translate( export const ENDPOINT = i18n.translate( 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOption', { - defaultMessage: 'Endpoint', + defaultMessage: 'Traditional Endpoints (desktops, laptops, virtual machines)', } ); export const CLOUD_SECURITY = i18n.translate( - 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudSecurityDropdownOption', + 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudDropdownOption', { - defaultMessage: 'Cloud Security', + defaultMessage: 'Cloud Workloads (Linux servers or Kubernetes environments)', } ); export const INTERACTIVE_ONLY = i18n.translate( @@ -51,15 +51,9 @@ export const ALL_EVENTS = i18n.translate( defaultMessage: 'All events', } ); -export const PREVENT_MALWARE = i18n.translate( - 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersPreventionMalware', +export const PREVENT_MALICIOUS_BEHAVIOR = i18n.translate( + 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersPreventionMaliciousBehavior', { - defaultMessage: 'Prevent Malware', - } -); -export const PREVENT_MALICIOUS_BEHAVIOUR = i18n.translate( - 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersPreventionMaliciousBehaviour', - { - defaultMessage: 'Prevent Malicious Behaviour', + defaultMessage: 'Prevent Malicious Behavior', } ); From 9cd21c51dffbcb80de824d9fe437c9b8aeed3986 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 27 Sep 2022 11:04:11 -0400 Subject: [PATCH 080/172] Add ent-search-docs-team as codeowners of enterprise search internal doclinks (#141948) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d0c6e476d6d44..5633cf2e6e1bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -352,6 +352,7 @@ x-pack/examples/files_example @elastic/kibana-app-services # Enterprise Search /x-pack/plugins/enterprise_search @elastic/enterprise-search-frontend /x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/shared/doc_links @elastic/ent-search-docs-team # Management Experience - Deployment Management /src/plugins/dev_tools/ @elastic/platform-deployment-management From 7e469af7c6881267f528b9a3d611244415f68269 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 27 Sep 2022 11:08:46 -0400 Subject: [PATCH 081/172] [Fleet] Point APM bundling process at package storage v2 (#141944) * Point APM bundling process at package storage v2 * Add Fleet as codeowner for bundled packages task --- .github/CODEOWNERS | 1 + src/dev/build/tasks/bundle_fleet_packages.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5633cf2e6e1bc..2c1a3e323a993 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -132,6 +132,7 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/test/fleet_api_integration @elastic/fleet /x-pack/test/fleet_cypress @elastic/fleet /x-pack/test/fleet_functional @elastic/fleet +/src/dev/build/tasks/bundle_fleet_packages.ts # APM /x-pack/plugins/apm/ @elastic/apm-ui diff --git a/src/dev/build/tasks/bundle_fleet_packages.ts b/src/dev/build/tasks/bundle_fleet_packages.ts index 4ba5e79d29928..30cfc1d22b0e6 100644 --- a/src/dev/build/tasks/bundle_fleet_packages.ts +++ b/src/dev/build/tasks/bundle_fleet_packages.ts @@ -15,6 +15,10 @@ import { Task, read, downloadToDisk, unzipBuffer, createZipFile } from '../lib'; const BUNDLED_PACKAGES_DIR = 'x-pack/plugins/fleet/target/bundled_packages'; +// APM needs to directly request its versions from Package Storage v2 - this should +// be removed when Package Storage v2 is in production +const PACKAGE_STORAGE_V2_URL = 'https://epr-v2.ea-web.elastic.dev'; + interface FleetPackage { name: string; version: string; @@ -64,7 +68,12 @@ export const BundleFleetPackages: Task = { } const archivePath = `${fleetPackage.name}-${versionToWrite}.zip`; - const archiveUrl = `${eprUrl}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; + let archiveUrl = `${eprUrl}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; + + // Point APM to package storage v2 + if (fleetPackage.name === 'apm') { + archiveUrl = `${PACKAGE_STORAGE_V2_URL}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; + } const destination = build.resolvePath(BUNDLED_PACKAGES_DIR, archivePath); From 0446e0100bafff7255cc7e8581ad82a5dc4a91ca Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 27 Sep 2022 17:09:49 +0200 Subject: [PATCH 082/172] [ML] Stabilize docs screenshots suites (#141755) This PR stabilizes the docs screenshots suite by disabling Kibana tour popovers and wrapping the multi selection service method into a retry. --- .../test/functional/services/ml/common_ui.ts | 35 +++++++++++-------- .../functional/services/ml/test_resources.ts | 8 +++++ .../screenshot_creation/apps/ml_docs/index.ts | 2 ++ .../apps/response_ops_docs/index.ts | 2 ++ 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index 0d8ee7d11a8b1..ef69f909437c1 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -164,22 +164,27 @@ export function MachineLearningCommonUIProvider({ }, async setMultiSelectFilter(testDataSubj: string, fieldTypes: string[]) { - await testSubjects.clickWhenNotDisabledWithoutRetry(`${testDataSubj}-button`); - await testSubjects.existOrFail(`${testDataSubj}-popover`); - await testSubjects.existOrFail(`${testDataSubj}-searchInput`); - const searchBarInput = await testSubjects.find(`${testDataSubj}-searchInput`); + await retry.tryForTime(60 * 1000, async () => { + // escape popover + await browser.pressKeys(browser.keys.ESCAPE); - for (const fieldType of fieldTypes) { - await retry.tryForTime(5000, async () => { - await searchBarInput.clearValueWithKeyboard(); - await searchBarInput.type(fieldType); - if (!(await testSubjects.exists(`${testDataSubj}-option-${fieldType}-checked`))) { - await testSubjects.existOrFail(`${testDataSubj}-option-${fieldType}`); - await testSubjects.click(`${testDataSubj}-option-${fieldType}`); - await testSubjects.existOrFail(`${testDataSubj}-option-${fieldType}-checked`); - } - }); - } + await testSubjects.clickWhenNotDisabledWithoutRetry(`${testDataSubj}-button`); + await testSubjects.existOrFail(`${testDataSubj}-popover`); + await testSubjects.existOrFail(`${testDataSubj}-searchInput`); + const searchBarInput = await testSubjects.find(`${testDataSubj}-searchInput`); + + for (const fieldType of fieldTypes) { + await retry.tryForTime(5000, async () => { + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.type(fieldType); + if (!(await testSubjects.exists(`${testDataSubj}-option-${fieldType}-checked`))) { + await testSubjects.existOrFail(`${testDataSubj}-option-${fieldType}`); + await testSubjects.click(`${testDataSubj}-option-${fieldType}`); + await testSubjects.existOrFail(`${testDataSubj}-option-${fieldType}-checked`); + } + }); + } + }); // escape popover await browser.pressKeys(browser.keys.ESCAPE); diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 4fccde6712e62..d1a7557caf2b1 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -46,6 +46,14 @@ export function MachineLearningTestResourcesProvider( await kibanaServer.uiSettings.unset('dateFormat:tz'); }, + async disableKibanaAnnouncements() { + await kibanaServer.uiSettings.update({ hideAnnouncements: true }); + }, + + async resetKibanaAnnouncements() { + await kibanaServer.uiSettings.unset('hideAnnouncements'); + }, + async savedObjectExistsById(id: string, objectType: SavedObjectType): Promise { const response = await supertest.get(`/api/saved_objects/${objectType}/${id}`); return response.status === 200; diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/index.ts b/x-pack/test/screenshot_creation/apps/ml_docs/index.ts index 1c12efc89caf7..806414939cd84 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/index.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/index.ts @@ -22,6 +22,7 @@ export default function ({ getPageObject, getService, loadTestFile }: FtrProvide before(async () => { await ml.testResources.installAllKibanaSampleData(); await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.testResources.disableKibanaAnnouncements(); await browser.setWindowSize(1920, 1080); }); @@ -29,6 +30,7 @@ export default function ({ getPageObject, getService, loadTestFile }: FtrProvide await securityPage.forceLogout(); await ml.testResources.removeAllKibanaSampleData(); await ml.testResources.resetKibanaTimeZone(); + await ml.testResources.resetKibanaAnnouncements(); }); loadTestFile(require.resolve('./anomaly_detection')); diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts index 64ed2599d65b4..e836e3e63c9b7 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts @@ -23,6 +23,7 @@ export default function ({ getPageObject, getService, loadTestFile }: FtrProvide before(async () => { await ml.testResources.installAllKibanaSampleData(); await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.testResources.disableKibanaAnnouncements(); await browser.setWindowSize(1920, 1080); await securityPage.login( esTestConfig.getUrlParts().username, @@ -34,6 +35,7 @@ export default function ({ getPageObject, getService, loadTestFile }: FtrProvide await securityPage.forceLogout(); await ml.testResources.removeAllKibanaSampleData(); await ml.testResources.resetKibanaTimeZone(); + await ml.testResources.resetKibanaAnnouncements(); }); loadTestFile(require.resolve('./stack_cases')); From f585dd6e5a3d0336d2a97fd0c5ee3ff88ccf0ab0 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Tue, 27 Sep 2022 16:14:22 +0100 Subject: [PATCH 083/172] [Security Solution][Alerts] saved query UX changes follow-up (#141747) ## Summary addresses feedback left during code review for https://github.com/elastic/kibana/pull/140064 - Saved query checkbox label shows saved query name - `createSavedQuery` moved to `x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts` - `useGetSavedQuery` moved to `x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details` - additional comments and minor code changes ### Before Screenshot 2022-09-26 at 11 38 05 ### After Screenshot 2022-09-26 at 11 37 23 --- .../custom_saved_query_rule.cy.ts | 4 ++-- .../cypress/tasks/api_calls/saved_queries.ts | 17 +++++++++++++++++ .../security_solution/cypress/tasks/common.ts | 17 ----------------- .../public/common/hooks/translations.ts | 7 ------- .../components/rules/query_bar/index.tsx | 1 + .../components/rules/step_define_rule/index.tsx | 7 +++++-- .../rules/step_define_rule/schema.tsx | 6 ------ .../rules/step_define_rule/translations.tsx | 13 +++++++++++-- .../detection_engine/rules/details/index.tsx | 4 +++- .../rules/details/translations.ts | 7 +++++++ .../rules/details}/use_get_saved_query.ts | 9 +++++---- .../lib/detection_engine/signals/get_filter.ts | 5 +---- 12 files changed, 52 insertions(+), 45 deletions(-) rename x-pack/plugins/security_solution/public/{common/hooks => detections/pages/detection_engine/rules/details}/use_get_saved_query.ts (81%) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts index 9b1d5c71a6fa9..5027fe09a8d3e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts @@ -25,8 +25,8 @@ import { import { goToRuleDetails, editFirstRule } from '../../tasks/alerts_detection_rules'; import { createTimeline } from '../../tasks/api_calls/timelines'; -import { createSavedQuery } from '../../tasks/api_calls/saved_queries'; -import { cleanKibana, deleteAlertsAndRules, deleteSavedQueries } from '../../tasks/common'; +import { createSavedQuery, deleteSavedQueries } from '../../tasks/api_calls/saved_queries'; +import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts index 8c4aea51ec045..08867cf55d4c0 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts @@ -33,3 +33,20 @@ export const createSavedQuery = ( }, headers: { 'kbn-xsrf': 'cypress-creds' }, }); + +export const deleteSavedQueries = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'query', + }, + }, + ], + }, + }, + }); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index 23f04d0fd3c2b..cd9525e95b0b2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -179,23 +179,6 @@ export const deleteCases = () => { }); }; -export const deleteSavedQueries = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { - query: { - bool: { - filter: [ - { - match: { - type: 'query', - }, - }, - ], - }, - }, - }); -}; - export const postDataView = (dataSource: string) => { cy.request({ method: 'POST', diff --git a/x-pack/plugins/security_solution/public/common/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/hooks/translations.ts index 1f939282b5c72..54ed3a79d017f 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/translations.ts @@ -39,10 +39,3 @@ export const EQL_TIME_INTERVAL_NOT_DEFINED = i18n.translate( defaultMessage: 'Time intervals are not defined.', } ); - -export const SAVED_QUERY_LOAD_ERROR_TOAST = i18n.translate( - 'xpack.securitySolution.hooks.useGetSavedQuery.errorToastMessage', - { - defaultMessage: 'Failed to load the saved query', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index a5316eabb11f9..7d7c9534c1688 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -70,6 +70,7 @@ const savedQueryToFieldValue = (savedQuery: SavedQuery): FieldValueQueryBar => ( filters: savedQuery.attributes.filters ?? [], query: savedQuery.attributes.query, saved_id: savedQuery.id, + title: savedQuery.attributes.title, }); export const QueryBarDefineRule = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 23042cd1165ec..21bf8125540b4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -739,8 +739,8 @@ const StepDefineRuleComponent: FC = ({ <> = ({ 'data-test-subj': 'detectionEngineStepDefineRuleShouldLoadQueryDynamically', euiFieldProps: { disabled: isLoading, + label: formQuery?.title + ? i18n.getSavedQueryCheckboxLabel(formQuery.title) + : undefined, }, }} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx index bc3937013b6ae..b23b496eae82e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx @@ -629,12 +629,6 @@ export const schema: FormSchema = { }, shouldLoadQueryDynamically: { type: FIELD_TYPES.CHECKBOX, - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldShouldLoadQueryDynamicallyLabel', - { - defaultMessage: 'Load the saved query dynamically on each rule execution', - } - ), defaultValue: false, }, }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx index b28d06777fd3c..4650da2ce603a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx @@ -78,13 +78,22 @@ export const EQL_QUERY_BAR_LABEL = i18n.translate( } ); -export const SAVED_QUERY_CHECKBOX_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.SavedQueryCheckboxLabel', +export const SAVED_QUERY_FORM_ROW_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.SavedQueryFormRowLabel', { defaultMessage: 'Saved query', } ); +export const getSavedQueryCheckboxLabel = (savedQueryName: string) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldShouldLoadQueryDynamicallyLabel', + { + defaultMessage: 'Load saved query "{savedQueryName}" dynamically on each rule execution', + values: { savedQueryName }, + } + ); + export const THREAT_MATCH_INDEX_HELPER_TEXT = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription', { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index e91756ce74db4..5149269c57d9c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -116,7 +116,7 @@ import { ExecutionLogTable } from './execution_log_table/execution_log_table'; import * as detectionI18n from '../../translations'; import * as ruleI18n from '../translations'; import { RuleDetailsContextProvider } from './rule_details_context'; -import { useGetSavedQuery } from '../../../../../common/hooks/use_get_saved_query'; +import { useGetSavedQuery } from './use_get_saved_query'; import * as i18n from './translations'; import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../../../components/callouts/missing_privileges_callout'; @@ -307,6 +307,8 @@ const RuleDetailsPageComponent: React.FC = ({ const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); // load saved query only if rule type === 'saved_query', as other rule types still can have saved_id property that is not used + // Rule schema allows to save any rule with saved_id property, but it only used for saved_query rule type + // In future we might look in possibility to restrict rule schema (breaking change!) and remove saved_id from the rest of rules through migration const savedQueryId = rule?.type === 'saved_query' ? rule?.saved_id : undefined; const { isSavedQueryLoading, savedQueryBar } = useGetSavedQuery(savedQueryId); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index 1a50d570ea179..b1c7e0033f5f4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -69,3 +69,10 @@ export const DELETED_RULE = i18n.translate( defaultMessage: 'Deleted rule', } ); + +export const SAVED_QUERY_LOAD_ERROR_TOAST = i18n.translate( + 'xpack.securitySolution.hooks.useGetSavedQuery.errorToastMessage', + { + defaultMessage: 'Failed to load the saved query', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/use_get_saved_query.ts similarity index 81% rename from x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts rename to x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/use_get_saved_query.ts index 932fe61e2ce47..4c3c4c6c42fe0 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/use_get_saved_query.ts @@ -7,12 +7,13 @@ import { useEffect, useMemo } from 'react'; -import { useSavedQueryServices } from '../utils/saved_query_services'; -import type { DefineStepRule } from '../../detections/pages/detection_engine/rules/types'; +import { useSavedQueryServices } from '../../../../../common/utils/saved_query_services'; +import type { DefineStepRule } from '../types'; + +import { useFetch, REQUEST_NAMES } from '../../../../../common/hooks/use_fetch'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; -import { useFetch, REQUEST_NAMES } from './use_fetch'; import { SAVED_QUERY_LOAD_ERROR_TOAST } from './translations'; -import { useAppToasts } from './use_app_toasts'; export const useGetSavedQuery = (savedQueryId: string | undefined) => { const savedQueryServices = useSavedQueryServices(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts index bc22ca9ea44d9..521fdf1e5a595 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts @@ -97,13 +97,10 @@ export const getFilter = async ({ index, exceptionFilter, }); - } else if (savedId && index != null) { - // if savedId present and we ending up here, then saved query failed to be fetched - // and we also didn't fall back to saved in rule query - throw Error(`Failed to fetch saved query. "${err.message}"`); } else { // user did not give any additional fall back mechanism for generating a rule // rethrow error for activity monitoring + err.message = `Failed to fetch saved query. "${err.message}"`; throw err; } } From ce35fcc26861e1c9e2dd8661f6742a2f119fb2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 27 Sep 2022 17:15:45 +0200 Subject: [PATCH 084/172] [Security Solution][Endpoint] Fix react minified error when getting endpoint data (#141212) * Fix react minified error when getting enpoint data * Revert overflow change to hidden Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/confirm_incoming_data_with_preview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index af0e3c882ea50..011134151cbd7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -75,7 +75,8 @@ const HitPreview: React.FC<{ hit: SearchHit }> = ({ hit }) => { ); const listItems = Object.entries(hitForDisplay).map(([key, value]) => ({ title: `${key}:`, - description: value, + // Ensures arrays and collections of nested objects are displayed correctly + description: JSON.stringify(value), })); return ( From f567c953d54cbaac2e738377f222b48e31f4afb6 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Tue, 27 Sep 2022 11:00:27 -0500 Subject: [PATCH 085/172] [Enterprise Search] align ml inference processor state (#141720) * align ml inference processor state updated how we render the state of the ml inference processor state to be inline with the trained model state. This will ensure we give the user more information if the model is in a state that will cause an error. * [Enterprise Search] replace flags with enum for model state Updated the InferencePipeline type to use an enum for the modelState instead of passing the string from the model and this also replaces the `isDeployed` flags since we want a more granular state that the enum will provide. --- .../common/types/pipelines.ts | 11 +- .../inference_pipeline_card.test.tsx | 14 +- .../pipelines/inference_pipeline_card.tsx | 42 +++-- .../pipelines/ml_model_health.test.tsx | 81 ++++++++++ .../pipelines/ml_model_health.tsx | 144 ++++++++++++++++++ .../enterprise_search_content/routes.ts | 2 + ...h_ml_inference_pipeline_processors.test.ts | 106 ++++++++++--- .../fetch_ml_inference_pipeline_processors.ts | 34 ++++- 8 files changed, 381 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 60fd87ca523b9..269f7149cc7b3 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -6,7 +6,16 @@ */ export interface InferencePipeline { - isDeployed: boolean; + modelState: TrainedModelState; + modelStateReason?: string; pipelineName: string; types: string[]; } + +export enum TrainedModelState { + NotDeployed = '', + Starting = 'starting', + Stopping = 'stopping', + Started = 'started', + Failed = 'failed', +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx index 1c79cff0244e3..27dc055564bd8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx @@ -11,14 +11,16 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiBadge, EuiHealth, EuiPanel, EuiTitle } from '@elastic/eui'; +import { EuiBadge, EuiPanel, EuiTitle } from '@elastic/eui'; + +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { InferencePipelineCard } from './inference_pipeline_card'; +import { TrainedModelHealth } from './ml_model_health'; -export const DEFAULT_VALUES = { - isDeployed: true, +export const DEFAULT_VALUES: InferencePipeline = { + modelState: TrainedModelState.Started, pipelineName: 'Sample Processor', - trainedModelName: 'example_trained_model', types: ['pytorch'], }; @@ -34,8 +36,6 @@ describe('InferencePipelineCard', () => { expect(wrapper.find(EuiPanel)).toHaveLength(1); expect(wrapper.find(EuiTitle)).toHaveLength(1); expect(wrapper.find(EuiBadge)).toHaveLength(1); - - const health = wrapper.find(EuiHealth); - expect(health.prop('children')).toEqual('Deployed'); + expect(wrapper.find(TrainedModelHealth)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx index b73121f947d73..e8017ff15a198 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx @@ -12,31 +12,30 @@ import { useActions, useValues } from 'kea'; import { EuiBadge, EuiButtonEmpty, + EuiButtonIcon, EuiConfirmModal, EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiPanel, EuiPopover, EuiPopoverTitle, EuiText, EuiTitle, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { InferencePipeline } from '../../../../../../common/types/pipelines'; +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { CANCEL_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants'; import { HttpLogic } from '../../../../shared/http'; +import { ML_MANAGE_TRAINED_MODELS_PATH } from '../../../routes'; import { IndexNameLogic } from '../index_name_logic'; +import { TrainedModelHealth } from './ml_model_health'; import { PipelinesLogic } from './pipelines_logic'; -export const InferencePipelineCard: React.FC = ({ - pipelineName, - isDeployed, - types, -}) => { +export const InferencePipelineCard: React.FC = (pipeline) => { const { http } = useValues(HttpLogic); const { indexName } = useValues(IndexNameLogic); const [isPopOverOpen, setIsPopOverOpen] = useState(false); @@ -46,10 +45,7 @@ export const InferencePipelineCard: React.FC = ({ setShowConfirmDelete(true); setIsPopOverOpen(false); }; - - const deployedText = i18n.translate('xpack.enterpriseSearch.inferencePipelineCard.isDeployed', { - defaultMessage: 'Deployed', - }); + const { pipelineName, types } = pipeline; const actionButton = ( = ({ - - {isDeployed && ( - - {deployedText} + + + + + {pipeline.modelState === TrainedModelState.NotDeployed && ( + + + + )} {types.map((type) => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx new file mode 100644 index 0000000000000..0eb88abb317e5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiHealth } from '@elastic/eui'; + +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; + +import { TrainedModelHealth } from './ml_model_health'; + +describe('TrainedModelHealth', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({}); + }); + + const commonModelData: InferencePipeline = { + modelState: TrainedModelState.NotDeployed, + pipelineName: 'Sample Processor', + types: ['pytorch'], + }; + it('renders model started', () => { + const pipeline: InferencePipeline = { + ...commonModelData, + modelState: TrainedModelState.Started, + }; + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Started'); + expect(health.prop('color')).toEqual('success'); + }); + it('renders model not deployed', () => { + const pipeline: InferencePipeline = { + ...commonModelData, + }; + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Not deployed'); + expect(health.prop('color')).toEqual('danger'); + }); + it('renders model stopping', () => { + const pipeline: InferencePipeline = { + ...commonModelData, + modelState: TrainedModelState.Stopping, + }; + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Stopping'); + expect(health.prop('color')).toEqual('warning'); + }); + it('renders model starting', () => { + const pipeline: InferencePipeline = { + ...commonModelData, + modelState: TrainedModelState.Starting, + }; + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Starting'); + expect(health.prop('color')).toEqual('warning'); + }); + it('renders model failed', () => { + const pipeline: InferencePipeline = { + ...commonModelData, + modelState: TrainedModelState.Failed, + modelStateReason: 'Model start boom.', + }; + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Deployment failed'); + expect(health.prop('color')).toEqual('danger'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx new file mode 100644 index 0000000000000..0d47c7018d4fe --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx @@ -0,0 +1,144 @@ +/* + * 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 { EuiHealth, EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; + +const modelStartedText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.started', + { + defaultMessage: 'Started', + } +); +const modelStartedTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.started.tooltip', + { + defaultMessage: 'This trained model is running and fully available', + } +); +const modelStartingText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.starting', + { + defaultMessage: 'Starting', + } +); +const modelStartingTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.starting.tooltip', + { + defaultMessage: + 'This trained model is in the process of starting up and will be available shortly', + } +); +const modelStoppingText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.stopping', + { + defaultMessage: 'Stopping', + } +); +const modelStoppingTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.stopping.tooltip', + { + defaultMessage: + 'This trained model is in the process of shutting down and is currently unavailable', + } +); +const modelDeploymentFailedText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.deploymentFailed', + { + defaultMessage: 'Deployment failed', + } +); +const modelNotDeployedText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed', + { + defaultMessage: 'Not deployed', + } +); +const modelNotDeployedTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed.tooltip', + { + defaultMessage: + 'This trained model is not currently deployed. Visit the trained models page to make changes', + } +); + +export const TrainedModelHealth: React.FC = ({ + modelState, + modelStateReason, +}) => { + let modelHealth: { + healthColor: string; + healthText: React.ReactNode; + tooltipText: React.ReactNode; + }; + switch (modelState) { + case TrainedModelState.Started: + modelHealth = { + healthColor: 'success', + healthText: modelStartedText, + tooltipText: modelStartedTooltip, + }; + break; + case TrainedModelState.Stopping: + modelHealth = { + healthColor: 'warning', + healthText: modelStoppingText, + tooltipText: modelStoppingTooltip, + }; + break; + case TrainedModelState.Starting: + modelHealth = { + healthColor: 'warning', + healthText: modelStartingText, + tooltipText: modelStartingTooltip, + }; + break; + case TrainedModelState.Failed: + modelHealth = { + healthColor: 'danger', + healthText: modelDeploymentFailedText, + tooltipText: ( + + ), + }; + break; + case TrainedModelState.NotDeployed: + modelHealth = { + healthColor: 'danger', + healthText: modelNotDeployedText, + tooltipText: modelNotDeployedTooltip, + }; + break; + } + return ( + + {modelHealth.healthText} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 60260dcaa5377..a980402119062 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -21,3 +21,5 @@ export const SEARCH_INDEX_PATH = `${SEARCH_INDICES_PATH}/:indexName`; export const SEARCH_INDEX_TAB_PATH = `${SEARCH_INDEX_PATH}/:tabId`; export const SEARCH_INDEX_CRAWLER_DOMAIN_DETAIL_PATH = `${SEARCH_INDEX_PATH}/crawler/domains/:domainId`; export const SEARCH_INDEX_SELECT_CONNECTOR_PATH = `${SEARCH_INDEX_PATH}/select_connector`; + +export const ML_MANAGE_TRAINED_MODELS_PATH = '/app/ml/trained_models'; diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts index 9a34bb42ec876..79d4600bb31e4 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts @@ -9,7 +9,7 @@ import { MlTrainedModelConfig } from '@elastic/elasticsearch/lib/api/typesWithBo import { ElasticsearchClient } from '@kbn/core/server'; import { BUILT_IN_MODEL_TAG } from '@kbn/ml-plugin/common/constants/data_frame_analytics'; -import { InferencePipeline } from '../../../common/types/pipelines'; +import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; import { fetchAndAddTrainedModelData, @@ -169,35 +169,74 @@ const mockGetTrainedModelsData = { model_type: 'pytorch', tags: [], }, + { + inference_config: { text_classification: {} }, + model_id: 'trained-model-id-3', + model_type: 'pytorch', + tags: [], + }, + { + inference_config: { fill_mask: {} }, + model_id: 'trained-model-id-4', + model_type: 'pytorch', + tags: [], + }, ], }; const mockGetTrainedModelStats = { - count: 1, + count: 4, trained_model_stats: [ { model_id: 'trained-model-id-1', }, { deployment_stats: { + allocation_status: { + allocation_count: 1, + }, state: 'started', }, model_id: 'trained-model-id-2', }, + { + deployment_stats: { + allocation_status: { + allocation_count: 1, + }, + state: 'failed', + reason: 'something is wrong, boom', + }, + model_id: 'trained-model-id-3', + }, + { + deployment_stats: { + allocation_status: { + allocation_count: 1, + }, + state: 'starting', + }, + model_id: 'trained-model-id-4', + }, ], }; -const trainedModelDataObject = { +const trainedModelDataObject: Record = { 'trained-model-id-1': { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-1', types: ['lang_ident', 'ner'], }, 'trained-model-id-2': { - isDeployed: true, + modelState: TrainedModelState.Started, pipelineName: 'ml-inference-pipeline-2', types: ['pytorch', 'ner'], }, + 'ml-inference-pipeline-3': { + modelState: TrainedModelState.NotDeployed, + pipelineName: 'ml-inference-pipeline-3', + types: ['lang_ident', 'ner'], + }, }; describe('fetchMlInferencePipelineProcessorNames lib function', () => { @@ -254,15 +293,15 @@ describe('fetchPipelineProcessorInferenceData lib function', () => { it('should return the inference processor data for the pipelines', async () => { mockClient.ingest.getPipeline.mockImplementation(() => Promise.resolve(mockGetPipeline2)); - const expected = [ + const expected: InferencePipelineData[] = [ { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', types: [], }, { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', types: [], @@ -338,20 +377,20 @@ describe('getMlModelConfigsForModelIds lib function', () => { Promise.resolve(mockGetTrainedModelStats) ); - const input = { + const input: Record = { 'trained-model-id-1': { - isDeployed: true, + modelState: TrainedModelState.Started, pipelineName: '', trainedModelName: 'trained-model-id-1', types: ['pytorch', 'ner'], }, 'trained-model-id-2': { - isDeployed: true, + modelState: TrainedModelState.Started, pipelineName: '', trainedModelName: 'trained-model-id-2', types: ['pytorch', 'ner'], }, - } as Record; + }; const expected = { 'trained-model-id-2': input['trained-model-id-2'], @@ -392,32 +431,57 @@ describe('fetchAndAddTrainedModelData lib function', () => { const pipelines: InferencePipelineData[] = [ { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', types: [], }, { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', types: [], }, + { + modelState: TrainedModelState.NotDeployed, + pipelineName: 'ml-inference-pipeline-3', + trainedModelName: 'trained-model-id-3', + types: [], + }, + { + modelState: TrainedModelState.NotDeployed, + pipelineName: 'ml-inference-pipeline-4', + trainedModelName: 'trained-model-id-4', + types: [], + }, ]; const expected: InferencePipelineData[] = [ { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', types: ['lang_ident', 'ner'], }, { - isDeployed: true, + modelState: TrainedModelState.Started, pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', types: ['pytorch', 'ner'], }, + { + modelState: TrainedModelState.Failed, + modelStateReason: 'something is wrong, boom', + pipelineName: 'ml-inference-pipeline-3', + trainedModelName: 'trained-model-id-3', + types: ['pytorch', 'text_classification'], + }, + { + modelState: TrainedModelState.Starting, + pipelineName: 'ml-inference-pipeline-4', + trainedModelName: 'trained-model-id-4', + types: ['pytorch', 'fill_mask'], + }, ]; const response = await fetchAndAddTrainedModelData( @@ -426,10 +490,10 @@ describe('fetchAndAddTrainedModelData lib function', () => { ); expect(mockClient.ml.getTrainedModels).toHaveBeenCalledWith({ - model_id: 'trained-model-id-1,trained-model-id-2', + model_id: 'trained-model-id-1,trained-model-id-2,trained-model-id-3,trained-model-id-4', }); expect(mockClient.ml.getTrainedModelsStats).toHaveBeenCalledWith({ - model_id: 'trained-model-id-1,trained-model-id-2', + model_id: 'trained-model-id-1,trained-model-id-2,trained-model-id-3,trained-model-id-4', }); expect(response).toEqual(expected); }); @@ -551,11 +615,7 @@ describe('fetchMlInferencePipelineProcessors lib function', () => { const expected: InferencePipeline[] = [ trainedModelDataObject['trained-model-id-1'], - { - isDeployed: false, - pipelineName: 'ml-inference-pipeline-3', - types: ['lang_ident', 'ner'], - }, + trainedModelDataObject['ml-inference-pipeline-3'], ]; const response = await fetchMlInferencePipelineProcessors( diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts index 3b695d53ba9ab..95839a9b6ac20 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts @@ -9,7 +9,7 @@ import { MlTrainedModelConfig } from '@elastic/elasticsearch/lib/api/typesWithBo import { ElasticsearchClient } from '@kbn/core/server'; import { BUILT_IN_MODEL_TAG } from '@kbn/ml-plugin/common/constants/data_frame_analytics'; -import { InferencePipeline } from '../../../common/types/pipelines'; +import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; export type InferencePipelineData = InferencePipeline & { @@ -58,7 +58,7 @@ export const fetchPipelineProcessorInferenceData = async ( const trainedModelName = inferenceProcessor?.inference?.model_id; if (trainedModelName) pipelineProcessorData.push({ - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: pipelineProcessorName, trainedModelName, types: [], @@ -98,7 +98,7 @@ export const getMlModelConfigsForModelIds = async ( if (trainedModelNames.includes(trainedModelName)) { modelConfigs[trainedModelName] = { - isDeployed: false, + modelState: TrainedModelState.NotDeployed, pipelineName: '', trainedModelName, types: getMlModelTypesForModelConfig(trainedModelData), @@ -109,8 +109,27 @@ export const getMlModelConfigsForModelIds = async ( trainedModelsStats.trained_model_stats.forEach((trainedModelStats) => { const trainedModelName = trainedModelStats.model_id; if (modelConfigs.hasOwnProperty(trainedModelName)) { - const isDeployed = trainedModelStats.deployment_stats?.state === 'started'; - modelConfigs[trainedModelName].isDeployed = isDeployed; + let modelState: TrainedModelState; + switch (trainedModelStats.deployment_stats?.state) { + case 'started': + modelState = TrainedModelState.Started; + break; + case 'starting': + modelState = TrainedModelState.Starting; + break; + case 'stopping': + modelState = TrainedModelState.Stopping; + break; + // @ts-ignore: type is wrong, "failed" is a possible state + case 'failed': + modelState = TrainedModelState.Failed; + break; + default: + modelState = TrainedModelState.NotDeployed; + break; + } + modelConfigs[trainedModelName].modelState = modelState; + modelConfigs[trainedModelName].modelStateReason = trainedModelStats.deployment_stats?.reason; } }); @@ -131,11 +150,12 @@ export const fetchAndAddTrainedModelData = async ( if (!model) { return data; } - const { types, isDeployed } = model; + const { types, modelState, modelStateReason } = model; return { ...data, types, - isDeployed, + modelState, + modelStateReason, }; }); }; From db931bf60e1a639dc887cdcae8239c96c6a098bf Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 27 Sep 2022 18:03:40 +0200 Subject: [PATCH 086/172] Update renovate.json with new backport labels (#141954) Use `backport:all-open` instead of the deprecated `auto-backport` label. --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 6eed31366c409..4075b2452bea1 100644 --- a/renovate.json +++ b/renovate.json @@ -125,7 +125,7 @@ ], "reviewers": ["team:kibana-security"], "matchBaseBranches": ["main"], - "labels": ["Team:Security", "release_note:skip", "auto-backport"], + "labels": ["Team:Security", "release_note:skip", "backport:all-open"], "enabled": true }, { From bde62fd5d82deb56d47fa99f8112e030c62a45de Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 27 Sep 2022 18:22:22 +0200 Subject: [PATCH 087/172] Fix wrong Critical Hosts and Critical Users count color (#141953) --- .../overview/components/entity_analytics/header/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index d8f087a7cc884..c1a52364a76f8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -28,7 +28,7 @@ import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml import { useQueryInspector } from '../../../../common/components/page/manage_query'; const StyledEuiTitle = styled(EuiTitle)` - color: ${({ theme: { eui } }) => eui.euiColorVis9}; + color: ${({ theme: { eui } }) => eui.euiColorDanger}; `; const HOST_RISK_QUERY_ID = 'hostRiskScoreKpiQuery'; From 7aa54285975814e0b4cdb5f6344e22b470a91383 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Tue, 27 Sep 2022 18:29:34 +0200 Subject: [PATCH 088/172] [Security Solution] Don't mute rules when bulk editing rule actions (#140626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Intro This PR modifies the logic of bulk updating rule actions, in preparation for https://github.com/elastic/kibana/pull/137430 ## Summary - Removes the mute logic for bulk updating rule actions - Remove option for “Perform no actions” from the bulk update rule actions dropdown options ONLY (option still available when creating or editing rules individually) - Also corrects bulk update rule actions flyout, so that: - available actions are always displayed - copy referring to using "Perform No Actions" to mute all selected rules is no longer displayed. ## Screenshots **Removed unwanted copy and "On each rule execution" selected as default** ![image](https://user-images.githubusercontent.com/5354282/191498419-10299ee5-4a9e-474e-b00a-657dc90816fa.png) **"Perform No Action" option no longer available** ![image](https://user-images.githubusercontent.com/5354282/191498500-3965edad-8142-4834-808e-c210e72e17cb.png) ### Checklist Delete any items that are not applicable to this PR. - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../index.ts | 1 - .../src/default_throttle_null/index.test.ts | 44 --- .../src/default_throttle_null/index.ts | 23 -- .../src/throttle/index.ts | 10 +- .../src/time_duration/index.test.ts | 373 +++++++++++++----- .../src/time_duration/index.ts | 76 ++-- .../request/perform_bulk_action_schema.ts | 19 +- .../description_step/throttle_description.tsx | 7 +- .../rules/step_rule_actions/index.tsx | 11 +- .../rules/throttle_select_field/index.tsx | 10 +- .../bulk_actions/forms/rule_actions_form.tsx | 64 +-- .../detection_engine/rules/bulk_edit_rules.ts | 33 +- .../group10/perform_bulk_action.ts | 16 +- .../group10/update_rules.ts | 4 +- .../group10/update_rules_bulk.ts | 6 +- 15 files changed, 412 insertions(+), 285 deletions(-) delete mode 100644 packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.test.ts delete mode 100644 packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.ts diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/index.ts index c2e00482cd2d3..75d5352c9f63e 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/index.ts @@ -18,7 +18,6 @@ export * from './src/default_per_page'; export * from './src/default_risk_score_mapping_array'; export * from './src/default_severity_mapping_array'; export * from './src/default_threat_array'; -export * from './src/default_throttle_null'; export * from './src/default_to_string'; export * from './src/default_uuid'; export * from './src/from'; diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.test.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.test.ts deleted file mode 100644 index b92815d4fe828..0000000000000 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { Throttle } from '../throttle'; -import { DefaultThrottleNull } from '.'; -import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -describe('default_throttle_null', () => { - test('it should validate a throttle string', () => { - const payload: Throttle = 'some string'; - const decoded = DefaultThrottleNull.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate an array with a number', () => { - const payload = 5; - const decoded = DefaultThrottleNull.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "DefaultThreatNull"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should return a default "null" if not provided a value', () => { - const payload = undefined; - const decoded = DefaultThrottleNull.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(null); - }); -}); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.ts deleted file mode 100644 index e9b9ec27ea418..0000000000000 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; -import { throttle, ThrottleOrNull } from '../throttle'; - -/** - * Types the DefaultThrottleNull as: - * - If null or undefined, then a null will be set - */ -export const DefaultThrottleNull = new t.Type( - 'DefaultThreatNull', - throttle.is, - (input, context): Either => - input == null ? t.success(null) : throttle.validate(input, context), - t.identity -); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts index 19e75dcc3be07..d7d636ad0994e 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts @@ -6,13 +6,15 @@ * Side Public License, v 1. */ +import { TimeDuration } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; -export const throttle = t.string; +export const throttle = t.union([ + t.literal('no_actions'), + t.literal('rule'), + TimeDuration({ allowedUnits: ['h', 'd'] }), +]); export type Throttle = t.TypeOf; export const throttleOrNull = t.union([throttle, t.null]); export type ThrottleOrNull = t.TypeOf; - -export const throttleOrNullOrUndefined = t.union([throttle, t.null, t.undefined]); -export type ThrottleOrUndefinedOrNull = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts index 872affa9989a7..dc6b5e61ebbf6 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.test.ts @@ -6,122 +6,315 @@ * Side Public License, v 1. */ -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { TimeDuration } from '.'; import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; describe('TimeDuration', () => { - test('it should validate a correctly formed TimeDuration with time unit of seconds', () => { - const payload = '1s'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + describe('with allowedDurations', () => { + test('it should validate a correctly formed TimeDuration with an allowed duration of 1s', () => { + const payload = '1s'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 's'], + [2, 'h'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); - test('it should validate a correctly formed TimeDuration with time unit of minutes', () => { - const payload = '100m'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should validate a correctly formed TimeDuration with an allowed duration of 7d', () => { + const payload = '1s'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 's'], + [2, 'h'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); - test('it should validate a correctly formed TimeDuration with time unit of hours', () => { - const payload = '10000000h'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate a time duration if the allowed durations does not include it', () => { + const payload = '24h'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 's'], + [2, 'h'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "24h" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate a TimeDuration of 0 length', () => { - const payload = '0s'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate a an allowed duration with a negative number', () => { + const payload = '10s'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 's'], + [-7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "0s" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "[[1,"s"],[-7,"d"]]" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate a negative TimeDuration', () => { - const payload = '-10s'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate an allowed duration with a fractional number', () => { + const payload = '1.5s'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 's'], + [-7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "-10s" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1.5s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate a decimal TimeDuration', () => { - const payload = '1.5s'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate a an allowed duration with a duration of 0', () => { + const payload = '10s'; + const decoded = TimeDuration({ + allowedDurations: [ + [0, 's'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1.5s" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "[[0,"s"],[7,"d"]]" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate a TimeDuration with some other time unit', () => { - const payload = '10000000w'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate a TimeDuration with an invalid time unit', () => { + const payload = '10000000days'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "10000000w" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "10000000days" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => { - const payload = '100ff0000w'; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => { + const payload = '100ff0000w'; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "100ff0000w" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "100ff0000w" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate an empty string', () => { - const payload = ''; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate an empty string', () => { + const payload = ''; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "TimeDuration"']); - expect(message.schema).toEqual({}); - }); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate an number', () => { + const payload = 100; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); - test('it should NOT validate an number', () => { - const payload = 100; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "100" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "100" supplied to "TimeDuration"', - ]); - expect(message.schema).toEqual({}); + test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => { + const payload = `${Math.pow(2, 53)}h`; + const decoded = TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + `Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`, + ]); + expect(message.schema).toEqual({}); + }); }); + describe('with allowedUnits', () => { + test('it should validate a correctly formed TimeDuration with time unit of seconds', () => { + const payload = '1s'; + const decoded = TimeDuration({ allowedUnits: ['s'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate a correctly formed TimeDuration with time unit of minutes', () => { + const payload = '100m'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate a correctly formed TimeDuration with time unit of hours', () => { + const payload = '10000000h'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate a correctly formed TimeDuration with time unit of days', () => { + const payload = '7d'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT validate a correctly formed TimeDuration with time unit of seconds if it is not an allowed unit', () => { + const payload = '30s'; + const decoded = TimeDuration({ allowedUnits: ['m', 'h', 'd'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "30s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a negative TimeDuration', () => { + const payload = '-10s'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-10s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a fractional number', () => { + const payload = '1.5s'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1.5s" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a TimeDuration with an invalid time unit', () => { + const payload = '10000000days'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "10000000days" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => { + const payload = '100ff0000w'; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "100ff0000w" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate an empty string', () => { + const payload = ''; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate an number', () => { + const payload = 100; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload); + const message = foldLeftRight(decoded); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "100" supplied to "TimeDuration"', + ]); + expect(message.schema).toEqual({}); + }); - test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => { - const payload = `${Math.pow(2, 53)}h`; - const decoded = TimeDuration.decode(payload); - const message = pipe(decoded, foldLeftRight); + test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => { + const payload = `${Math.pow(2, 53)}h`; + const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload); + const message = foldLeftRight(decoded); - expect(getPaths(left(message.errors))).toEqual([ - `Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`, - ]); - expect(message.schema).toEqual({}); + expect(getPaths(left(message.errors))).toEqual([ + `Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`, + ]); + expect(message.schema).toEqual({}); + }); }); }); diff --git a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts index 5549979ac68de..0b69c62e0d2f5 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts @@ -12,38 +12,60 @@ import { Either } from 'fp-ts/lib/Either'; /** * Types the TimeDuration as: * - A string that is not empty, and composed of a positive integer greater than 0 followed by a unit of time - * - in the format {safe_integer}{timeUnit}, e.g. "30s", "1m", "2h" + * - in the format {safe_integer}{timeUnit}, e.g. "30s", "1m", "2h", "7d" */ -export const TimeDuration = new t.Type( - 'TimeDuration', - t.string.is, - (input, context): Either => { - if (typeof input === 'string' && input.trim() !== '') { - try { - const inputLength = input.length; - const unit = input.trim().at(-1); - const time = parseFloat(input.trim().substring(0, inputLength - 1)); - if (!Number.isInteger(time)) { - return t.failure(input, context); - } - if ( - time >= 1 && - Number.isSafeInteger(time) && - (unit === 's' || unit === 'm' || unit === 'h') - ) { - return t.success(input); - } else { +type TimeUnits = 's' | 'm' | 'h' | 'd' | 'w' | 'y'; +interface TimeDurationWithAllowedDurations { + allowedDurations: Array<[number, TimeUnits]>; + allowedUnits?: never; +} +interface TimeDurationWithAllowedUnits { + allowedUnits: TimeUnits[]; + allowedDurations?: never; +} + +type TimeDurationType = TimeDurationWithAllowedDurations | TimeDurationWithAllowedUnits; + +const isTimeSafe = (time: number) => time >= 1 && Number.isSafeInteger(time); + +export const TimeDuration = ({ allowedUnits, allowedDurations }: TimeDurationType) => { + return new t.Type( + 'TimeDuration', + t.string.is, + (input, context): Either => { + if (typeof input === 'string' && input.trim() !== '') { + try { + const inputLength = input.length; + const time = Number(input.trim().substring(0, inputLength - 1)); + const unit = input.trim().at(-1); + if (!isTimeSafe(time)) { + return t.failure(input, context); + } + if (allowedDurations) { + for (const [allowedTime, allowedUnit] of allowedDurations) { + if (!isTimeSafe(allowedTime)) { + return t.failure(allowedDurations, context); + } + if (allowedTime === time && allowedUnit === unit) { + return t.success(input); + } + } + return t.failure(input, context); + } else if (allowedUnits.includes(unit as TimeUnits)) { + return t.success(input); + } else { + return t.failure(input, context); + } + } catch (error) { return t.failure(input, context); } - } catch (error) { + } else { return t.failure(input, context); } - } else { - return t.failure(input, context); - } - }, - t.identity -); + }, + t.identity + ); +}; export type TimeDurationC = typeof TimeDuration; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts index a74845721a022..0140e5f8d9262 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts @@ -9,7 +9,6 @@ import * as t from 'io-ts'; import { NonEmptyArray, TimeDuration, enumeration } from '@kbn/securitysolution-io-ts-types'; import { - throttle, action_group as actionGroup, action_params as actionParams, action_id as actionId, @@ -41,6 +40,18 @@ export enum BulkActionEditType { 'set_schedule' = 'set_schedule', } +export const throttleForBulkActions = t.union([ + t.literal('rule'), + TimeDuration({ + allowedDurations: [ + [1, 'h'], + [1, 'd'], + [7, 'd'], + ], + }), +]); +export type ThrottleForBulkActions = t.TypeOf; + const bulkActionEditPayloadTags = t.type({ type: t.union([ t.literal(BulkActionEditType.add_tags), @@ -96,7 +107,7 @@ const bulkActionEditPayloadRuleActions = t.type({ t.literal(BulkActionEditType.set_rule_actions), ]), value: t.type({ - throttle, + throttle: throttleForBulkActions, actions: t.array(normalizedRuleAction), }), }); @@ -106,8 +117,8 @@ export type BulkActionEditPayloadRuleActions = t.TypeOf; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/throttle_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/throttle_description.tsx index 69110b2e53602..c0b6780ea8bc5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/throttle_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/throttle_description.tsx @@ -6,10 +6,13 @@ */ import { find } from 'lodash/fp'; -import { THROTTLE_OPTIONS, DEFAULT_THROTTLE_OPTION } from '../throttle_select_field'; +import { + THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING, + DEFAULT_THROTTLE_OPTION, +} from '../throttle_select_field'; export const buildThrottleDescription = (value = DEFAULT_THROTTLE_OPTION.value, title: string) => { - const throttleOption = find(['value', value], THROTTLE_OPTIONS); + const throttleOption = find(['value', value], THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING); return { title, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx index 5cdf89df572cf..fe2c5d8fa4ee0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx @@ -33,7 +33,7 @@ import { Form, UseField, useForm, useFormData } from '../../../../shared_imports import { StepContentWrapper } from '../step_content_wrapper'; import { ThrottleSelectField, - THROTTLE_OPTIONS, + THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING, DEFAULT_THROTTLE_OPTION, } from '../throttle_select_field'; import { RuleActionsField } from '../rule_actions_field'; @@ -62,11 +62,14 @@ const GhostFormField = () => <>; const getThrottleOptions = (throttle?: string | null) => { // Add support for throttle options set by the API - if (throttle && findIndex(['value', throttle], THROTTLE_OPTIONS) < 0) { - return [...THROTTLE_OPTIONS, { value: throttle, text: throttle }]; + if ( + throttle && + findIndex(['value', throttle], THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING) < 0 + ) { + return [...THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING, { value: throttle, text: throttle }]; } - return THROTTLE_OPTIONS; + return THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING; }; const DisplayActionsHeader = () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/throttle_select_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/throttle_select_field/index.tsx index 4bc15d418a38b..264e72fafb60c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/throttle_select_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/throttle_select_field/index.tsx @@ -13,15 +13,19 @@ import { } from '../../../../../common/constants'; import { SelectField } from '../../../../shared_imports'; -export const THROTTLE_OPTIONS = [ - { value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' }, +export const THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS = [ { value: NOTIFICATION_THROTTLE_RULE, text: 'On each rule execution' }, { value: '1h', text: 'Hourly' }, { value: '1d', text: 'Daily' }, { value: '7d', text: 'Weekly' }, ]; -export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS[0]; +export const THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING = [ + { value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' }, + ...THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS, +]; + +export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING[0]; type ThrottleSelectField = typeof SelectField; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx index 8a9e52c9d5528..0ec448a1ab1fb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx @@ -12,6 +12,7 @@ import type { RuleAction, ActionTypeRegistryContract, } from '@kbn/triggers-actions-ui-plugin/public'; + import type { FormSchema } from '../../../../../../../shared_imports'; import { useForm, @@ -21,9 +22,12 @@ import { getUseField, Field, } from '../../../../../../../shared_imports'; -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../../../common/constants'; +import type { + BulkActionEditPayload, + ThrottleForBulkActions, +} from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { NOTIFICATION_THROTTLE_RULE } from '../../../../../../../../common/constants'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; import { bulkAddRuleActions as i18n } from '../translations'; @@ -32,7 +36,7 @@ import { useKibana } from '../../../../../../../common/lib/kibana'; import { ThrottleSelectField, - THROTTLE_OPTIONS, + THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS, } from '../../../../../../components/rules/throttle_select_field'; import { getAllActionMessageParams } from '../../../helpers'; @@ -42,7 +46,7 @@ import { validateRuleActionsField } from '../../../../../../containers/detection const CommonUseField = getUseField({ component: Field }); export interface RuleActionsFormData { - throttle: string; + throttle: ThrottleForBulkActions; actions: RuleAction[]; overwrite: boolean; } @@ -68,7 +72,7 @@ const getFormSchema = ( }); const defaultFormData: RuleActionsFormData = { - throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, + throttle: NOTIFICATION_THROTTLE_RULE, actions: [], overwrite: false, }; @@ -93,7 +97,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction defaultValue: defaultFormData, }); - const [{ overwrite, throttle }] = useFormData({ form, watch: ['overwrite', 'throttle'] }); + const [{ overwrite }] = useFormData({ form, watch: ['overwrite', 'throttle'] }); const handleSubmit = useCallback(async () => { const { data, isValid } = await form.submit(); @@ -121,7 +125,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction dataTestSubj: 'bulkEditRulesRuleActionThrottle', hasNoInitialSelection: false, euiFieldProps: { - options: THROTTLE_OPTIONS, + options: THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS, }, }), [] @@ -129,8 +133,6 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction const messageVariables = useMemo(() => getAllActionMessageParams(), []); - const showActionsSelect = throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS; - return ( -
  • - - - - ), - overwriteActionsCheckbox: ( - - - - ), - }} - /> -
  • {i18n.RULE_VARIABLES_DETAIL}
  • @@ -193,18 +171,14 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction /> - {showActionsSelect && ( - <> - - - - )} + + [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].includes(rule.type) ); - // bulk edit actions are applying in a historical order. + // bulk edit actions are applied in historical order. // So, we need to find a rule action that will be applied the last, to be able to check if rule should be muted/unmuted const rulesAction = ruleActions.pop(); if (rulesAction) { - const muteOrUnmuteErrors: BulkEditError[] = []; - const rulesToMuteOrUnmute = await pMap( + const unmuteErrors: BulkEditError[] = []; + const rulesToUnmute = await pMap( result.rules, async (rule) => { try { if (rule.muteAll && rulesAction.value.throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS) { await rulesClient.unmuteAll({ id: rule.id }); return (await readRules({ rulesClient, id: rule.id, ruleId: undefined })) ?? rule; - } else if ( - !rule.muteAll && - rulesAction.value.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS - ) { - await rulesClient.muteAll({ id: rule.id }); - return (await readRules({ rulesClient, id: rule.id, ruleId: undefined })) ?? rule; } return rule; } catch (err) { - muteOrUnmuteErrors.push({ + unmuteErrors.push({ message: err.message, rule: { id: rule.id, @@ -115,8 +106,8 @@ export const bulkEditRules = async ({ return { ...result, - rules: rulesToMuteOrUnmute.filter((rule): rule is RuleAlertType => rule != null), - errors: [...result.errors, ...muteOrUnmuteErrors], + rules: rulesToUnmute.filter((rule): rule is RuleAlertType => rule != null), + errors: [...result.errors, ...unmuteErrors], }; } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index b60ce575c4cd0..c17e679b6be31 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -1513,10 +1513,9 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('throttle', () => { + // For bulk editing of rule actions, NOTIFICATION_THROTTLE_NO_ACTIONS + // is not available as payload, because "Perform No Actions" is not a valid option const casesForEmptyActions = [ - { - payloadThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS, - }, { payloadThrottle: NOTIFICATION_THROTTLE_RULE, }, @@ -1561,10 +1560,6 @@ export default ({ getService }: FtrProviderContext): void => { }); const casesForNonEmptyActions = [ - { - payloadThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS, - expectedThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS, - }, { payloadThrottle: NOTIFICATION_THROTTLE_RULE, expectedThrottle: NOTIFICATION_THROTTLE_RULE, @@ -1616,12 +1611,9 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('notifyWhen', () => { + // For bulk editing of rule actions, NOTIFICATION_THROTTLE_NO_ACTIONS + // is not available as payload, because "Perform No Actions" is not a valid option const cases = [ - { - payload: { throttle: NOTIFICATION_THROTTLE_NO_ACTIONS }, - // keeps existing default value which is onActiveAlert - expected: { notifyWhen: 'onActiveAlert' }, - }, { payload: { throttle: '1d' }, expected: { notifyWhen: 'onThrottleInterval' }, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index a8dc735376f69..705714ec3ba50 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -220,7 +220,7 @@ export default ({ getService }: FtrProviderContext) => { updatedRule.rule_id = createRuleBody.rule_id; updatedRule.name = 'some other name'; updatedRule.actions = [action1]; - updatedRule.throttle = '1m'; + updatedRule.throttle = '1d'; delete updatedRule.id; const { body } = await supertest @@ -243,7 +243,7 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]; - outputRule.throttle = '1m'; + outputRule.throttle = '1d'; const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); expect(bodyToCompare).to.eql(outputRule); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index dc7209b9f1c98..2f5fb63382552 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -150,12 +150,12 @@ export default ({ getService }: FtrProviderContext) => { const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.name = 'some other name'; updatedRule1.actions = [action1]; - updatedRule1.throttle = '1m'; + updatedRule1.throttle = '1d'; const updatedRule2 = getSimpleRuleUpdate('rule-2'); updatedRule2.name = 'some other name'; updatedRule2.actions = [action1]; - updatedRule2.throttle = '1m'; + updatedRule2.throttle = '1d'; // update both rule names const { body }: { body: FullResponseSchema[] } = await supertest @@ -179,7 +179,7 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]; - outputRule.throttle = '1m'; + outputRule.throttle = '1d'; const bodyToCompare = removeServerGeneratedProperties(response); expect(bodyToCompare).to.eql(outputRule); }); From 4695634fec475d6e6c0e42c23840467f272968d6 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 27 Sep 2022 20:02:47 +0300 Subject: [PATCH 089/172] Test assignees section on upgrade (#141973) --- x-pack/test/functional_with_es_ssl/apps/cases/upgrade.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/upgrade.ts b/x-pack/test/functional_with_es_ssl/apps/cases/upgrade.ts index cbbbb90c05abc..c1e2979d313bb 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/upgrade.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/upgrade.ts @@ -18,6 +18,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const supertest = getService('supertest'); const testSubjects = getService('testSubjects'); const find = getService('find'); + const toasts = getService('toasts'); const updateConnector = async (id: string, req: Record) => { const { body: connector } = await supertest @@ -74,6 +75,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); describe('Case view page', function () { + it('does not show any error toasters', async () => { + expect(await toasts.getToastCount()).to.be(0); + }); + it('shows the title correctly', async () => { const title = await testSubjects.find('header-page-title'); expect(await title.getVisibleText()).equal('Upgrade test in Kibana'); @@ -275,6 +280,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { it('shows the add comment button', async () => { await testSubjects.exists('submit-comment'); }); + + it('shows the assignees section', async () => { + await testSubjects.exists('case-view-assignees'); + }); }); }); }; From cbb2e2a4c6d181b87dffafba495cc391ceeb9da7 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:20:34 +0200 Subject: [PATCH 090/172] [Security Solution][Endpoint][Response Actions] Additional actions log tests (#141447) * list handler tests - tests the logic for branching with status filter - tests request params formats for strings into string [] - tests logic for index_not_found_exception * verify that hostnames filter also shows inactive agents refs elastic/security-team/issues/4721 * verify useGetEndpointsList lists 50 agents at most Even when metadata list has more than 50 agents the `useGetEndpointsList` hook should list only 50 at most refs elastic/security-team/issues/4721 * verify useGetEndpointsList shows 10 more in list When all 50 or more agents are selected in the filter list, the hook should list 10 more agents refs elastic/security-team/issues/4721 * Add a metadata list test for hostname search refs elastic/security-team/issues/4721 * Refactor table props into a hook fixes elastic/security-team/issues/4990 * remove redundant checks review changes (@paul-tavares) --- .../components/hooks.tsx | 1 - .../response_actions_log.test.tsx | 20 + .../response_actions_log.tsx | 469 +----------------- .../use_response_actions_log_table.tsx | 441 ++++++++++++++++ .../endpoint/use_get_endpoints_list.test.ts | 125 +++++ .../routes/actions/list_handler.test.ts | 169 +++++++ .../apis/metadata.fixtures.ts | 12 +- .../apis/metadata.ts | 20 + 8 files changed, 807 insertions(+), 450 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/actions/list_handler.test.ts diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx index bb2c1b7761d5a..34cc5a3ca0f99 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx @@ -159,7 +159,6 @@ export const getCommandKey = ( } }; -// TODO: add more filter names here export type FilterName = keyof typeof FILTER_NAMES; export const useActionsLogFilter = ({ filterName, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index 30148c9643baa..556c765296337 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -393,6 +393,26 @@ describe('Response actions history', () => { expect(noTrays).toEqual([]); }); + it('should contain relevant details in each expanded row', async () => { + render(); + const { getAllByTestId } = renderResult; + + const expandButtons = getAllByTestId(`${testPrefix}-expand-button`); + expandButtons.map((button) => userEvent.click(button)); + const trays = getAllByTestId(`${testPrefix}-details-tray`); + expect(trays).toBeTruthy(); + expect(Array.from(trays[0].querySelectorAll('dt')).map((title) => title.textContent)).toEqual( + [ + 'Command placed', + 'Execution started on', + 'Execution completed', + 'Input', + 'Parameters', + 'Output:', + ] + ); + }); + it('should refresh data when autoRefresh is toggled on', async () => { render(); const { getByTestId } = renderResult; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index 2a9362830c76d..ac9a0829bb7b8 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -5,112 +5,29 @@ * 2.0. */ -import { - EuiAvatar, - EuiBasicTable, - EuiButtonIcon, - EuiDescriptionList, - EuiEmptyPrompt, - EuiFacetButton, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiScreenReaderOnly, - EuiI18nNumber, - EuiText, - EuiCodeBlock, - EuiToolTip, - RIGHT_ALIGNMENT, -} from '@elastic/eui'; -import { euiStyled, css } from '@kbn/kibana-react-plugin/common'; +import { EuiBasicTable, EuiEmptyPrompt, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; -import type { HorizontalAlignment, CriteriaWithPagination } from '@elastic/eui'; +import type { CriteriaWithPagination } from '@elastic/eui'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { ResponseActions, ResponseActionStatus, } from '../../../../common/endpoint/service/response_actions/constants'; -import { getEmptyValue } from '../../../common/components/empty_value'; -import { FormattedDate } from '../../../common/components/formatted_date'; + import type { ActionListApiResponse } from '../../../../common/endpoint/types'; import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; import { ManagementEmptyStateWrapper } from '../management_empty_state_wrapper'; import { useGetEndpointActionList } from '../../hooks'; -import { OUTPUT_MESSAGES, TABLE_COLUMN_NAMES, UX_MESSAGES } from './translations'; -import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants'; +import { UX_MESSAGES } from './translations'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import { ActionsLogFilters } from './components/actions_log_filters'; -import { - getActionStatus, - getUiCommand, - getCommandKey, - useDateRangePicker, -} from './components/hooks'; -import { StatusBadge } from './components/status_badge'; +import { getCommandKey, useDateRangePicker } from './components/hooks'; import { useActionHistoryUrlParams } from './components/use_action_history_url_params'; import { useUrlPagination } from '../../hooks/use_url_pagination'; import { ManagementPageLoader } from '../management_page_loader'; import { ActionsLogEmptyState } from './components/actions_log_empty_state'; - -const emptyValue = getEmptyValue(); - -// Truncated usernames -const StyledFacetButton = euiStyled(EuiFacetButton)` - .euiText { - margin-top: 0.38rem; - overflow-y: visible !important; - } -`; - -const customDescriptionListCss = css` - &.euiDescriptionList { - > .euiDescriptionList__title { - color: ${(props) => props.theme.eui.euiColorDarkShade}; - font-size: ${(props) => props.theme.eui.euiFontSizeXS}; - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } - - > .euiDescriptionList__description { - font-weight: ${(props) => props.theme.eui.euiFontWeightSemiBold}; - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } - } -`; - -const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ - compressed: true, - type: 'column', -})` - ${customDescriptionListCss} -`; - -// output section styles -const topSpacingCss = css` - ${(props) => `${props.theme.eui.euiCodeBlockPaddingModifiers.paddingMedium} 0`} -`; -const dashedBorderCss = css` - ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; -`; -const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` - ${customDescriptionListCss} - dd { - margin: ${topSpacingCss}; - padding: ${topSpacingCss}; - border-top: ${dashedBorderCss}; - border-bottom: ${dashedBorderCss}; - } -`; - -// code block styles -const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ - transparentBackground: true, - paddingSize: 'none', -})` - code { - color: ${(props) => props.theme.eui.euiColorDarkShade} !important; - } -`; +import { useResponseActionsLogTable } from './use_response_actions_log_table'; export const ResponseActionsLog = memo< Pick & { @@ -131,9 +48,6 @@ export const ResponseActionsLog = memo< } = useActionHistoryUrlParams(); const getTestId = useTestIdGenerator('response-actions-list'); - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ - [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; - }>({}); // Used to decide if display global loader or not (only the fist time tha page loads) const [isFirstAttempt, setIsFirstAttempt] = useState(true); @@ -183,6 +97,19 @@ export const ResponseActionsLog = memo< { retry: false } ); + // total actions + const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); + + // table columns and expanded row state + const { itemIdToExpandedRowMap, recordRangeLabel, responseActionListColumns, tablePagination } = + useResponseActionsLogTable({ + showHostNames, + pageIndex: isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1, + pageSize: isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize, + queryParams, + totalItemCount, + }); + // Hide page header when there is no actions index calling the setIsDataInResponse with false value. // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track // if the API request was done for the first time. @@ -248,310 +175,6 @@ export const ResponseActionsLog = memo< [setQueryParams] ); - // total actions - const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); - - // expanded tray contents - const toggleDetails = useCallback( - (item: ActionListApiResponse['data'][number]) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item.id]) { - delete itemIdToExpandedRowMapValues[item.id]; - } else { - const { - startedAt, - completedAt, - isCompleted, - wasSuccessful, - isExpired, - command: _command, - parameters, - } = item; - - const parametersList = parameters - ? Object.entries(parameters).map(([key, value]) => { - return `${key}:${value}`; - }) - : undefined; - - const command = getUiCommand(_command); - const dataList = [ - { - title: OUTPUT_MESSAGES.expandSection.placedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.startedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.completedAt, - description: `${completedAt ?? emptyValue}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.input, - description: `${command}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.parameters, - description: parametersList ? parametersList : emptyValue, - }, - ].map(({ title, description }) => { - return { - title: {title}, - description: {description}, - }; - }); - - const outputList = [ - { - title: ( - {`${OUTPUT_MESSAGES.expandSection.output}:`} - ), - description: ( - // codeblock for output - - {isExpired - ? OUTPUT_MESSAGES.hasExpired(command) - : isCompleted - ? wasSuccessful - ? OUTPUT_MESSAGES.wasSuccessful(command) - : OUTPUT_MESSAGES.hasFailed(command) - : OUTPUT_MESSAGES.isPending(command)} - - ), - }, - ]; - - itemIdToExpandedRowMapValues[item.id] = ( - <> - - - - - - - - - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }, - [getTestId, itemIdToExpandedRowMap] - ); - // memoized callback for toggleDetails - const onClickCallback = useCallback( - (actionListDataItem: ActionListApiResponse['data'][number]) => () => - toggleDetails(actionListDataItem), - [toggleDetails] - ); - - // table column - const responseActionListColumns = useMemo(() => { - const columns = [ - { - field: 'startedAt', - name: TABLE_COLUMN_NAMES.time, - width: !showHostNames ? '21%' : '15%', - truncateText: true, - render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { - return ( - - ); - }, - }, - { - field: 'command', - name: TABLE_COLUMN_NAMES.command, - width: !showHostNames ? '21%' : '10%', - truncateText: true, - render: (_command: ActionListApiResponse['data'][number]['command']) => { - const command = getUiCommand(_command); - return ( - - - {command} - - - ); - }, - }, - { - field: 'createdBy', - name: TABLE_COLUMN_NAMES.user, - width: !showHostNames ? '21%' : '14%', - truncateText: true, - render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { - return ( - - } - > - - - {userId} - - - - ); - }, - }, - // conditional hostnames column - { - field: 'hosts', - name: TABLE_COLUMN_NAMES.hosts, - width: '20%', - truncateText: true, - render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { - const hosts = _hosts && Object.values(_hosts); - // join hostnames if the action is for multiple agents - // and skip empty strings for names if any - const _hostnames = hosts - .reduce((acc, host) => { - if (host.name.trim()) { - acc.push(host.name); - } - return acc; - }, []) - .join(', '); - - let hostnames = _hostnames; - if (!_hostnames) { - if (hosts.length > 1) { - // when action was for a single agent and no host name - hostnames = UX_MESSAGES.unenrolled.hosts; - } else if (hosts.length === 1) { - // when action was for a multiple agents - // and none of them have a host name - hostnames = UX_MESSAGES.unenrolled.host; - } - } - return ( - - - {hostnames} - - - ); - }, - }, - { - field: 'comment', - name: TABLE_COLUMN_NAMES.comments, - width: !showHostNames ? '21%' : '30%', - truncateText: true, - render: (comment: ActionListApiResponse['data'][number]['comment']) => { - return ( - - - {comment ?? emptyValue} - - - ); - }, - }, - { - field: 'status', - name: TABLE_COLUMN_NAMES.status, - width: !showHostNames ? '15%' : '10%', - render: (_status: ActionListApiResponse['data'][number]['status']) => { - const status = getActionStatus(_status); - - return ( - - - - ); - }, - }, - { - field: '', - align: RIGHT_ALIGNMENT as HorizontalAlignment, - width: '40px', - isExpander: true, - name: ( - - {UX_MESSAGES.screenReaderExpand} - - ), - render: (actionListDataItem: ActionListApiResponse['data'][number]) => { - return ( - - ); - }, - }, - ]; - // filter out the `hosts` column - // if showHostNames is FALSE - if (!showHostNames) { - return columns.filter((column) => column.field !== 'hosts'); - } - return columns; - }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); - - // table pagination - const tablePagination = useMemo(() => { - const pageIndex = isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1; - const pageSize = isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize; - return { - // this controls the table UI page - // to match 0-based table paging - pageIndex, - pageSize, - totalItemCount, - pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], - }; - }, [ - isFlyout, - paginationFromUrlParams.page, - paginationFromUrlParams.pageSize, - queryParams.page, - queryParams.pageSize, - totalItemCount, - ]); - // handle onChange const handleTableOnChange = useCallback( ({ page: _page }: CriteriaWithPagination) => { @@ -563,16 +186,12 @@ export const ResponseActionsLog = memo< page: index + 1, pageSize: size, }; - if (isFlyout) { - setQueryParams((prevState) => ({ - ...prevState, - ...pagingArgs, - })); - } else { - setQueryParams((prevState) => ({ - ...prevState, - ...pagingArgs, - })); + + setQueryParams((prevState) => ({ + ...prevState, + ...pagingArgs, + })); + if (!isFlyout) { setPaginationOnUrlParams({ ...pagingArgs, }); @@ -582,42 +201,6 @@ export const ResponseActionsLog = memo< [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] ); - // compute record ranges - const pagedResultsCount = useMemo(() => { - const page = queryParams.page ?? 1; - const perPage = queryParams?.pageSize ?? 10; - - const totalPages = Math.ceil(totalItemCount / perPage); - const fromCount = perPage * page - perPage + 1; - const toCount = - page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; - return { fromCount, toCount }; - }, [queryParams.page, queryParams.pageSize, totalItemCount]); - - // create range label to display - const recordRangeLabel = useMemo( - () => ( - - - - {'-'} - - - ), - total: , - recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, - }} - /> - - ), - [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] - ); - if (error?.body?.statusCode === 404 && error?.body?.message === 'index_not_found_exception') { return ; } else if (isFetching && isFirstAttempt) { @@ -675,7 +258,7 @@ export const ResponseActionsLog = memo< columns={responseActionListColumns} itemId="id" itemIdToExpandedRowMap={itemIdToExpandedRowMap} - isExpandable={true} + isExpandable pagination={tablePagination} onChange={handleTableOnChange} loading={isFetching} diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx new file mode 100644 index 0000000000000..e4dd30b468127 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx @@ -0,0 +1,441 @@ +/* + * 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, { useCallback, useMemo, useState } from 'react'; +import type { HorizontalAlignment } from '@elastic/eui'; + +import { + EuiI18nNumber, + EuiAvatar, + EuiButtonIcon, + EuiCodeBlock, + EuiDescriptionList, + EuiFacetButton, + EuiFlexGroup, + EuiFlexItem, + RIGHT_ALIGNMENT, + EuiScreenReaderOnly, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { ActionListApiResponse } from '../../../../common/endpoint/types'; +import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; +import { FormattedDate } from '../../../common/components/formatted_date'; +import { OUTPUT_MESSAGES, TABLE_COLUMN_NAMES, UX_MESSAGES } from './translations'; +import { getActionStatus, getUiCommand } from './components/hooks'; +import { getEmptyValue } from '../../../common/components/empty_value'; +import { StatusBadge } from './components/status_badge'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; +import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants'; + +const emptyValue = getEmptyValue(); + +// Truncated usernames +const StyledFacetButton = euiStyled(EuiFacetButton)` + .euiText { + margin-top: 0.38rem; + overflow-y: visible !important; + } +`; + +const customDescriptionListCss = css` + &.euiDescriptionList { + > .euiDescriptionList__title { + color: ${(props) => props.theme.eui.euiColorDarkShade}; + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + margin-top: ${(props) => props.theme.eui.euiSizeS}; + } + + > .euiDescriptionList__description { + font-weight: ${(props) => props.theme.eui.euiFontWeightSemiBold}; + margin-top: ${(props) => props.theme.eui.euiSizeS}; + } + } +`; + +const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ + compressed: true, + type: 'column', +})` + ${customDescriptionListCss} +`; + +// output section styles +const topSpacingCss = css` + ${(props) => `${props.theme.eui.euiCodeBlockPaddingModifiers.paddingMedium} 0`} +`; +const dashedBorderCss = css` + ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; +`; +const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` + ${customDescriptionListCss} + dd { + margin: ${topSpacingCss}; + padding: ${topSpacingCss}; + border-top: ${dashedBorderCss}; + border-bottom: ${dashedBorderCss}; + } +`; + +// code block styles +const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ + transparentBackground: true, + paddingSize: 'none', +})` + code { + color: ${(props) => props.theme.eui.euiColorDarkShade} !important; + } +`; + +export const useResponseActionsLogTable = ({ + pageIndex, + pageSize, + queryParams, + showHostNames, + totalItemCount, +}: { + pageIndex: number; + pageSize: number; + queryParams: EndpointActionListRequestQuery; + showHostNames: boolean; + totalItemCount: number; +}) => { + const getTestId = useTestIdGenerator('response-actions-list'); + + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ + [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; + }>({}); + + // expanded tray contents + const toggleDetails = useCallback( + (item: ActionListApiResponse['data'][number]) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[item.id]) { + delete itemIdToExpandedRowMapValues[item.id]; + } else { + const { + startedAt, + completedAt, + isCompleted, + wasSuccessful, + isExpired, + command: _command, + parameters, + } = item; + + const parametersList = parameters + ? Object.entries(parameters).map(([key, value]) => { + return `${key}:${value}`; + }) + : undefined; + + const command = getUiCommand(_command); + const dataList = [ + { + title: OUTPUT_MESSAGES.expandSection.placedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.startedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.completedAt, + description: `${completedAt ?? emptyValue}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.input, + description: `${command}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.parameters, + description: parametersList ? parametersList : emptyValue, + }, + ].map(({ title, description }) => { + return { + title: {title}, + description: {description}, + }; + }); + + const outputList = [ + { + title: ( + {`${OUTPUT_MESSAGES.expandSection.output}:`} + ), + description: ( + // codeblock for output + + {isExpired + ? OUTPUT_MESSAGES.hasExpired(command) + : isCompleted + ? wasSuccessful + ? OUTPUT_MESSAGES.wasSuccessful(command) + : OUTPUT_MESSAGES.hasFailed(command) + : OUTPUT_MESSAGES.isPending(command)} + + ), + }, + ]; + + itemIdToExpandedRowMapValues[item.id] = ( + <> + + + + + + + + + + ); + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }, + [getTestId, itemIdToExpandedRowMap] + ); + // memoized callback for toggleDetails + const onClickCallback = useCallback( + (actionListDataItem: ActionListApiResponse['data'][number]) => () => + toggleDetails(actionListDataItem), + [toggleDetails] + ); + + const responseActionListColumns = useMemo(() => { + const columns = [ + { + field: 'startedAt', + name: TABLE_COLUMN_NAMES.time, + width: !showHostNames ? '21%' : '15%', + truncateText: true, + render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { + return ( + + ); + }, + }, + { + field: 'command', + name: TABLE_COLUMN_NAMES.command, + width: !showHostNames ? '21%' : '10%', + truncateText: true, + render: (_command: ActionListApiResponse['data'][number]['command']) => { + const command = getUiCommand(_command); + return ( + + + {command} + + + ); + }, + }, + { + field: 'createdBy', + name: TABLE_COLUMN_NAMES.user, + width: !showHostNames ? '21%' : '14%', + truncateText: true, + render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { + return ( + + } + > + + + {userId} + + + + ); + }, + }, + // conditional hostnames column + { + field: 'hosts', + name: TABLE_COLUMN_NAMES.hosts, + width: '20%', + truncateText: true, + render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { + const hosts = _hosts && Object.values(_hosts); + // join hostnames if the action is for multiple agents + // and skip empty strings for names if any + const _hostnames = hosts + .reduce((acc, host) => { + if (host.name.trim()) { + acc.push(host.name); + } + return acc; + }, []) + .join(', '); + + let hostnames = _hostnames; + if (!_hostnames) { + if (hosts.length > 1) { + // when action was for a single agent and no host name + hostnames = UX_MESSAGES.unenrolled.hosts; + } else if (hosts.length === 1) { + // when action was for a multiple agents + // and none of them have a host name + hostnames = UX_MESSAGES.unenrolled.host; + } + } + return ( + + + {hostnames} + + + ); + }, + }, + { + field: 'comment', + name: TABLE_COLUMN_NAMES.comments, + width: !showHostNames ? '21%' : '30%', + truncateText: true, + render: (comment: ActionListApiResponse['data'][number]['comment']) => { + return ( + + + {comment ?? emptyValue} + + + ); + }, + }, + { + field: 'status', + name: TABLE_COLUMN_NAMES.status, + width: !showHostNames ? '15%' : '10%', + render: (_status: ActionListApiResponse['data'][number]['status']) => { + const status = getActionStatus(_status); + + return ( + + + + ); + }, + }, + { + field: '', + align: RIGHT_ALIGNMENT as HorizontalAlignment, + width: '40px', + isExpander: true, + name: ( + + {UX_MESSAGES.screenReaderExpand} + + ), + render: (actionListDataItem: ActionListApiResponse['data'][number]) => { + return ( + + ); + }, + }, + ]; + // filter out the `hosts` column + // if showHostNames is FALSE + if (!showHostNames) { + return columns.filter((column) => column.field !== 'hosts'); + } + return columns; + }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); + + // table pagination + const tablePagination = useMemo(() => { + return { + pageIndex, + pageSize, + totalItemCount, + pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], + }; + }, [pageIndex, pageSize, totalItemCount]); + + // compute record ranges + const pagedResultsCount = useMemo(() => { + const page = queryParams.page ?? 1; + const perPage = queryParams?.pageSize ?? 10; + + const totalPages = Math.ceil(totalItemCount / perPage); + const fromCount = perPage * page - perPage + 1; + const toCount = + page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; + return { fromCount, toCount }; + }, [queryParams.page, queryParams.pageSize, totalItemCount]); + + // create range label to display + const recordRangeLabel = useMemo( + () => ( + + + + {'-'} + + + ), + total: , + recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, + }} + /> + + ), + [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] + ); + + return { itemIdToExpandedRowMap, responseActionListColumns, recordRangeLabel, tablePagination }; +}; diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts index 0804bef55b3e7..cdb1041cda7ed 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts @@ -11,6 +11,8 @@ import { useGetEndpointsList } from './use_get_endpoints_list'; import { HOST_METADATA_LIST_ROUTE } from '../../../../common/endpoint/constants'; import { useQuery as _useQuery } from '@tanstack/react-query'; import { endpointMetadataHttpMocks } from '../../pages/endpoint_hosts/mocks'; +import { EndpointStatus, HostStatus } from '../../../../common/endpoint/types'; +import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; const useQueryMock = _useQuery as jest.Mock; @@ -107,4 +109,127 @@ describe('useGetEndpointsList hook', () => { }) ); }); + + it('should also list inactive agents', async () => { + const getApiResponse = apiMocks.responseProvider.metadataList.getMockImplementation(); + + // set a few of the agents as inactive/unenrolled + apiMocks.responseProvider.metadataList.mockImplementation(() => { + if (getApiResponse) { + return { + ...getApiResponse(), + data: getApiResponse().data.map((item, i) => { + const isInactiveIndex = [0, 1, 3].includes(i); + return { + ...item, + host_status: isInactiveIndex ? HostStatus.INACTIVE : item.host_status, + metadata: { + ...item.metadata, + host: { + ...item.metadata.host, + hostname: isInactiveIndex + ? `${item.metadata.host.hostname}-inactive` + : item.metadata.host.hostname, + }, + Endpoint: { + ...item.metadata.Endpoint, + status: isInactiveIndex + ? EndpointStatus.unenrolled + : item.metadata.Endpoint.status, + }, + }, + }; + }), + }; + } + throw new Error('some error'); + }); + + // verify useGetEndpointsList hook returns the same inactive agents + const res = await renderReactQueryHook(() => useGetEndpointsList({ searchString: 'inactive' })); + expect( + res.data?.map((host) => host.name.split('-')[2]).filter((name) => name === 'inactive').length + ).toEqual(3); + }); + + it('should only list 50 agents when more than 50 in the metadata list API', async () => { + const getApiResponse = apiMocks.responseProvider.metadataList.getMockImplementation(); + + apiMocks.responseProvider.metadataList.mockImplementation(() => { + if (getApiResponse) { + const generator = new EndpointDocGenerator('seed'); + const total = 60; + const data = Array.from({ length: total }, () => { + const endpoint = { + metadata: generator.generateHostMetadata(), + host_status: HostStatus.UNHEALTHY, + }; + + generator.updateCommonInfo(); + + return endpoint; + }); + + return { + ...getApiResponse(), + data, + page: 0, + // this page size is not used by the hook (it uses the default of 50) + // this is only for the test + pageSize: 80, + total, + }; + } + throw new Error('some error'); + }); + + // verify useGetEndpointsList hook returns all 50 agents in the list + const res = await renderReactQueryHook(() => useGetEndpointsList({ searchString: '' })); + expect(res.data?.length).toEqual(50); + }); + + it('should only list 10 more agents when 50 or more agents are already selected', async () => { + const getApiResponse = apiMocks.responseProvider.metadataList.getMockImplementation(); + + apiMocks.responseProvider.metadataList.mockImplementation(() => { + if (getApiResponse) { + const generator = new EndpointDocGenerator('seed'); + const total = 61; + const data = Array.from({ length: total }, () => { + const endpoint = { + metadata: generator.generateHostMetadata(), + host_status: HostStatus.UNHEALTHY, + }; + + generator.updateCommonInfo(); + + return endpoint; + }); + + return { + ...getApiResponse(), + data, + page: 0, + // since we're mocking that all 50 agents are selected + // page size is set to max allowed + pageSize: 10000, + total, + }; + } + throw new Error('some error'); + }); + + // get the first 50 agents to select + const agentIdsToSelect = apiMocks.responseProvider + .metadataList() + .data.map((d) => d.metadata.agent.id) + .slice(0, 50); + + // call useGetEndpointsList with all 50 agents selected + const res = await renderReactQueryHook(() => + useGetEndpointsList({ searchString: '', selectedAgentIds: agentIdsToSelect }) + ); + // verify useGetEndpointsList hook returns 60 agents + expect(res.data?.length).toEqual(60); + }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/list_handler.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list_handler.test.ts new file mode 100644 index 0000000000000..51326c8adbd12 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list_handler.test.ts @@ -0,0 +1,169 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { KibanaResponseFactory, RequestHandler, RouteConfig } from '@kbn/core/server'; +import { + coreMock, + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, + loggingSystemMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; +import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; +import { ENDPOINTS_ACTION_LIST_ROUTE } from '../../../../common/endpoint/constants'; +import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; +import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; +import { EndpointAppContextService } from '../../endpoint_app_context_services'; +import { + createMockEndpointAppContextServiceSetupContract, + createMockEndpointAppContextServiceStartContract, + createRouteHandlerContext, +} from '../../mocks'; +import { registerActionListRoutes } from './list'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; +import { doesLogsEndpointActionsIndexExist } from '../../utils'; +import { getActionList, getActionListByStatus } from '../../services'; + +jest.mock('../../utils'); +const mockDoesLogsEndpointActionsIndexExist = doesLogsEndpointActionsIndexExist as jest.Mock; + +jest.mock('../../services'); +const mockGetActionList = getActionList as jest.Mock; +const mockGetActionListByStatus = getActionListByStatus as jest.Mock; + +describe(' Action List Handler', () => { + let endpointAppContextService: EndpointAppContextService; + let mockResponse: jest.Mocked; + + let actionListHandler: ( + query?: EndpointActionListRequestQuery + ) => Promise>; + + beforeEach(() => { + const esClientMock = elasticsearchServiceMock.createScopedClusterClient(); + const routerMock = httpServiceMock.createRouter(); + endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); + endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); + mockDoesLogsEndpointActionsIndexExist.mockResolvedValue(true); + + registerActionListRoutes(routerMock, { + logFactory: loggingSystemMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); + + actionListHandler = async ( + query?: EndpointActionListRequestQuery + ): Promise> => { + const req = httpServerMock.createKibanaRequest({ + query, + }); + mockResponse = httpServerMock.createResponseFactory(); + const [, routeHandler]: [ + RouteConfig, + RequestHandler< + unknown, + EndpointActionListRequestQuery, + unknown, + SecuritySolutionRequestHandlerContext + > + ] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith(ENDPOINTS_ACTION_LIST_ROUTE) + )!; + await routeHandler( + coreMock.createCustomRequestHandlerContext( + createRouteHandlerContext(esClientMock, savedObjectsClientMock.create()) + ) as SecuritySolutionRequestHandlerContext, + req, + mockResponse + ); + + return mockResponse; + }; + }); + + afterEach(() => { + endpointAppContextService.stop(); + }); + + describe('Internals', () => { + it('should return `notFound` when actions index does not exist', async () => { + mockDoesLogsEndpointActionsIndexExist.mockResolvedValue(false); + await actionListHandler({ pageSize: 10, page: 1 }); + expect(mockResponse.notFound).toHaveBeenCalledWith({ + body: 'index_not_found_exception', + }); + }); + + it('should return `ok` when actions index exists', async () => { + await actionListHandler({ pageSize: 10, page: 1 }); + expect(mockResponse.ok).toHaveBeenCalled(); + }); + + it('should call `getActionListByStatus` when statuses filter values are provided', async () => { + await actionListHandler({ pageSize: 10, page: 1, statuses: ['failed', 'pending'] }); + expect(mockGetActionListByStatus).toBeCalledWith( + expect.objectContaining({ statuses: ['failed', 'pending'] }) + ); + }); + + it('should correctly format the request when calling `getActionListByStatus`', async () => { + await actionListHandler({ + agentIds: 'agentX', + commands: 'running-processes', + statuses: 'failed', + userIds: 'userX', + }); + expect(mockGetActionListByStatus).toBeCalledWith( + expect.objectContaining({ + elasticAgentIds: ['agentX'], + commands: ['running-processes'], + statuses: ['failed'], + userIds: ['userX'], + }) + ); + }); + + it('should call `getActionList` when statuses filter values are not provided', async () => { + await actionListHandler({ + pageSize: 10, + page: 1, + commands: ['isolate', 'kill-process'], + userIds: ['userX', 'userY'], + }); + expect(mockGetActionList).toBeCalledWith( + expect.objectContaining({ + commands: ['isolate', 'kill-process'], + userIds: ['userX', 'userY'], + }) + ); + }); + + it('should correctly format the request when calling `getActionList`', async () => { + await actionListHandler({ + page: 1, + pageSize: 10, + agentIds: 'agentX', + commands: 'isolate', + userIds: 'userX', + }); + + expect(mockGetActionList).toHaveBeenCalledWith( + expect.objectContaining({ + commands: ['isolate'], + elasticAgentIds: ['agentX'], + userIds: ['userX'], + }) + ); + }); + }); +}); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.fixtures.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.fixtures.ts index 281ba95d8e0cb..3a81ad1c71dcb 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.fixtures.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.fixtures.ts @@ -247,8 +247,8 @@ export function generateMetadataDocs(timestamp: number) { dataset: 'endpoint.metadata', }, host: { - hostname: 'rezzani-7.example.com', - name: 'rezzani-7.example.com', + hostname: 'Example-host-name-XYZ', + name: 'Example-host-name-XYZ', id: 'fc0ff548-feba-41b6-8367-65e8790d0eaf', ip: ['10.101.149.26', '2606:a000:ffc0:39:11ef:37b9:3371:578c'], mac: ['e2-6d-f9-0-46-2e'], @@ -399,8 +399,8 @@ export function generateMetadataDocs(timestamp: number) { }, host: { architecture: 'x86', - hostname: 'rezzani-7.example.com', - name: 'rezzani-7.example.com', + hostname: 'Example-host-name-XYZ', + name: 'Example-host-name-XYZ', id: 'fc0ff548-feba-41b6-8367-65e8790d0eaf', ip: ['10.101.149.26', '2606:a000:ffc0:39:11ef:37b9:3371:578c'], mac: ['e2-6d-f9-0-46-2e'], @@ -550,8 +550,8 @@ export function generateMetadataDocs(timestamp: number) { }, host: { architecture: 'x86', - hostname: 'rezzani-7.example.com', - name: 'rezzani-7.example.com', + hostname: 'Example-host-name-XYZ', + name: 'Example-host-name-XYZ', id: 'fc0ff548-feba-41b6-8367-65e8790d0eaf', ip: ['10.101.149.26', '2606:a000:ffc0:39:11ef:37b9:3371:578c'], mac: ['e2-6d-f9-0-46-2e'], diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 508aa8884fcfe..0ee6b44952577 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -259,6 +259,26 @@ export default function ({ getService }: FtrProviderContext) { expect(body.pageSize).to.eql(10); }); + it('metadata api should return the endpoint based on the agent hostname', async () => { + const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; + const targetAgentHostname = 'Example-host-name-XYZ'; + const { body } = await supertest + .get(HOST_METADATA_LIST_ROUTE) + .set('kbn-xsrf', 'xxx') + .query({ + kuery: `united.endpoint.host.hostname:${targetAgentHostname}`, + }) + .expect(200); + expect(body.total).to.eql(1); + const resultHostId: string = body.data[0].metadata.host.id; + const resultElasticAgentName: string = body.data[0].metadata.host.hostname; + expect(resultHostId).to.eql(targetEndpointId); + expect(resultElasticAgentName).to.eql(targetAgentHostname); + expect(body.data.length).to.eql(1); + expect(body.page).to.eql(0); + expect(body.pageSize).to.eql(10); + }); + it('metadata api should return all hosts when filter is empty string', async () => { const { body } = await supertest .get(HOST_METADATA_LIST_ROUTE) From 54ad60464d3642ddd6659253bc67c2e189d012d3 Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Tue, 27 Sep 2022 10:27:32 -0700 Subject: [PATCH 091/172] [automation] Update references to bundled packages (#141818) Co-authored-by: apmmachine --- fleet_packages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fleet_packages.json b/fleet_packages.json index c4a7f87127f10..4651e86287588 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -20,7 +20,7 @@ [ { "name": "apm", - "version": "8.4.0", + "version": "8.6.0-preview-1663775281", "forceAlignStackVersion": true }, { @@ -29,7 +29,7 @@ }, { "name": "endpoint", - "version": "8.4.1" + "version": "8.5.0" }, { "name": "fleet_server", @@ -39,4 +39,4 @@ "name": "synthetics", "version": "0.10.2" } -] +] \ No newline at end of file From 02f874bb01567a0c397fcc91b74c3ae47a240054 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 27 Sep 2022 13:36:34 -0400 Subject: [PATCH 092/172] Hide curl request in Search Index pipelines tab for connector and crawler indices (#141976) --- .../pipelines/ingest_pipelines_card.tsx | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx index ca6140b7b9625..7bf1ef06e1e75 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx @@ -26,6 +26,7 @@ import { KibanaLogic } from '../../../../shared/kibana'; import { LicensingLogic } from '../../../../shared/licensing'; import { CreateCustomPipelineApiLogic } from '../../../api/index/create_custom_pipeline_api_logic'; import { FetchCustomPipelineApiLogic } from '../../../api/index/fetch_custom_pipeline_api_logic'; +import { isApiIndex } from '../../../utils/indices'; import { CurlRequest } from '../components/curl_request/curl_request'; import { IndexViewLogic } from '../index_view_logic'; @@ -36,7 +37,7 @@ import { PipelinesLogic } from './pipelines_logic'; export const IngestPipelinesCard: React.FC = () => { const { indexName } = useValues(IndexViewLogic); - const { canSetPipeline, pipelineState, showModal } = useValues(PipelinesLogic); + const { canSetPipeline, index, pipelineState, showModal } = useValues(PipelinesLogic); const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic); const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); @@ -97,22 +98,24 @@ export const IngestPipelinesCard: React.FC = () => {
    - - - - - - + + {isApiIndex(index) && ( + + + + + + )} {i18n.translate( From fb55d8d6c151ac9f7070a7d1ccf6fdbe01020cbe Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 27 Sep 2022 19:51:37 +0200 Subject: [PATCH 093/172] [ML] Fix expanded row layout in the Nodes table (#141964) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../nodes_overview/allocated_models.tsx | 1 + .../nodes_overview/expanded_row.tsx | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx index 0da7778c6e2bc..d1808c2c82135 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx @@ -190,6 +190,7 @@ export const AllocatedModels: FC = ({ })} onTableChange={() => {}} data-test-subj={'mlNodesAllocatedModels'} + css={{ overflow: 'auto' }} /> ); }; diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx index 4c96904862786..a6b6adc4fbc20 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx @@ -17,6 +17,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { cloneDeep } from 'lodash'; import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; +import { css } from '@emotion/react'; import { NodeItem } from './nodes_list'; import { useListItemsFormatter } from '../models_management/expanded_row'; import { AllocatedModels } from './allocated_models'; @@ -44,10 +45,12 @@ export const ExpandedRow: FC = ({ item }) => { attributes['ml.max_jvm_size'] = bytesFormatter(attributes['ml.max_jvm_size']); return ( - <> - - - +
    + @@ -85,25 +88,26 @@ export const ExpandedRow: FC = ({ item }) => { /> + - {allocatedModels.length > 0 ? ( - - - -
    - -
    -
    - + {allocatedModels.length > 0 ? ( + <> + + + +
    + +
    +
    + - -
    -
    - ) : null} - - + + + + ) : null} +
    ); }; From 2aebaf720ea805fdd2b5545251f4944c56c873c8 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:57:54 -0400 Subject: [PATCH 094/172] Updating no matches text (#141981) --- .../user_profiles/no_matches.test.tsx | 2 +- .../components/user_profiles/no_matches.tsx | 21 ++++++++++++++++--- .../components/user_profiles/translations.ts | 15 +++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/cases/public/components/user_profiles/no_matches.test.tsx b/x-pack/plugins/cases/public/components/user_profiles/no_matches.test.tsx index 3471aad3fec3c..e2d23dfd988e1 100644 --- a/x-pack/plugins/cases/public/components/user_profiles/no_matches.test.tsx +++ b/x-pack/plugins/cases/public/components/user_profiles/no_matches.test.tsx @@ -13,6 +13,6 @@ describe('NoMatches', () => { it('renders the no matches messages', () => { render(); - expect(screen.getByText('No matching users with required access.')); + expect(screen.getByText("User doesn't exist or is unavailable")); }); }); diff --git a/x-pack/plugins/cases/public/components/user_profiles/no_matches.tsx b/x-pack/plugins/cases/public/components/user_profiles/no_matches.tsx index 638d705fade86..ccd27b123f235 100644 --- a/x-pack/plugins/cases/public/components/user_profiles/no_matches.tsx +++ b/x-pack/plugins/cases/public/components/user_profiles/no_matches.tsx @@ -5,7 +5,15 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTextAlign } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiTextAlign, +} from '@elastic/eui'; import React from 'react'; import * as i18n from './translations'; @@ -25,11 +33,18 @@ const NoMatchesComponent: React.FC = () => { - {i18n.NO_MATCHING_USERS} + {i18n.USER_DOES_NOT_EXIST}
    - {i18n.TRY_MODIFYING_SEARCH} + {i18n.MODIFY_SEARCH} +
    + + {i18n.LEARN_PRIVILEGES_GRANT_ACCESS} +
    diff --git a/x-pack/plugins/cases/public/components/user_profiles/translations.ts b/x-pack/plugins/cases/public/components/user_profiles/translations.ts index db76408759bec..2624ec834cd2e 100644 --- a/x-pack/plugins/cases/public/components/user_profiles/translations.ts +++ b/x-pack/plugins/cases/public/components/user_profiles/translations.ts @@ -44,12 +44,19 @@ export const ASSIGNEES = i18n.translate('xpack.cases.userProfile.assigneesTitle' defaultMessage: 'Assignees', }); -export const NO_MATCHING_USERS = i18n.translate('xpack.cases.userProfiles.noMatchingUsers', { - defaultMessage: 'No matching users with required access.', +export const USER_DOES_NOT_EXIST = i18n.translate('xpack.cases.userProfiles.userDoesNotExist', { + defaultMessage: "User doesn't exist or is unavailable", }); -export const TRY_MODIFYING_SEARCH = i18n.translate('xpack.cases.userProfiles.tryModifyingSearch', { - defaultMessage: 'Try modifying your search.', +export const LEARN_PRIVILEGES_GRANT_ACCESS = i18n.translate( + 'xpack.cases.userProfiles.learnPrivileges', + { + defaultMessage: 'Learn what privileges grant access to cases.', + } +); + +export const MODIFY_SEARCH = i18n.translate('xpack.cases.userProfiles.modifySearch', { + defaultMessage: "Modify your search or check the user's privileges.", }); export const INVALID_ASSIGNEES = i18n.translate('xpack.cases.create.invalidAssignees', { From aeb7a65b5fa30e056cf116952a083e132fc906ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:06:07 -0400 Subject: [PATCH 095/172] [APM] rename tech preview feedback copy (#141857) --- .../observability/server/ui_settings.ts | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 69e7f0ba51af6..a5b111569e311 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -29,11 +29,16 @@ import { const technicalPreviewLabel = i18n.translate( 'xpack.observability.uiSettings.technicalPreviewLabel', - { - defaultMessage: 'technical preview', - } + { defaultMessage: 'technical preview' } ); +function feedbackLink({ href }: { href: string }) { + return `${i18n.translate( + 'xpack.observability.uiSettings.giveFeedBackLabel', + { defaultMessage: 'Give feedback' } + )}`; +} + type UiSettings = UiSettingsParams & { showInLabs?: boolean }; /** @@ -167,12 +172,7 @@ export const uiSettings: Record = { '{technicalPreviewLabel} Enable the Service groups feature on APM UI. {feedbackLink}.', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, - feedbackLink: - '' + - i18n.translate('xpack.observability.enableServiceGroups.feedbackLinkText', { - defaultMessage: 'Give feedback', - }) + - '', + feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-service-groups' }), }, }), schema: schema.boolean(), @@ -206,15 +206,7 @@ export const uiSettings: Record = { '{technicalPreviewLabel} Default APM Service Inventory page sort (for Services without Machine Learning applied) to sort by Service Name. {feedbackLink}.', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, - feedbackLink: - '' + - i18n.translate( - 'xpack.observability.apmServiceInventoryOptimizedSorting.feedbackLinkText', - { - defaultMessage: 'Give feedback', - } - ) + - '', + feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-apm-page-performance' }), }, } ), @@ -245,12 +237,7 @@ export const uiSettings: Record = { '{technicalPreviewLabel} Enable the APM Trace Explorer feature, that allows you to search and inspect traces with KQL or EQL. {feedbackLink}.', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, - feedbackLink: - '' + - i18n.translate('xpack.observability.apmTraceExplorerTabDescription.feedbackLinkText', { - defaultMessage: 'Give feedback', - }) + - '', + feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-trace-explorer' }), }, }), schema: schema.boolean(), @@ -269,12 +256,7 @@ export const uiSettings: Record = { '{technicalPreviewLabel} Enable the APM Operations Breakdown feature, that displays aggregates for backend operations. {feedbackLink}.', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, - feedbackLink: - '' + - i18n.translate('xpack.observability.apmOperationsBreakdownDescription.feedbackLinkText', { - defaultMessage: 'Give feedback', - }) + - '', + feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-operations-breakdown' }), }, }), schema: schema.boolean(), @@ -318,12 +300,7 @@ export const uiSettings: Record = { '{technicalPreviewLabel} Display Amazon Lambda metrics in the service metrics tab. {feedbackLink}', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, - feedbackLink: - '' + - i18n.translate('xpack.observability.awsLambdaDescription', { - defaultMessage: 'Send feedback', - }) + - '', + feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-aws-lambda' }), }, }), schema: schema.boolean(), From bad534f71b7f8b975b392794195dcf36eee60759 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 27 Sep 2022 12:44:37 -0600 Subject: [PATCH 096/172] [RAM] Split bulk snooze options into 4 menu options (snooze/unsnooze/schedule/unschedule) (#141785) * Split bulk snooze options into 4 items * Update x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_modal.tsx Co-authored-by: Lisa Cawley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Lisa Cawley --- .../rule_quick_edit_buttons.test.tsx | 55 ++++++++++-- .../components/rule_quick_edit_buttons.tsx | 84 ++++++++++++++++++ .../components/bulk_snooze_modal.tsx | 86 +++++++++++++++---- .../components/bulk_snooze_schedule_modal.tsx | 49 ++++++----- .../rules_list/components/rules_list.test.tsx | 67 ++++++++++++++- .../rules_list/components/rules_list.tsx | 43 +++++++++- 6 files changed, 337 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx index 4e6ae5c7cdb91..1605f09ebc0f5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx @@ -20,9 +20,13 @@ jest.mock('../../../../common/lib/kibana', () => ({ const setRulesToUpdateAPIKey = jest.fn(); const setRulesToSnooze = jest.fn(); +const setRulesToUnsnooze = jest.fn(); const setRulesToSchedule = jest.fn(); +const setRulesToUnschedule = jest.fn(); const setRulesToSnoozeFilter = jest.fn(); +const setRulesToUnsnoozeFilter = jest.fn(); const setRulesToScheduleFilter = jest.fn(); +const setRulesToUnscheduleFilter = jest.fn(); const setRulesToUpdateAPIKeyFilter = jest.fn(); describe('rule_quick_edit_buttons', () => { @@ -46,9 +50,13 @@ describe('rule_quick_edit_buttons', () => { setRulesToDelete={() => {}} setRulesToUpdateAPIKey={() => {}} setRulesToSnooze={() => {}} + setRulesToUnsnooze={() => {}} setRulesToSchedule={() => {}} + setRulesToUnschedule={() => {}} setRulesToSnoozeFilter={() => {}} + setRulesToUnsnoozeFilter={() => {}} setRulesToScheduleFilter={() => {}} + setRulesToUnscheduleFilter={() => {}} setRulesToUpdateAPIKeyFilter={() => {}} /> ); @@ -58,7 +66,9 @@ describe('rule_quick_edit_buttons', () => { expect(wrapper.find('[data-test-subj="updateAPIKeys"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="deleteAll"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="bulkSnooze"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="bulkUnsnooze"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').exists()).toBeTruthy(); }); it('renders enableAll if rules are all disabled', async () => { @@ -77,9 +87,13 @@ describe('rule_quick_edit_buttons', () => { setRulesToDelete={() => {}} setRulesToUpdateAPIKey={() => {}} setRulesToSnooze={() => {}} + setRulesToUnsnooze={() => {}} setRulesToSchedule={() => {}} + setRulesToUnschedule={() => {}} setRulesToSnoozeFilter={() => {}} + setRulesToUnsnoozeFilter={() => {}} setRulesToScheduleFilter={() => {}} + setRulesToUnscheduleFilter={() => {}} setRulesToUpdateAPIKeyFilter={() => {}} /> ); @@ -104,20 +118,27 @@ describe('rule_quick_edit_buttons', () => { setRulesToDelete={() => {}} setRulesToUpdateAPIKey={() => {}} setRulesToSnooze={() => {}} + setRulesToUnsnooze={() => {}} setRulesToSchedule={() => {}} + setRulesToUnschedule={() => {}} setRulesToSnoozeFilter={() => {}} + setRulesToUnsnoozeFilter={() => {}} setRulesToScheduleFilter={() => {}} + setRulesToUnscheduleFilter={() => {}} setRulesToUpdateAPIKeyFilter={() => {}} /> ); expect(wrapper.find('[data-test-subj="disableAll"]').first().prop('isDisabled')).toBeTruthy(); expect(wrapper.find('[data-test-subj="deleteAll"]').first().prop('isDisabled')).toBeTruthy(); - - expect(wrapper.find('[data-test-subj="updateAPIKeys"]').first().prop('isDiabled')).toBeFalsy(); - expect(wrapper.find('[data-test-subj="bulkSnooze"]').first().prop('isDiabled')).toBeFalsy(); + expect(wrapper.find('[data-test-subj="updateAPIKeys"]').first().prop('isDisabled')).toBeFalsy(); + expect(wrapper.find('[data-test-subj="bulkSnooze"]').first().prop('isDisabled')).toBeFalsy(); + expect(wrapper.find('[data-test-subj="bulkUnsnooze"]').first().prop('isDisabled')).toBeFalsy(); + expect( + wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').first().prop('isDisabled') + ).toBeFalsy(); expect( - wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').first().prop('isDiabled') + wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').first().prop('isDisabled') ).toBeFalsy(); }); @@ -137,10 +158,14 @@ describe('rule_quick_edit_buttons', () => { onActionPerformed={() => {}} setRulesToDelete={() => {}} setRulesToSnooze={setRulesToSnooze} + setRulesToUnsnooze={setRulesToUnsnooze} setRulesToSchedule={setRulesToSchedule} + setRulesToUnschedule={setRulesToUnschedule} setRulesToUpdateAPIKey={setRulesToUpdateAPIKey} setRulesToSnoozeFilter={setRulesToSnoozeFilter} + setRulesToUnsnoozeFilter={setRulesToUnsnoozeFilter} setRulesToScheduleFilter={setRulesToScheduleFilter} + setRulesToUnscheduleFilter={setRulesToUnscheduleFilter} setRulesToUpdateAPIKeyFilter={setRulesToUpdateAPIKeyFilter} /> ); @@ -148,14 +173,22 @@ describe('rule_quick_edit_buttons', () => { wrapper.find('[data-test-subj="bulkSnooze"]').first().simulate('click'); expect(setRulesToSnooze).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkUnsnooze"]').first().simulate('click'); + expect(setRulesToUnsnooze).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').first().simulate('click'); expect(setRulesToSchedule).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').first().simulate('click'); + expect(setRulesToUnschedule).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="updateAPIKeys"]').first().simulate('click'); expect(setRulesToUpdateAPIKey).toHaveBeenCalledTimes(1); expect(setRulesToSnoozeFilter).not.toHaveBeenCalled(); + expect(setRulesToUnsnoozeFilter).not.toHaveBeenCalled(); expect(setRulesToScheduleFilter).not.toHaveBeenCalled(); + expect(setRulesToUnscheduleFilter).not.toHaveBeenCalled(); expect(setRulesToUpdateAPIKeyFilter).not.toHaveBeenCalled(); }); @@ -175,10 +208,14 @@ describe('rule_quick_edit_buttons', () => { onActionPerformed={() => {}} setRulesToDelete={() => {}} setRulesToSnooze={setRulesToSnooze} + setRulesToUnsnooze={setRulesToUnsnooze} setRulesToSchedule={setRulesToSchedule} + setRulesToUnschedule={setRulesToUnschedule} setRulesToUpdateAPIKey={setRulesToUpdateAPIKey} setRulesToSnoozeFilter={setRulesToSnoozeFilter} + setRulesToUnsnoozeFilter={setRulesToUnsnoozeFilter} setRulesToScheduleFilter={setRulesToScheduleFilter} + setRulesToUnscheduleFilter={setRulesToUnscheduleFilter} setRulesToUpdateAPIKeyFilter={setRulesToUpdateAPIKeyFilter} /> ); @@ -186,14 +223,22 @@ describe('rule_quick_edit_buttons', () => { wrapper.find('[data-test-subj="bulkSnooze"]').first().simulate('click'); expect(setRulesToSnoozeFilter).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkUnsnooze"]').first().simulate('click'); + expect(setRulesToUnsnoozeFilter).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').first().simulate('click'); expect(setRulesToScheduleFilter).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').first().simulate('click'); + expect(setRulesToUnscheduleFilter).toHaveBeenCalledTimes(1); + wrapper.find('[data-test-subj="updateAPIKeys"]').first().simulate('click'); expect(setRulesToUpdateAPIKeyFilter).toHaveBeenCalledTimes(1); - expect(setRulesToSchedule).not.toHaveBeenCalled(); expect(setRulesToSnooze).not.toHaveBeenCalled(); + expect(setRulesToUnsnooze).not.toHaveBeenCalled(); + expect(setRulesToSchedule).not.toHaveBeenCalled(); + expect(setRulesToUnschedule).not.toHaveBeenCalled(); expect(setRulesToUpdateAPIKey).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx index 0b7db1ebeceba..c91c461993a54 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx @@ -25,14 +25,20 @@ export type ComponentOpts = { onPerformingAction?: () => void; onActionPerformed?: () => void; isSnoozingRules?: boolean; + isUnsnoozingRules?: boolean; isSchedulingRules?: boolean; + isUnschedulingRules?: boolean; isUpdatingRuleAPIKeys?: boolean; setRulesToDelete: React.Dispatch>; setRulesToUpdateAPIKey: React.Dispatch>; setRulesToSnooze: React.Dispatch>; + setRulesToUnsnooze: React.Dispatch>; setRulesToSchedule: React.Dispatch>; + setRulesToUnschedule: React.Dispatch>; setRulesToSnoozeFilter: React.Dispatch>; + setRulesToUnsnoozeFilter: React.Dispatch>; setRulesToScheduleFilter: React.Dispatch>; + setRulesToUnscheduleFilter: React.Dispatch>; setRulesToUpdateAPIKeyFilter: React.Dispatch>; } & BulkOperationsComponentOpts; @@ -65,16 +71,22 @@ export const RuleQuickEditButtons: React.FunctionComponent = ({ onPerformingAction = noop, onActionPerformed = noop, isSnoozingRules = false, + isUnsnoozingRules = false, isSchedulingRules = false, + isUnschedulingRules = false, isUpdatingRuleAPIKeys = false, enableRules, disableRules, setRulesToDelete, setRulesToUpdateAPIKey, setRulesToSnooze, + setRulesToUnsnooze, setRulesToSchedule, + setRulesToUnschedule, setRulesToSnoozeFilter, + setRulesToUnsnoozeFilter, setRulesToScheduleFilter, + setRulesToUnscheduleFilter, setRulesToUpdateAPIKeyFilter, }: ComponentOpts) => { const { @@ -90,7 +102,9 @@ export const RuleQuickEditButtons: React.FunctionComponent = ({ isDisablingRules || isDeletingRules || isSnoozingRules || + isUnsnoozingRules || isSchedulingRules || + isUnschedulingRules || isUpdatingRuleAPIKeys; const allRulesDisabled = useMemo(() => { @@ -220,6 +234,28 @@ export const RuleQuickEditButtons: React.FunctionComponent = ({ } } + async function onUnsnoozeAllClick() { + onPerformingAction(); + try { + if (isAllSelected) { + setRulesToUnsnoozeFilter(getFilter()); + } else { + setRulesToUnsnooze(selectedItems); + } + } catch (e) { + toasts.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.bulkActionPopover.failedToSnoozeRules', + { + defaultMessage: 'Failed to snooze or unsnooze rules', + } + ), + }); + } finally { + onActionPerformed(); + } + } + async function onScheduleAllClick() { onPerformingAction(); try { @@ -242,6 +278,28 @@ export const RuleQuickEditButtons: React.FunctionComponent = ({ } } + async function onUnscheduleAllClick() { + onPerformingAction(); + try { + if (isAllSelected) { + setRulesToUnscheduleFilter(getFilter()); + } else { + setRulesToUnschedule(selectedItems); + } + } catch (e) { + toasts.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.bulkActionPopover.failedToSnoozeRules', + { + defaultMessage: 'Failed to snooze or unsnooze rules', + } + ), + }); + } finally { + onActionPerformed(); + } + } + return ( = ({ />
    + + + + + = ({ /> + + + + + void; onSave: () => void; - setIsLoading: (isLoading: boolean) => void; + setIsSnoozingRule: (isLoading: boolean) => void; + setIsUnsnoozingRule: (isLoading: boolean) => void; onSearchPopulate?: (filter: string) => void; } & BulkOperationsComponentOpts; @@ -43,13 +47,29 @@ const failureMessage = i18n.translate( } ); +const deleteConfirmPlural = (total: number) => + i18n.translate('xpack.triggersActionsUI.sections.rulesList.bulkUnsnoozeConfirmationPlural', { + defaultMessage: 'Unsnooze {total, plural, one {# rule} other {# rules}}? ', + values: { total }, + }); + +const deleteConfirmSingle = (ruleName: string) => + i18n.translate('xpack.triggersActionsUI.sections.rulesList.bulkUnsnoozeConfirmationSingle', { + defaultMessage: 'Unsnooze {ruleName}?', + values: { ruleName }, + }); + export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { const { rulesToSnooze, + rulesToUnsnooze, rulesToSnoozeFilter, + rulesToUnsnoozeFilter, + numberOfSelectedRules = 0, onClose, onSave, - setIsLoading, + setIsSnoozingRule, + setIsUnsnoozingRule, onSearchPopulate, bulkSnoozeRules, bulkUnsnoozeRules, @@ -68,12 +88,12 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { return rulesToSnooze.length > 0; }, [rulesToSnooze, rulesToSnoozeFilter]); - const isSnoozed = useMemo(() => { - if (rulesToSnoozeFilter) { + const isUnsnoozeModalOpen = useMemo(() => { + if (rulesToUnsnoozeFilter) { return true; } - return rulesToSnooze.some((item) => isRuleSnoozed(item)); - }, [rulesToSnooze, rulesToSnoozeFilter]); + return rulesToUnsnooze.length > 0; + }, [rulesToUnsnooze, rulesToUnsnoozeFilter]); const interval = useMemo(() => { if (rulesToSnoozeFilter) { @@ -87,7 +107,7 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { const onSnoozeRule = async (schedule: SnoozeSchedule) => { onClose(); - setIsLoading(true); + setIsSnoozingRule(true); try { const response = await bulkSnoozeRules({ ids: rulesToSnooze.map((item) => item.id), @@ -100,18 +120,17 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { title: failureMessage, }); } - setIsLoading(false); + setIsSnoozingRule(false); onSave(); }; - const onUnsnoozeRule = async (scheduleIds?: string[]) => { + const onUnsnoozeRule = async () => { onClose(); - setIsLoading(true); + setIsUnsnoozingRule(true); try { const response = await bulkUnsnoozeRules({ - ids: rulesToSnooze.map((item) => item.id), - filter: rulesToSnoozeFilter, - scheduleIds, + ids: rulesToUnsnooze.map((item) => item.id), + filter: rulesToUnsnoozeFilter, }); showToast(response, 'snooze'); } catch (error) { @@ -119,10 +138,42 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { title: failureMessage, }); } - setIsLoading(false); + setIsUnsnoozingRule(false); onSave(); }; + const confirmationTitle = useMemo(() => { + if (!rulesToUnsnoozeFilter && numberOfSelectedRules === 1 && rulesToUnsnooze[0]) { + return deleteConfirmSingle(rulesToUnsnooze[0].name); + } + return deleteConfirmPlural(numberOfSelectedRules); + }, [rulesToUnsnooze, rulesToUnsnoozeFilter, numberOfSelectedRules]); + + if (isUnsnoozeModalOpen) { + return ( + + ); + } + if (isSnoozeModalOpen) { return ( @@ -138,11 +189,11 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { @@ -153,6 +204,7 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { ); } + return null; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx index 56293c01614de..cbf009acaa9ae 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx @@ -5,18 +5,18 @@ * 2.0. */ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { + EuiConfirmModal, EuiModal, EuiModalHeader, + EuiModalHeaderTitle, EuiModalBody, EuiModalFooter, EuiSpacer, EuiButtonEmpty, - EuiModalHeaderTitle, - EuiConfirmModal, } from '@elastic/eui'; import { withBulkRuleOperations, @@ -49,24 +49,30 @@ const deleteConfirmSingle = (ruleName: string) => export type BulkSnoozeScheduleModalProps = { rulesToSchedule: RuleTableItem[]; + rulesToUnschedule: RuleTableItem[]; rulesToScheduleFilter?: string; + rulesToUnscheduleFilter?: string; numberOfSelectedRules?: number; onClose: () => void; onSave: () => void; - setIsLoading: (isLoading: boolean) => void; + setIsSchedulingRule: (isLoading: boolean) => void; + setIsUnschedulingRule: (isLoading: boolean) => void; onSearchPopulate?: (filter: string) => void; } & BulkOperationsComponentOpts; export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => { const { rulesToSchedule, + rulesToUnschedule, rulesToScheduleFilter, + rulesToUnscheduleFilter, numberOfSelectedRules = 0, onClose, onSave, bulkSnoozeRules, bulkUnsnoozeRules, - setIsLoading, + setIsSchedulingRule, + setIsUnschedulingRule, onSearchPopulate, } = props; @@ -76,8 +82,6 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => const { showToast } = useBulkEditResponse({ onSearchPopulate }); - const [showConfirmation, setShowConfirmation] = useState(false); - const isScheduleModalOpen = useMemo(() => { if (rulesToScheduleFilter) { return true; @@ -85,9 +89,16 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => return rulesToSchedule.length > 0; }, [rulesToSchedule, rulesToScheduleFilter]); + const isUnscheduleModalOpen = useMemo(() => { + if (rulesToUnscheduleFilter) { + return true; + } + return rulesToUnschedule.length > 0; + }, [rulesToUnschedule, rulesToUnscheduleFilter]); + const onAddSnoozeSchedule = async (schedule: SnoozeSchedule) => { onClose(); - setIsLoading(true); + setIsSchedulingRule(true); try { const response = await bulkSnoozeRules({ ids: rulesToSchedule.map((item) => item.id), @@ -100,18 +111,17 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => title: failureMessage, }); } - setIsLoading(false); + setIsSchedulingRule(false); onSave(); }; const onRemoveSnoozeSchedule = async () => { - setShowConfirmation(false); onClose(); - setIsLoading(true); + setIsUnschedulingRule(true); try { const response = await bulkUnsnoozeRules({ - ids: rulesToSchedule.map((item) => item.id), - filter: rulesToScheduleFilter, + ids: rulesToUnschedule.map((item) => item.id), + filter: rulesToUnscheduleFilter, scheduleIds: [], }); showToast(response, 'snoozeSchedule'); @@ -120,7 +130,7 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => title: failureMessage, }); } - setIsLoading(false); + setIsUnschedulingRule(false); onSave(); }; @@ -131,14 +141,11 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => return deleteConfirmPlural(numberOfSelectedRules); }, [rulesToSchedule, rulesToScheduleFilter, numberOfSelectedRules]); - if (showConfirmation) { + if (isUnscheduleModalOpen) { return ( { - setShowConfirmation(false); - onClose(); - }} + onCancel={onClose} onConfirm={onRemoveSnoozeSchedule} confirmButtonText={i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.bulkDeleteConfirmButton', @@ -154,6 +161,7 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => )} buttonColor="danger" defaultFocusedButton="confirm" + data-test-subj="bulkRemoveScheduleConfirmationModal" /> ); } @@ -172,13 +180,12 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => setShowConfirmation(true)} + onCancelSchedules={onRemoveSnoozeSchedule} onClose={() => {}} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 3bde062935a86..6bfe953e28696 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -80,8 +80,14 @@ jest.mock('../../../../common/get_experimental_features', () => ({ const ruleTags = ['a', 'b', 'c', 'd']; -const { loadRuleTypes, updateAPIKey, loadRuleTags, bulkSnoozeRules, bulkUpdateAPIKey } = - jest.requireMock('../../../lib/rule_api'); +const { + loadRuleTypes, + updateAPIKey, + loadRuleTags, + bulkSnoozeRules, + bulkUnsnoozeRules, + bulkUpdateAPIKey, +} = jest.requireMock('../../../lib/rule_api'); const { loadRuleAggregationsWithKueryFilter } = jest.requireMock( '../../../lib/rule_api/aggregate_kuery_filter' ); @@ -1994,6 +2000,33 @@ describe.skip('Rules list bulk actions', () => { ); }); + it('can bulk unsnooze', async () => { + await setup(); + wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change'); + wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click'); + wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click'); + + // Unselect something to test filtering + wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change'); + + wrapper.find('[data-test-subj="bulkUnsnooze"]').first().simulate('click'); + + expect(wrapper.find('[data-test-subj="bulkUnsnoozeConfirmationModal"]').exists()).toBeTruthy(); + wrapper.find('[data-test-subj="confirmModalConfirmButton"]').first().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(bulkUnsnoozeRules).toHaveBeenCalledWith( + expect.objectContaining({ + ids: [], + filter: 'NOT (alert.id: "alert:2")', + }) + ); + }); + it('can bulk add snooze schedule', async () => { await setup(); wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change'); @@ -2020,6 +2053,36 @@ describe.skip('Rules list bulk actions', () => { ); }); + it('can bulk remove snooze schedule', async () => { + await setup(); + wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change'); + wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click'); + wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click'); + + // Unselect something to test filtering + wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change'); + + wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').first().simulate('click'); + + expect( + wrapper.find('[data-test-subj="bulkRemoveScheduleConfirmationModal"]').exists() + ).toBeTruthy(); + wrapper.find('[data-test-subj="confirmModalConfirmButton"]').first().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(bulkUnsnoozeRules).toHaveBeenCalledWith( + expect.objectContaining({ + ids: [], + filter: 'NOT (alert.id: "alert:2")', + scheduleIds: [], + }) + ); + }); + it('can bulk update API key', async () => { await setup(); wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index a725a0e916c19..0061e56d48498 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -209,14 +209,22 @@ export const RulesList = ({ const [rulesToSnooze, setRulesToSnooze] = useState([]); const [rulesToSnoozeFilter, setRulesToSnoozeFilter] = useState(''); + const [rulesToUnsnooze, setRulesToUnsnooze] = useState([]); + const [rulesToUnsnoozeFilter, setRulesToUnsnoozeFilter] = useState(''); + const [rulesToSchedule, setRulesToSchedule] = useState([]); const [rulesToScheduleFilter, setRulesToScheduleFilter] = useState(''); + const [rulesToUnschedule, setRulesToUnschedule] = useState([]); + const [rulesToUnscheduleFilter, setRulesToUnscheduleFilter] = useState(''); + const [rulesToUpdateAPIKey, setRulesToUpdateAPIKey] = useState([]); const [rulesToUpdateAPIKeyFilter, setRulesToUpdateAPIKeyFilter] = useState(''); const [isSnoozingRules, setIsSnoozingRules] = useState(false); const [isSchedulingRules, setIsSchedulingRules] = useState(false); + const [isUnsnoozingRules, setIsUnsnoozingRules] = useState(false); + const [isUnschedulingRules, setIsUnschedulingRules] = useState(false); const [isUpdatingRuleAPIKeys, setIsUpdatingRuleAPIKeys] = useState(false); const hasAnyAuthorizedRuleType = useMemo(() => { @@ -597,11 +605,21 @@ export const RulesList = ({ setRulesToSnoozeFilter(''); }; + const clearRulesToUnsnooze = () => { + setRulesToUnsnooze([]); + setRulesToUnsnoozeFilter(''); + }; + const clearRulesToSchedule = () => { setRulesToSchedule([]); setRulesToScheduleFilter(''); }; + const clearRulesToUnschedule = () => { + setRulesToUnschedule([]); + setRulesToUnscheduleFilter(''); + }; + const clearRulesToUpdateAPIKey = () => { setRulesToUpdateAPIKey([]); setRulesToUpdateAPIKeyFilter(''); @@ -613,7 +631,9 @@ export const RulesList = ({ ruleTypesState.isLoading || isPerformingAction || isSnoozingRules || + isUnsnoozingRules || isSchedulingRules || + isUnschedulingRules || isUpdatingRuleAPIKeys ); }, [ @@ -621,7 +641,9 @@ export const RulesList = ({ ruleTypesState, isPerformingAction, isSnoozingRules, + isUnsnoozingRules, isSchedulingRules, + isUnschedulingRules, isUpdatingRuleAPIKeys, ]); @@ -903,14 +925,20 @@ export const RulesList = ({ setIsPerformingAction(false); }} isSnoozingRules={isSnoozingRules} + isUnsnoozingRules={isUnsnoozingRules} isSchedulingRules={isSchedulingRules} + isUnschedulingRules={isUnschedulingRules} isUpdatingRuleAPIKeys={isUpdatingRuleAPIKeys} setRulesToDelete={setRulesToDelete} setRulesToUpdateAPIKey={setRulesToUpdateAPIKey} setRulesToSnooze={setRulesToSnooze} + setRulesToUnsnooze={setRulesToUnsnooze} setRulesToSchedule={setRulesToSchedule} + setRulesToUnschedule={setRulesToUnschedule} setRulesToSnoozeFilter={setRulesToSnoozeFilter} + setRulesToUnsnoozeFilter={setRulesToUnsnoozeFilter} setRulesToScheduleFilter={setRulesToScheduleFilter} + setRulesToUnscheduleFilter={setRulesToUnscheduleFilter} setRulesToUpdateAPIKeyFilter={setRulesToUpdateAPIKeyFilter} /> @@ -983,13 +1011,19 @@ export const RulesList = ({ /> { clearRulesToSnooze(); + clearRulesToUnsnooze(); }} onSave={async () => { clearRulesToSnooze(); + clearRulesToUnsnooze(); onClearSelection(); await loadData(); }} @@ -997,14 +1031,19 @@ export const RulesList = ({ /> { clearRulesToSchedule(); + clearRulesToUnschedule(); }} onSave={async () => { clearRulesToSchedule(); + clearRulesToUnschedule(); onClearSelection(); await loadData(); }} From 7af3f3945c7a1f35048f338bb1110dbe28b6fff8 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 27 Sep 2022 14:51:55 -0400 Subject: [PATCH 097/172] Allow empty nextSyncConfig field in register connector request (#141986) --- .../server/routes/enterprise_search/connectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index c0dcc2dbdb0a9..0aaf30ef126d4 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -114,7 +114,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { connectorId: schema.string(), }), body: schema.object({ - nextSyncConfig: schema.string(), + nextSyncConfig: schema.maybe(schema.string()), }), }, }, From 504d8c4c700c8d8d1b2c7796b3915c2bc41b2c40 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:54:15 -0400 Subject: [PATCH 098/172] [Security Solution][Endpoint] Generic hook to handle Response Actions API interactions (#141224) - New general hook (`useConsoleActionSubmitter()`) for handling response console commands that need to generate an endpoint response action - Refactored all Response Actions to use new hook - Fixed bug in `useIsMounted()` hook --- .../common/endpoint/schema/actions.ts | 2 + .../common/endpoint/types/actions.ts | 1 + .../artifact_list_page.test.tsx | 2 +- .../artifact_list_page/artifact_list_page.tsx | 4 +- .../components/artifact_delete_modal.test.ts | 2 +- .../components/artifact_flyout.test.tsx | 2 +- .../components/artifact_flyout.tsx | 12 +- .../hooks/use_with_artifact_list_data.ts | 8 +- .../management/components/console/types.ts | 2 +- .../get_processes_action.test.tsx | 4 +- .../get_processes_action.tsx | 257 +++++---------- .../components/endpoint_responder/hooks.tsx | 100 ------ .../use_console_action_submitter.test.tsx | 275 +++++++++++++++++ .../hooks/use_console_action_submitter.tsx | 292 ++++++++++++++++++ .../isolate_action.test.tsx | 6 +- .../endpoint_responder/isolate_action.tsx | 54 ++-- .../kill_process_action.test.tsx | 8 +- .../kill_process_action.tsx | 144 ++------- .../release_action.test.tsx | 6 +- .../endpoint_responder/release_action.tsx | 55 ++-- .../suspend_process_action.test.tsx | 8 +- .../suspend_process_action.tsx | 144 ++------- .../components/endpoint_responder/types.ts | 22 +- .../endpoint_responder/utils.test.ts | 4 +- .../components/endpoint_responder/utils.ts | 10 +- .../components/page_overlay/page_overlay.tsx | 8 +- .../public/management/hooks/use_is_mounted.ts | 26 -- .../{components/mocks.tsx => mocks/utils.ts} | 3 +- .../policy_artifacts_delete_modal.test.tsx | 2 +- .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 32 files changed, 806 insertions(+), 672 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks.tsx create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/hooks/use_is_mounted.ts rename x-pack/plugins/security_solution/public/management/{components/mocks.tsx => mocks/utils.ts} (93%) diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index 08adac7c9ede0..8b982e6e6f463 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -28,6 +28,8 @@ export const NoParametersRequestSchema = { body: schema.object({ ...BaseActionRequestSchema }), }; +export type BaseActionRequestBody = TypeOf; + export const KillOrSuspendProcessRequestSchema = { body: schema.object({ ...BaseActionRequestSchema, diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index bcbdcfb3b66b8..91eb10c5f45a2 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -253,6 +253,7 @@ export interface PendingActionsResponse { } export type PendingActionsRequestQuery = TypeOf; + export interface ActionDetails { /** The action id */ id: string; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx index 9160732e32b3e..6780335312251 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx @@ -12,7 +12,7 @@ import { act, fireEvent, waitFor, waitForElementToBeRemoved } from '@testing-lib import userEvent from '@testing-library/user-event'; import type { ArtifactListPageRenderingSetup } from './mocks'; import { getArtifactListPageRenderingSetup } from './mocks'; -import { getDeferred } from '../mocks'; +import { getDeferred } from '../../mocks/utils'; jest.mock('../../../common/components/user_privileges'); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx index fc687282cccac..0586034d15550 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx @@ -11,6 +11,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import type { EuiFlyoutSize } from '@elastic/eui/src/components/flyout/flyout'; import { useLocation } from 'react-router-dom'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import type { ServerApiError } from '../../../common/types'; import { AdministrationListPage } from '../administration_list_page'; @@ -45,7 +46,6 @@ import { useToasts } from '../../../common/lib/kibana'; import { useMemoizedRouteState } from '../../common/hooks'; import { BackToExternalAppSecondaryButton } from '../back_to_external_app_secondary_button'; import { BackToExternalAppButton } from '../back_to_external_app_button'; -import { useIsMounted } from '../../hooks/use_is_mounted'; type ArtifactEntryCardType = typeof ArtifactEntryCard; @@ -221,7 +221,7 @@ export const ArtifactListPage = memo( ); const handleArtifactDeleteModalOnSuccess = useCallback(() => { - if (isMounted) { + if (isMounted()) { setSelectedItemForDelete(undefined); refetchListData(); } diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts index d0fb3e3c59dfa..57ea165f0b85f 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts @@ -11,7 +11,7 @@ import type { ArtifactListPageRenderingSetup } from '../mocks'; import { getArtifactListPageRenderingSetup } from '../mocks'; import { act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { getDeferred } from '../../mocks'; +import { getDeferred } from '../../../mocks/utils'; describe('When displaying the Delete artifact modal in the Artifact List Page', () => { let renderResult: ReturnType; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx index ee3ac4a907ee4..5179bb7b76be6 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx @@ -19,7 +19,7 @@ import type { trustedAppsAllHttpMocks } from '../../../mocks'; import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; import { entriesToConditionEntries } from '../../../../common/utils/exception_list_items/mappers'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { getDeferred } from '../../mocks'; +import { getDeferred } from '../../../mocks/utils'; jest.mock('../../../../common/components/user_privileges'); const useUserPrivileges = _useUserPrivileges as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.tsx index 4d9f53c73341e..1261d01c7af44 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.tsx @@ -24,6 +24,7 @@ import { import type { EuiFlyoutSize } from '@elastic/eui/src/components/flyout/flyout'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import { useUrlParams } from '../../../hooks/use_url_params'; import { useIsFlyoutOpened } from '../hooks/use_is_flyout_opened'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; @@ -39,7 +40,6 @@ import { useKibana, useToasts } from '../../../../common/lib/kibana'; import { createExceptionListItemForCreate } from '../../../../../common/endpoint/service/artifacts/utils'; import { useWithArtifactSubmitData } from '../hooks/use_with_artifact_submit_data'; import { useIsArtifactAllowedPerPolicyUsage } from '../hooks/use_is_artifact_allowed_per_policy_usage'; -import { useIsMounted } from '../../../hooks/use_is_mounted'; import { useGetArtifact } from '../../../hooks/artifacts'; import type { PolicyData } from '../../../../../common/endpoint/types'; @@ -271,7 +271,7 @@ export const ArtifactFlyout = memo( const handleFormComponentOnChange: ArtifactFormComponentProps['onChange'] = useCallback( ({ item: updatedItem, isValid }) => { - if (isMounted) { + if (isMounted()) { setFormState({ item: updatedItem, isValid, @@ -289,7 +289,7 @@ export const ArtifactFlyout = memo( : labels.flyoutCreateSubmitSuccess(result) ); - if (isMounted) { + if (isMounted()) { // Close the flyout // `undefined` will cause params to be dropped from url setUrlParams({ ...urlParams, itemId: undefined, show: undefined }, true); @@ -307,12 +307,12 @@ export const ArtifactFlyout = memo( submitHandler(formState.item, formMode) .then(handleSuccess) .catch((submitHandlerError) => { - if (isMounted) { + if (isMounted()) { setExternalSubmitHandlerError(submitHandlerError); } }) .finally(() => { - if (isMounted) { + if (isMounted()) { setExternalIsSubmittingData(false); } }); @@ -326,7 +326,7 @@ export const ArtifactFlyout = memo( useEffect(() => { if (isEditFlow && !hasItemDataForEdit && !error && isInitializing && !isLoadingItemForEdit) { fetchItemForEdit().then(({ data: editItemData }) => { - if (editItemData && isMounted) { + if (editItemData && isMounted()) { setFormState(createFormInitialState(apiClient.listId, editItemData)); } }); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/hooks/use_with_artifact_list_data.ts b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/hooks/use_with_artifact_list_data.ts index 813e205b64c9a..ddae258fef895 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/hooks/use_with_artifact_list_data.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/hooks/use_with_artifact_list_data.ts @@ -8,8 +8,8 @@ import { useEffect, useMemo, useState } from 'react'; import type { Pagination } from '@elastic/eui'; import { useQuery } from '@tanstack/react-query'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import type { ServerApiError } from '../../../../common/types'; -import { useIsMounted } from '../../../hooks/use_is_mounted'; import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; import { useUrlParams } from '../../../hooks/use_url_params'; import type { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client'; @@ -98,7 +98,7 @@ export const useWithArtifactListData = ( // Once we know if data exists, update the page initializing state. // This should only ever happen at most once; useEffect(() => { - if (isMounted) { + if (isMounted()) { if (isPageInitializing && !isLoadingDataExists) { setIsPageInitializing(false); } @@ -107,7 +107,7 @@ export const useWithArtifactListData = ( // Update the uiPagination once the query succeeds useEffect(() => { - if (isMounted && listData && !isLoadingListData && isSuccessListData) { + if (isMounted() && listData && !isLoadingListData && isSuccessListData) { setUiPagination((prevState) => { return { ...prevState, @@ -134,7 +134,7 @@ export const useWithArtifactListData = ( // >> Check if data exists again (which should return true useEffect(() => { if ( - isMounted && + isMounted() && !isLoadingListData && !isLoadingDataExists && !listDataError && diff --git a/x-pack/plugins/security_solution/public/management/components/console/types.ts b/x-pack/plugins/security_solution/public/management/components/console/types.ts index 2d863e7878be2..5b90a18f27ce1 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/types.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/types.ts @@ -168,7 +168,7 @@ export type CommandExecutionComponent< /** The arguments that could have been entered by the user */ TArgs extends SupportedArguments = any, /** Internal store for the Command execution */ - TStore extends object = Record, + TStore extends object = any, /** The metadata defined on the Command Definition */ TMeta = any > = ComponentType>; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.test.tsx index 29b6fd0446577..97689a790afa1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.test.tsx @@ -129,7 +129,7 @@ describe('When using processes action from response actions console', () => { enterConsoleCommand(renderResult, 'processes'); await waitFor(() => { - expect(renderResult.getByTestId('getProcessesErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('getProcesses-actionFailure').textContent).toMatch( /error one \| error two/ ); }); @@ -145,7 +145,7 @@ describe('When using processes action from response actions console', () => { enterConsoleCommand(renderResult, 'processes'); await waitFor(() => { - expect(renderResult.getByTestId('performGetProcessesErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('getProcesses-apiFailure').textContent).toMatch( /this is an error/ ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx index c778f03fcb5f5..d7b05ae721abd 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx @@ -5,21 +5,17 @@ * 2.0. */ -import React, { memo, useEffect, useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; import { EuiBasicTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; import type { - ActionDetails, GetProcessesActionOutputContent, + ProcessesRequestBody, } from '../../../../common/endpoint/types'; -import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; -import type { EndpointCommandDefinitionMeta } from './types'; -import type { CommandExecutionComponentProps } from '../console/types'; import { useSendGetEndpointProcessesRequest } from '../../hooks/endpoint/use_send_get_endpoint_processes_request'; -import { ActionError } from './action_error'; +import type { ActionRequestComponentProps } from './types'; // @ts-expect-error TS2769 const StyledEuiBasicTable = styled(EuiBasicTable)` @@ -43,181 +39,90 @@ const StyledEuiBasicTable = styled(EuiBasicTable)` } `; -export const GetProcessesActionResult = memo< - CommandExecutionComponentProps< - { comment?: string }, - { - actionId?: string; - actionRequestSent?: boolean; - completedActionDetails?: ActionDetails; - apiError?: IHttpFetchError; - }, - EndpointCommandDefinitionMeta - > ->(({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const { actionId, completedActionDetails, apiError } = store; - - const isPending = status === 'pending'; - const isError = status === 'error'; - const actionRequestSent = Boolean(store.actionRequestSent); - - const { - mutate: getProcesses, - data: getProcessesData, - isSuccess: isGetProcessesSuccess, - error: processesActionRequestError, - } = useSendGetEndpointProcessesRequest(); - - const { data: actionDetails } = useGetActionDetails( - actionId ?? '-', - { - enabled: Boolean(actionId) && isPending, - refetchInterval: isPending ? 3000 : false, - } - ); - - // Send get processes request if not yet done - useEffect(() => { - if (!actionRequestSent && endpointId) { - getProcesses({ - endpoint_ids: [endpointId], - comment: command.args.args?.comment?.[0], - }); - - setStore((prevState) => { - return { ...prevState, actionRequestSent: true }; - }); - } - }, [actionRequestSent, command.args.args?.comment, endpointId, getProcesses, setStore]); +export const GetProcessesActionResult = memo( + ({ command, setStore, store, status, setStatus, ResultComponent }) => { + const endpointId = command.commandDefinition?.meta?.endpointId; + const actionCreator = useSendGetEndpointProcessesRequest(); + + const actionRequestBody = useMemo(() => { + return endpointId + ? { + endpoint_ids: [endpointId], + comment: command.args.args?.comment?.[0], + } + : undefined; + }, [command.args.args?.comment, endpointId]); + + const { result, actionDetails: completedActionDetails } = useConsoleActionSubmitter< + ProcessesRequestBody, + GetProcessesActionOutputContent + >({ + ResultComponent, + setStore, + store, + status, + setStatus, + actionCreator, + actionRequestBody, + dataTestSubj: 'getProcesses', + }); + + const columns = useMemo( + () => [ + { + field: 'user', + name: i18n.translate( + 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.user', + { defaultMessage: 'USER' } + ), + width: '10%', + }, + { + field: 'pid', + name: i18n.translate( + 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.pid', + { defaultMessage: 'PID' } + ), + width: '5%', + }, + { + field: 'entity_id', + name: i18n.translate( + 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.enityId', + { defaultMessage: 'ENTITY ID' } + ), + width: '30%', + }, + + { + field: 'command', + name: i18n.translate( + 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.command', + { defaultMessage: 'COMMAND' } + ), + width: '55%', + }, + ], + [] + ); - // If get processes request was created, store the action id if necessary - useEffect(() => { - if (isPending) { - if (isGetProcessesSuccess && actionId !== getProcessesData?.data.id) { - setStore((prevState) => { - return { ...prevState, actionId: getProcessesData?.data.id }; - }); - } else if (processesActionRequestError) { - setStatus('error'); - setStore((prevState) => { - return { ...prevState, apiError: processesActionRequestError }; - }); + const tableEntries = useMemo(() => { + if (endpointId) { + return completedActionDetails?.outputs?.[endpointId]?.content.entries ?? []; } - } - }, [ - actionId, - getProcessesData?.data.id, - processesActionRequestError, - isGetProcessesSuccess, - setStatus, - setStore, - isPending, - ]); - - useEffect(() => { - if (actionDetails?.data.isCompleted && isPending) { - setStatus('success'); - setStore((prevState) => { - return { - ...prevState, - completedActionDetails: actionDetails?.data, - }; - }); - } - }, [actionDetails?.data, setStatus, setStore, isPending]); - - const columns = useMemo( - () => [ - { - field: 'user', - name: i18n.translate( - 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.user', - { defaultMessage: 'USER' } - ), - width: '10%', - }, - { - field: 'pid', - name: i18n.translate( - 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.pid', - { defaultMessage: 'PID' } - ), - width: '5%', - }, - { - field: 'entity_id', - name: i18n.translate( - 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.enityId', - { defaultMessage: 'ENTITY ID' } - ), - width: '30%', - }, - - { - field: 'command', - name: i18n.translate( - 'xpack.securitySolution.endpointResponseActions.getProcesses.table.header.command', - { defaultMessage: 'COMMAND' } - ), - width: '55%', - }, - ], - [] - ); + return []; + }, [completedActionDetails?.outputs, endpointId]); - const tableEntries = useMemo(() => { - if (endpointId) { - return completedActionDetails?.outputs?.[endpointId]?.content.entries ?? []; + if (!completedActionDetails || !completedActionDetails.wasSuccessful) { + return result; } - return []; - }, [completedActionDetails?.outputs, endpointId]); - - // Show nothing if still pending - if (isPending) { - return ; - } - // Show errors if perform action fails - if (isError && apiError) { + // Show results return ( - - + + ); } - - // Show errors - if (completedActionDetails?.errors) { - return ( - - ); - } - - // Show results - return ( - - - - ); -}); +); GetProcessesActionResult.displayName = 'GetProcessesActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks.tsx deleted file mode 100644 index bbc390bb2ba53..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect, useRef } from 'react'; -import { useIsMounted } from '../../hooks/use_is_mounted'; -import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; -import { ACTION_DETAILS_REFRESH_INTERVAL } from './constants'; -import type { ActionRequestState, ActionRequestComponentProps } from './types'; -import type { useSendIsolateEndpointRequest } from '../../hooks/endpoint/use_send_isolate_endpoint_request'; -import type { useSendReleaseEndpointRequest } from '../../hooks/endpoint/use_send_release_endpoint_request'; - -export const useUpdateActionState = ({ - actionRequestApi, - actionRequest, - command, - endpointId, - setStatus, - setStore, - isPending, -}: Pick & { - actionRequestApi: ReturnType< - typeof useSendIsolateEndpointRequest | typeof useSendReleaseEndpointRequest - >; - actionRequest?: ActionRequestState; - endpointId?: string; - isPending: boolean; -}) => { - const isMounted = useIsMounted(); - const actionRequestSent = Boolean(actionRequest?.requestSent); - const { data: actionDetails } = useGetActionDetails(actionRequest?.actionId ?? '-', { - enabled: Boolean(actionRequest?.actionId) && isPending, - refetchInterval: isPending ? ACTION_DETAILS_REFRESH_INTERVAL : false, - }); - - // keep a reference to track the console's mounted state - // in order to update the store and cause a re-render on action request API response - const latestIsMounted = useRef(false); - latestIsMounted.current = isMounted; - - // Create action request - useEffect(() => { - if (!actionRequestSent && endpointId && isMounted) { - const request: ActionRequestState = { - requestSent: true, - actionId: undefined, - }; - - actionRequestApi - .mutateAsync({ - endpoint_ids: [endpointId], - comment: command.args.args?.comment?.[0], - }) - .then((response) => { - request.actionId = response.data.id; - - if (latestIsMounted.current) { - setStore((prevState) => { - return { ...prevState, actionRequest: { ...request } }; - }); - } - }); - - setStore((prevState) => { - return { ...prevState, actionRequest: request }; - }); - } - }, [ - actionRequestApi, - actionRequestSent, - command.args.args?.comment, - endpointId, - isMounted, - setStore, - ]); - - useEffect(() => { - // update the console's mounted state ref - latestIsMounted.current = isMounted; - // set to false when unmounted/console is hidden - return () => { - latestIsMounted.current = false; - }; - }, [isMounted]); - - useEffect(() => { - if (actionDetails?.data.isCompleted && isPending) { - setStatus('success'); - setStore((prevState) => { - return { - ...prevState, - completedActionDetails: actionDetails.data, - }; - }); - } - }, [actionDetails?.data, actionDetails?.data.isCompleted, setStatus, setStore, isPending]); -}; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx new file mode 100644 index 0000000000000..7ef506ea30f32 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx @@ -0,0 +1,275 @@ +/* + * 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 { + UseConsoleActionSubmitterOptions, + ConsoleActionSubmitter, + CommandResponseActionApiState, +} from './use_console_action_submitter'; +import { useConsoleActionSubmitter } from './use_console_action_submitter'; +import type { AppContextTestRender } from '../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import { EndpointActionGenerator } from '../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import React, { useState } from 'react'; +import type { CommandExecutionResultProps } from '../../console'; +import type { DeferredInterface } from '../../../mocks/utils'; +import { getDeferred } from '../../../mocks/utils'; +import type { ActionDetails } from '../../../../../common/endpoint/types'; +import { act, waitFor } from '@testing-library/react'; +import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks'; + +describe('When using `useConsoleActionSubmitter()` hook', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let renderArgs: UseConsoleActionSubmitterOptions; + let updateHookRenderArgs: () => void; + let hookRenderResultStorage: jest.Mock<(args: ConsoleActionSubmitter) => void>; + let releaseSuccessActionRequestApiResponse: DeferredInterface['resolve']; + let releaseFailedActionRequestApiResponse: DeferredInterface['reject']; + let apiMocks: ReturnType; + + const ActionSubmitterTestComponent = () => { + const [hookOptions, setHookOptions] = useState(renderArgs); + + updateHookRenderArgs = () => { + new Promise((r) => { + setTimeout(r, 1); + }).then(() => { + setHookOptions({ + ...renderArgs, + }); + }); + }; + + const { result, actionDetails } = useConsoleActionSubmitter(hookOptions); + + hookRenderResultStorage({ result, actionDetails }); + + return
    {result}
    ; + }; + + const getOutputTextContent = (): string => { + return renderResult.getByTestId('testContainer').textContent ?? ''; + }; + + beforeEach(() => { + const { render: renderComponent, coreStart } = createAppRootMockRenderer(); + const actionGenerator = new EndpointActionGenerator(); + const deferred = getDeferred(); + + apiMocks = responseActionsHttpMocks(coreStart.http); + + hookRenderResultStorage = jest.fn(); + releaseSuccessActionRequestApiResponse = () => + deferred.resolve(actionGenerator.generateActionDetails({ id: '123' })); + releaseFailedActionRequestApiResponse = deferred.reject; + + let status: UseConsoleActionSubmitterOptions['status'] = 'pending'; + let commandStore: CommandResponseActionApiState = {}; + + renderArgs = { + dataTestSubj: 'test', + actionRequestBody: { + endpoint_ids: ['123'], + }, + actionCreator: { + mutateAsync: jest.fn(async () => { + return { + data: await deferred.promise, + }; + }), + } as unknown as UseConsoleActionSubmitterOptions['actionCreator'], + get status() { + return status; + }, + setStatus: jest.fn((newStatus) => { + status = newStatus; + updateHookRenderArgs(); + }), + get store() { + return commandStore; + }, + setStore: jest.fn((newStoreOrCallback: object | ((prevStore: object) => object)) => { + if (typeof newStoreOrCallback === 'function') { + commandStore = newStoreOrCallback(commandStore); + } else { + commandStore = newStoreOrCallback; + } + + updateHookRenderArgs(); + }), + ResultComponent: jest.fn( + ({ children, showAs, 'data-test-subj': dataTestSubj }: CommandExecutionResultProps) => { + return ( +
    + {children} +
    + ); + } + ), + }; + + render = () => { + renderResult = renderComponent(); + return renderResult; + }; + }); + + afterEach(() => { + renderResult.unmount(); + }); + + it('should return expected interface while its still pending', () => { + render(); + + expect(hookRenderResultStorage).toHaveBeenLastCalledWith({ + result: expect.anything(), + action: undefined, + }); + + expect(renderResult.getByTestId('test-pending')).not.toBeNull(); + }); + + it('should update command state when request is sent', () => { + render(); + + expect(renderArgs.store?.actionApiState?.request.sent).toBe(true); + expect(renderArgs.store?.actionApiState?.request.actionId).toBe(undefined); + }); + + it('should store the action id when action request api is successful', async () => { + render(); + + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + expect(renderArgs.store?.actionApiState?.request.actionId).toBe('123'); + }); + }); + + it('should store action request api error', async () => { + render(); + const error = new Error('oh oh. request failed'); + + act(() => { + releaseFailedActionRequestApiResponse(error); + }); + + await waitFor(() => { + expect(renderArgs.store?.actionApiState?.request.actionId).toBe(undefined); + expect(renderArgs.store?.actionApiState?.request.error).toBe(error); + }); + + await waitFor(() => { + expect(getOutputTextContent()).toEqual( + 'The following error was encountered:oh oh. request failed' + ); + }); + }); + + it('should still store the action id if component is unmounted while action request API is in flight', async () => { + render(); + renderResult.unmount(); + + expect(renderArgs.store.actionApiState?.request.sent).toBe(true); + + const requestState = renderArgs.store.actionApiState?.request; + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + // this check just ensure that we mutated the state when the api returned success instead of + // dispatching a `setStore()`. + expect(renderArgs.store.actionApiState?.request === requestState).toBe(true); + + expect(renderArgs.store.actionApiState?.request.actionId).toEqual('123'); + }); + }); + + it('should call action details api once we have an action id', async () => { + render(); + + expect(apiMocks.responseProvider.actionDetails).not.toHaveBeenCalled(); + + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + }); + + it('should continue to show pending message until action completes', async () => { + apiMocks.responseProvider.actionDetails.mockImplementation(() => { + return { + data: new EndpointActionGenerator().generateActionDetails({ + id: '123', + isCompleted: false, + }), + }; + }); + render(); + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + + expect(renderResult.getByTestId('test-pending')).not.toBeNull(); + + expect(hookRenderResultStorage).toHaveBeenLastCalledWith({ + result: expect.anything(), + actionDetails: undefined, + }); + }); + + it('should store action details api error', async () => { + const error = new Error('on oh. getting action details failed'); + apiMocks.responseProvider.actionDetails.mockImplementation(() => { + throw error; + }); + + render(); + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + + expect(renderArgs.store.actionApiState?.actionDetailsError).toBe(error); + + expect(renderResult.getByTestId('test-apiFailure').textContent).toEqual( + 'The following error was encountered:on oh. getting action details failed' + ); + }); + + it('should store action details once action completes', async () => { + const actionDetails = new EndpointActionGenerator().generateActionDetails({ id: '123' }); + apiMocks.responseProvider.actionDetails.mockReturnValue({ data: actionDetails }); + + render(); + releaseSuccessActionRequestApiResponse(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + + expect(renderArgs.store.actionApiState?.actionDetails).toBe(actionDetails); + expect(hookRenderResultStorage).toHaveBeenLastCalledWith({ + result: expect.anything(), + actionDetails, + }); + + expect(renderResult.getByTestId('test-success').textContent).toEqual(''); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx new file mode 100644 index 0000000000000..7183b5cc61ef7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx @@ -0,0 +1,292 @@ +/* + * 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, useMemo } from 'react'; +import type { UseMutationResult } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import type { BaseActionRequestBody } from '../../../../../common/endpoint/schema/actions'; +import { ActionSuccess } from '../action_success'; +import { ActionError } from '../action_error'; +import { FormattedError } from '../../formatted_error'; +import { useGetActionDetails } from '../../../hooks/endpoint/use_get_action_details'; +import { ACTION_DETAILS_REFRESH_INTERVAL } from '../constants'; +import type { + ActionDetails, + Immutable, + ResponseActionApiResponse, +} from '../../../../../common/endpoint/types'; +import type { CommandExecutionComponentProps } from '../../console'; + +export interface ConsoleActionSubmitter { + /** + * The ui to be returned to the console. This UI will display different states of the action, + * including pending, error conditions and generic success messages. + */ + result: JSX.Element; + actionDetails: Immutable> | undefined; +} + +/** + * Command store state for response action api state. + */ +export interface CommandResponseActionApiState { + actionApiState?: { + request: { + sent: boolean; + actionId: string | undefined; + error: IHttpFetchError | undefined; + }; + actionDetails: ActionDetails | undefined; + actionDetailsError: IHttpFetchError | undefined; + }; +} + +export interface UseConsoleActionSubmitterOptions< + TReqBody extends BaseActionRequestBody = BaseActionRequestBody, + TActionOutputContent extends object = object +> extends Pick< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + CommandExecutionComponentProps>, + 'ResultComponent' | 'setStore' | 'store' | 'status' | 'setStatus' + > { + actionCreator: UseMutationResult; + /** + * The API request body. If `undefined`, then API will not be called. + */ + actionRequestBody: TReqBody | undefined; + + dataTestSubj?: string; +} + +/** + * generic hook for use with Response Action commands. It will create the action, store its ID and + * continuously pull the Action's Details until it completes. It handles all aspects of UI display + * for the different states of the command (pending -> success/failure) + * + * @param actionCreator + * @param actionRequestBody + * @param setStatus + * @param status + * @param setStore + * @param store + * @param ResultComponent + * @param dataTestSubj + */ +export const useConsoleActionSubmitter = < + TReqBody extends BaseActionRequestBody = BaseActionRequestBody, + TActionOutputContent extends object = object +>({ + actionCreator, + actionRequestBody, + setStatus, + status, + setStore, + store, + ResultComponent, + dataTestSubj, +}: UseConsoleActionSubmitterOptions< + TReqBody, + TActionOutputContent +>): ConsoleActionSubmitter => { + const isMounted = useIsMounted(); + const getTestId = useTestIdGenerator(dataTestSubj); + const isPending = status === 'pending'; + + const currentActionState = useMemo< + Immutable>['actionApiState']> + >( + () => + store.actionApiState ?? { + request: { + sent: false, + error: undefined, + actionId: undefined, + }, + actionDetails: undefined, + actionDetailsError: undefined, + }, + [store.actionApiState] + ); + + const { actionDetails, actionDetailsError } = currentActionState; + const { + actionId, + sent: actionRequestSent, + error: actionRequestError, + } = currentActionState.request; + + const { data: apiActionDetailsResponse, error: apiActionDetailsError } = + useGetActionDetails(actionId ?? '-', { + enabled: Boolean(actionId) && isPending, + refetchInterval: isPending ? ACTION_DETAILS_REFRESH_INTERVAL : false, + }); + + // Create the action request if not yet done + useEffect(() => { + if (!actionRequestSent && actionRequestBody && isMounted()) { + const updatedRequestState: Required< + CommandResponseActionApiState + >['actionApiState']['request'] = { + ...( + currentActionState as Required< + CommandResponseActionApiState + >['actionApiState'] + ).request, + sent: true, + }; + + // The object defined above (`updatedRequestState`) is saved to the command state right away. + // the creation of the Action request (below) will mutate this object to store the Action ID + // once the API response is received. We do this to ensure that the action is not created more + // than once if the user happens to close the console prior to the response being returned. + // Once a response is received, we check if the component is mounted, and if so, then we send + // another update to the command store which will cause it to re-render and start checking for + // action completion. + actionCreator + .mutateAsync(actionRequestBody) + .then((response) => { + updatedRequestState.actionId = response.data.id; + }) + .catch((err) => { + updatedRequestState.error = err; + }) + .finally(() => { + // If the component is mounted, then set the store with the updated data (causes a rerender) + if (isMounted()) { + setStore((prevState) => { + return { + ...prevState, + actionApiState: { + ...(prevState.actionApiState ?? currentActionState), + request: { ...updatedRequestState }, + }, + }; + }); + } + }); + + setStore((prevState) => { + return { + ...prevState, + actionApiState: { + ...(prevState.actionApiState ?? currentActionState), + request: updatedRequestState, + }, + }; + }); + } + }, [ + actionCreator, + actionRequestBody, + actionRequestSent, + currentActionState, + isMounted, + setStore, + ]); + + // If an error was returned while attempting to create the action request, + // then set command status to error + useEffect(() => { + if (actionRequestError && isPending) { + setStatus('error'); + } + }, [actionRequestError, isPending, setStatus]); + + // If an error was return by the Action Details API, then store it and set the status to error + useEffect(() => { + if (apiActionDetailsError && isPending) { + setStatus('error'); + setStore((prevState) => { + return { + ...prevState, + actionApiState: { + ...(prevState.actionApiState ?? currentActionState), + actionDetails: undefined, + actionDetailsError: apiActionDetailsError, + }, + }; + }); + } + }, [apiActionDetailsError, currentActionState, isPending, setStatus, setStore]); + + // If the action details indicates complete, then update the action's console state and set the status to success + useEffect(() => { + if (apiActionDetailsResponse?.data.isCompleted && isPending) { + setStatus(apiActionDetailsResponse?.data.wasSuccessful ? 'success' : 'error'); + setStore((prevState) => { + return { + ...prevState, + actionApiState: { + ...(prevState.actionApiState ?? currentActionState), + actionDetails: apiActionDetailsResponse.data, + }, + // Unclear why I needed to cast this here. For some reason the `ActionDetails['outputs']` is + // reporting a type error for the `content` property, although the types seem to line up. + } as typeof prevState; + }); + } + }, [apiActionDetailsResponse, currentActionState, isPending, setStatus, setStore]); + + // Calculate the action's UI result based on the different API responses + const result = useMemo(() => { + if (isPending) { + return ; + } + + const apiError = actionRequestError || actionDetailsError; + + if (apiError) { + return ( + + + + + ); + } + + if (actionDetails) { + // Response action failures + if (actionDetails.errors) { + return ( + + ); + } + + return ( + + ); + } + + return <>; + }, [ + isPending, + actionRequestError, + actionDetailsError, + actionDetails, + ResultComponent, + getTestId, + ]); + + return { + result, + actionDetails: currentActionState.actionDetails, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.test.tsx index 63e05d420deaf..110ddbb53b1b0 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.test.tsx @@ -16,9 +16,9 @@ import { getEndpointResponseActionsConsoleCommands } from './endpoint_response_a import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; import { enterConsoleCommand } from '../console/mocks'; import { waitFor } from '@testing-library/react'; -import { getDeferred } from '../mocks'; import type { ResponderCapabilities } from '../../../../common/endpoint/constants'; import { RESPONDER_CAPABILITIES } from '../../../../common/endpoint/constants'; +import { getDeferred } from '../../mocks/utils'; describe('When using isolate action from response actions console', () => { let render: ( @@ -115,7 +115,7 @@ describe('When using isolate action from response actions console', () => { enterConsoleCommand(renderResult, 'isolate'); await waitFor(() => { - expect(renderResult.getByTestId('isolateSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('isolate-success')).toBeTruthy(); }); }); @@ -130,7 +130,7 @@ describe('When using isolate action from response actions console', () => { enterConsoleCommand(renderResult, 'isolate'); await waitFor(() => { - expect(renderResult.getByTestId('isolateErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('isolate-actionFailure').textContent).toMatch( /error one \| error two/ ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx index ec11d022650ca..8df7692cf3ac2 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx @@ -5,47 +5,37 @@ * 2.0. */ -import React, { memo } from 'react'; +import { memo, useMemo } from 'react'; +import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; import type { ActionRequestComponentProps } from './types'; import { useSendIsolateEndpointRequest } from '../../hooks/endpoint/use_send_isolate_endpoint_request'; -import { ActionError } from './action_error'; -import { useUpdateActionState } from './hooks'; export const IsolateActionResult = memo( ({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const { completedActionDetails, actionRequest } = store; - const isPending = status === 'pending'; const isolateHostApi = useSendIsolateEndpointRequest(); - useUpdateActionState({ - actionRequestApi: isolateHostApi, - actionRequest, - command, - endpointId, - setStatus, - setStore, - isPending, - }); - - // Show nothing if still pending - if (isPending) { - return ; - } + const actionRequestBody = useMemo(() => { + const endpointId = command.commandDefinition?.meta?.endpointId; + const comment = command.args.args?.comment?.[0]; - // Show errors - if (completedActionDetails?.errors) { - return ( - - ); - } + return endpointId + ? { + endpoint_ids: [endpointId], + comment, + } + : undefined; + }, [command.args.args?.comment, command.commandDefinition?.meta?.endpointId]); - // Show Success - return ; + return useConsoleActionSubmitter({ + ResultComponent, + setStore, + store, + status, + setStatus, + actionCreator: isolateHostApi, + actionRequestBody, + dataTestSubj: 'isolate', + }).result; } ); IsolateActionResult.displayName = 'IsolateActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.test.tsx index 827a4d6191754..f888df2099b13 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.test.tsx @@ -195,7 +195,7 @@ describe('When using the kill-process action from response actions console', () enterConsoleCommand(renderResult, 'kill-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('killProcessSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('killProcess-success')).toBeTruthy(); }); }); @@ -204,7 +204,7 @@ describe('When using the kill-process action from response actions console', () enterConsoleCommand(renderResult, 'kill-process --entityId 123wer'); await waitFor(() => { - expect(renderResult.getByTestId('killProcessSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('killProcess-success')).toBeTruthy(); }); }); @@ -219,7 +219,7 @@ describe('When using the kill-process action from response actions console', () enterConsoleCommand(renderResult, 'kill-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('killProcessErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('killProcess-actionFailure').textContent).toMatch( /error one \| error two/ ); }); @@ -234,7 +234,7 @@ describe('When using the kill-process action from response actions console', () enterConsoleCommand(renderResult, 'kill-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('killProcessAPIErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('killProcess-apiFailure').textContent).toMatch( /this is an error/ ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx index 3e4dcf61d1690..bf501c31b9e85 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx @@ -5,130 +5,40 @@ * 2.0. */ -import React, { memo, useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { memo, useMemo } from 'react'; +import type { KillOrSuspendProcessRequestBody } from '../../../../common/endpoint/types'; import { parsedPidOrEntityIdParameter } from './utils'; -import { ActionSuccess } from './action_success'; -import type { - ActionDetails, - KillProcessActionOutputContent, -} from '../../../../common/endpoint/types'; -import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; -import type { EndpointCommandDefinitionMeta } from './types'; import { useSendKillProcessRequest } from '../../hooks/endpoint/use_send_kill_process_endpoint_request'; -import type { CommandExecutionComponentProps } from '../console/types'; -import { ActionError } from './action_error'; -import { ACTION_DETAILS_REFRESH_INTERVAL } from './constants'; +import type { ActionRequestComponentProps } from './types'; +import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; export const KillProcessActionResult = memo< - CommandExecutionComponentProps< - { comment?: string; pid?: string; entityId?: string }, - { - actionId?: string; - actionRequestSent?: boolean; - completedActionDetails?: ActionDetails; - apiError?: IHttpFetchError; - }, - EndpointCommandDefinitionMeta - > + ActionRequestComponentProps<{ pid?: string[]; entityId?: string[] }> >(({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const { actionId, completedActionDetails, apiError } = store; - const isPending = status === 'pending'; - const isError = status === 'error'; - const actionRequestSent = Boolean(store.actionRequestSent); + const actionCreator = useSendKillProcessRequest(); - const { mutate, data, isSuccess, error } = useSendKillProcessRequest(); - - const { data: actionDetails } = useGetActionDetails( - actionId ?? '-', - { - enabled: Boolean(actionId) && isPending, - refetchInterval: isPending ? ACTION_DETAILS_REFRESH_INTERVAL : false, - } - ); - - // Send Kill request if not yet done - useEffect(() => { + const actionRequestBody = useMemo(() => { + const endpointId = command.commandDefinition?.meta?.endpointId; const parameters = parsedPidOrEntityIdParameter(command.args.args); - if (!actionRequestSent && endpointId && parameters) { - mutate({ - endpoint_ids: [endpointId], - comment: command.args.args?.comment?.[0], - parameters, - }); - setStore((prevState) => { - return { ...prevState, actionRequestSent: true }; - }); - } - }, [actionRequestSent, command.args.args, endpointId, mutate, setStore]); - - // If kill-process request was created, store the action id if necessary - useEffect(() => { - if (isPending) { - if (isSuccess && actionId !== data.data.id) { - setStore((prevState) => { - return { ...prevState, actionId: data.data.id }; - }); - } else if (error) { - setStatus('error'); - setStore((prevState) => { - return { ...prevState, apiError: error }; - }); - } - } - }, [actionId, data?.data.id, isSuccess, error, setStore, setStatus, isPending]); - - useEffect(() => { - if (actionDetails?.data.isCompleted && isPending) { - setStatus('success'); - setStore((prevState) => { - return { - ...prevState, - completedActionDetails: actionDetails.data, - }; - }); - } - }, [actionDetails?.data, setStatus, setStore, isPending]); - - // Show API errors if perform action fails - if (isError && apiError) { - return ( - - - - ); - } - - // Show nothing if still pending - if (isPending || !completedActionDetails) { - return ; - } - - // Show errors - if (completedActionDetails?.errors) { - return ( - - ); - } - - // Show Success - return ( - - ); + return endpointId + ? { + endpoint_ids: [endpointId], + comment: command.args.args?.comment?.[0], + parameters, + } + : undefined; + }, [command.args.args, command.commandDefinition?.meta?.endpointId]); + + return useConsoleActionSubmitter({ + ResultComponent, + setStore, + store, + status, + setStatus, + actionCreator, + actionRequestBody, + dataTestSubj: 'killProcess', + }).result; }); KillProcessActionResult.displayName = 'KillProcessActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.test.tsx index 19e3be94469eb..d1c1ea264f863 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.test.tsx @@ -16,9 +16,9 @@ import { getEndpointResponseActionsConsoleCommands } from './endpoint_response_a import { enterConsoleCommand } from '../console/mocks'; import { waitFor } from '@testing-library/react'; import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; -import { getDeferred } from '../mocks'; import type { ResponderCapabilities } from '../../../../common/endpoint/constants'; import { RESPONDER_CAPABILITIES } from '../../../../common/endpoint/constants'; +import { getDeferred } from '../../mocks/utils'; describe('When using the release action from response actions console', () => { let render: ( @@ -116,7 +116,7 @@ describe('When using the release action from response actions console', () => { enterConsoleCommand(renderResult, 'release'); await waitFor(() => { - expect(renderResult.getByTestId('releaseSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('release-success')).toBeTruthy(); }); }); @@ -131,7 +131,7 @@ describe('When using the release action from response actions console', () => { enterConsoleCommand(renderResult, 'release'); await waitFor(() => { - expect(renderResult.getByTestId('releaseErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('release-actionFailure').textContent).toMatch( /error one \| error two/ ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx index f789b48671324..9b0f371ca003f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx @@ -5,48 +5,37 @@ * 2.0. */ -import React, { memo } from 'react'; +import { memo, useMemo } from 'react'; import type { ActionRequestComponentProps } from './types'; import { useSendReleaseEndpointRequest } from '../../hooks/endpoint/use_send_release_endpoint_request'; -import { ActionError } from './action_error'; -import { useUpdateActionState } from './hooks'; +import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; export const ReleaseActionResult = memo( ({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const { completedActionDetails, actionRequest } = store; - const isPending = status === 'pending'; - const releaseHostApi = useSendReleaseEndpointRequest(); - useUpdateActionState({ - actionRequestApi: releaseHostApi, - actionRequest, - command, - endpointId, - setStatus, - setStore, - isPending, - }); - - // Show nothing if still pending - if (isPending) { - return ; - } + const actionRequestBody = useMemo(() => { + const endpointId = command.commandDefinition?.meta?.endpointId; + const comment = command.args.args?.comment?.[0]; - // Show errors - if (completedActionDetails?.errors) { - return ( - - ); - } + return endpointId + ? { + endpoint_ids: [endpointId], + comment, + } + : undefined; + }, [command.args.args?.comment, command.commandDefinition?.meta?.endpointId]); - // Show Success - return ; + return useConsoleActionSubmitter({ + ResultComponent, + setStore, + store, + status, + setStatus, + actionCreator: releaseHostApi, + actionRequestBody, + dataTestSubj: 'release', + }).result; } ); ReleaseActionResult.displayName = 'ReleaseActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx index 9446fb5dcba6a..7479e52edfb07 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx @@ -186,7 +186,7 @@ describe('When using the suspend-process action from response actions console', enterConsoleCommand(renderResult, 'suspend-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('suspendProcess-success')).toBeTruthy(); }); }); @@ -195,7 +195,7 @@ describe('When using the suspend-process action from response actions console', enterConsoleCommand(renderResult, 'suspend-process --entityId 123wer'); await waitFor(() => { - expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy(); + expect(renderResult.getByTestId('suspendProcess-success')).toBeTruthy(); }); }); @@ -210,7 +210,7 @@ describe('When using the suspend-process action from response actions console', enterConsoleCommand(renderResult, 'suspend-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('suspendProcessErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('suspendProcess-actionFailure').textContent).toMatch( /error one \| error two/ ); }); @@ -225,7 +225,7 @@ describe('When using the suspend-process action from response actions console', enterConsoleCommand(renderResult, 'suspend-process --pid 123'); await waitFor(() => { - expect(renderResult.getByTestId('suspendProcessAPIErrorCallout').textContent).toMatch( + expect(renderResult.getByTestId('suspendProcess-apiFailure').textContent).toMatch( /this is an error/ ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx index a60e7eb6bd65b..f8401a81fa114 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx @@ -5,130 +5,46 @@ * 2.0. */ -import React, { memo, useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { memo, useMemo } from 'react'; import { parsedPidOrEntityIdParameter } from './utils'; -import { ActionSuccess } from './action_success'; import type { - ActionDetails, SuspendProcessActionOutputContent, + KillOrSuspendProcessRequestBody, } from '../../../../common/endpoint/types'; -import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; -import type { EndpointCommandDefinitionMeta } from './types'; import { useSendSuspendProcessRequest } from '../../hooks/endpoint/use_send_suspend_process_endpoint_request'; -import type { CommandExecutionComponentProps } from '../console/types'; -import { ActionError } from './action_error'; -import { ACTION_DETAILS_REFRESH_INTERVAL } from './constants'; +import type { ActionRequestComponentProps } from './types'; +import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; export const SuspendProcessActionResult = memo< - CommandExecutionComponentProps< - { comment?: string; pid?: string; entityId?: string }, - { - actionId?: string; - actionRequestSent?: boolean; - completedActionDetails?: ActionDetails; - apiError?: IHttpFetchError; - }, - EndpointCommandDefinitionMeta - > + ActionRequestComponentProps<{ pid?: string[]; entityId?: string[] }> >(({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const { actionId, completedActionDetails, apiError } = store; - const isPending = status === 'pending'; - const isError = status === 'error'; - const actionRequestSent = Boolean(store.actionRequestSent); + const actionCreator = useSendSuspendProcessRequest(); - const { mutate, data, isSuccess, error } = useSendSuspendProcessRequest(); - - const { data: actionDetails } = useGetActionDetails( - actionId ?? '-', - { - enabled: Boolean(actionId) && isPending, - refetchInterval: isPending ? ACTION_DETAILS_REFRESH_INTERVAL : false, - } - ); - - // Send Suspend request if not yet done - useEffect(() => { + const actionRequestBody = useMemo(() => { + const endpointId = command.commandDefinition?.meta?.endpointId; const parameters = parsedPidOrEntityIdParameter(command.args.args); - if (!actionRequestSent && endpointId && parameters) { - mutate({ - endpoint_ids: [endpointId], - comment: command.args.args?.comment?.[0], - parameters, - }); - setStore((prevState) => { - return { ...prevState, actionRequestSent: true }; - }); - } - }, [actionRequestSent, command.args.args, endpointId, mutate, setStore]); - - // If suspend-process request was created, store the action id if necessary - useEffect(() => { - if (isPending) { - if (isSuccess && actionId !== data.data.id) { - setStore((prevState) => { - return { ...prevState, actionId: data.data.id }; - }); - } else if (error) { - setStatus('error'); - setStore((prevState) => { - return { ...prevState, apiError: error }; - }); - } - } - }, [actionId, data?.data.id, isSuccess, error, setStore, setStatus, isPending]); - - useEffect(() => { - if (actionDetails?.data.isCompleted && isPending) { - setStatus('success'); - setStore((prevState) => { - return { - ...prevState, - completedActionDetails: actionDetails.data, - }; - }); - } - }, [actionDetails?.data, setStatus, setStore, isPending]); - - // Show API errors if perform action fails - if (isError && apiError) { - return ( - - - - ); - } - - // Show nothing if still pending - if (isPending || !completedActionDetails) { - return ; - } - - // Show errors - if (completedActionDetails?.errors) { - return ( - - ); - } - - // Show Success - return ( - - ); + return endpointId + ? { + endpoint_ids: [endpointId], + comment: command.args.args?.comment?.[0], + parameters, + } + : undefined; + }, [command.args.args, command.commandDefinition?.meta?.endpointId]); + + return useConsoleActionSubmitter< + KillOrSuspendProcessRequestBody, + SuspendProcessActionOutputContent + >({ + ResultComponent, + setStore, + store, + status, + setStatus, + actionCreator, + actionRequestBody, + dataTestSubj: 'suspendProcess', + }).result; }); SuspendProcessActionResult.displayName = 'SuspendProcessActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/types.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/types.ts index 34c306ed0a116..92cc3e8c9017a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/types.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/types.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { CommandResponseActionApiState } from './hooks/use_console_action_submitter'; import type { ManagedConsoleExtensionComponentProps } from '../console'; -import type { ActionDetails, HostMetadata } from '../../../../common/endpoint/types'; +import type { HostMetadata } from '../../../../common/endpoint/types'; import type { CommandExecutionComponentProps } from '../console/types'; export interface EndpointCommandDefinitionMeta { @@ -17,16 +18,9 @@ export type EndpointResponderExtensionComponentProps = ManagedConsoleExtensionCo endpoint: HostMetadata; }>; -export interface ActionRequestState { - requestSent: boolean; - actionId?: string; -} - -export type ActionRequestComponentProps = CommandExecutionComponentProps< - { comment?: string }, - { - actionRequest?: ActionRequestState; - completedActionDetails?: ActionDetails; - }, - EndpointCommandDefinitionMeta ->; +export type ActionRequestComponentProps = + CommandExecutionComponentProps< + { comment?: string } & TArgs, + CommandResponseActionApiState, + EndpointCommandDefinitionMeta + >; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.test.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.test.ts index ab84e9de959f0..48b6753645c92 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.test.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.test.ts @@ -19,9 +19,9 @@ describe('Endpoint Responder - Utilities', () => { expect(parameters).toEqual({ entity_id: '123qwe' }); }); - it('should return undefined if no params are defined', () => { + it('should return entity id with emtpy string if no params are defined', () => { const parameters = parsedPidOrEntityIdParameter({}); - expect(parameters).toEqual(undefined); + expect(parameters).toEqual({ entity_id: '' }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.ts index 9ebcd090bd2a0..0b8e59d0353f7 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/utils.ts @@ -4,17 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { EndpointActionDataParameterTypes } from '../../../../common/endpoint/types'; +import type { ResponseActionParametersWithPidOrEntityId } from '../../../../common/endpoint/types'; export const parsedPidOrEntityIdParameter = (parameters: { pid?: string[]; entityId?: string[]; -}): EndpointActionDataParameterTypes => { +}): ResponseActionParametersWithPidOrEntityId => { if (parameters.pid) { return { pid: Number(parameters.pid[0]) }; - } else if (parameters.entityId) { - return { entity_id: parameters.entityId[0] }; } - return undefined; + return { + entity_id: parameters?.entityId?.[0] ?? '', + }; }; diff --git a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx index b5c7629b76aa6..2d3a9c9cb7f07 100644 --- a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx +++ b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx @@ -13,6 +13,7 @@ import classnames from 'classnames'; import { useLocation } from 'react-router-dom'; import type { EuiPortalProps } from '@elastic/eui/src/components/portal/portal'; import type { EuiTheme } from '@kbn/kibana-react-plugin/common'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import { useHasFullScreenContent } from '../../../common/containers/use_full_screen'; import { FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET, @@ -22,7 +23,6 @@ import { SELECTOR_TIMELINE_IS_VISIBLE_CSS_CLASS_NAME, TIMELINE_EUI_THEME_ZINDEX_LEVEL, } from '../../../timelines/components/timeline/styles'; -import { useIsMounted } from '../../hooks/use_is_mounted'; const OverlayRootContainer = styled.div` border: none; @@ -246,7 +246,7 @@ export const PageOverlay = memo( // Capture the URL `pathname` that the overlay was opened for useEffect(() => { - if (isMounted) { + if (isMounted()) { setOpenedOnPathName((prevState) => { if (isHidden) { return null; @@ -270,7 +270,7 @@ export const PageOverlay = memo( // If `hideOnUrlPathNameChange` is true, then determine if the pathname changed and if so, call `onHide()` useEffect(() => { if ( - isMounted && + isMounted() && onHide && hideOnUrlPathnameChange && !isHidden && @@ -283,7 +283,7 @@ export const PageOverlay = memo( // Handle adding class names to the `document.body` DOM element useEffect(() => { - if (isMounted) { + if (isMounted()) { if (isHidden) { unSetDocumentBodyOverlayIsVisible(); unSetDocumentBodyLock(); diff --git a/x-pack/plugins/security_solution/public/management/hooks/use_is_mounted.ts b/x-pack/plugins/security_solution/public/management/hooks/use_is_mounted.ts deleted file mode 100644 index 0c5a79b2ca2fc..0000000000000 --- a/x-pack/plugins/security_solution/public/management/hooks/use_is_mounted.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect, useState } from 'react'; - -/** - * Track when a component is mounted/unmounted. Good for use in async processing that may update - * a component's internal state. - */ -export const useIsMounted = (): boolean => { - const [isMounted, setIsMounted] = useState(false); - - useEffect(() => { - setIsMounted(true); - - return () => { - setIsMounted(false); - }; - }, []); - - return isMounted; -}; diff --git a/x-pack/plugins/security_solution/public/management/components/mocks.tsx b/x-pack/plugins/security_solution/public/management/mocks/utils.ts similarity index 93% rename from x-pack/plugins/security_solution/public/management/components/mocks.tsx rename to x-pack/plugins/security_solution/public/management/mocks/utils.ts index 45c12df818fd8..946d2d50b05d2 100644 --- a/x-pack/plugins/security_solution/public/management/components/mocks.tsx +++ b/x-pack/plugins/security_solution/public/management/mocks/utils.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -interface DeferredInterface { + +export interface DeferredInterface { promise: Promise; resolve: (data: T) => void; reject: (e: Error) => void; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx index 37f04ff804c1d..b8f7a8c19fbcd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx @@ -20,7 +20,7 @@ import { PolicyArtifactsDeleteModal } from './policy_artifacts_delete_modal'; import { exceptionsListAllHttpMocks } from '../../../../../mocks/exceptions_list_http_mocks'; import { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client'; import { POLICY_ARTIFACT_DELETE_MODAL_LABELS } from './translations'; -import { getDeferred } from '../../../../../components/mocks'; +import { getDeferred } from '../../../../../mocks/utils'; const listType: Array = [ 'endpoint_events', diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b2984037b0302..56922e26edc21 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -25702,9 +25702,6 @@ "xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "Cette liste inclut {numberOfEntries} événements de processus.", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rév. {revNumber}", "xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "{ errorCount, plural, =1 {Erreur rencontrée} other {Erreurs rencontrées}} :", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessage": "L'erreur suivante a été rencontrée : {error}", - "xpack.securitySolution.endpointResponseActions.killProcess.performApiErrorMessage": "L'erreur suivante a été rencontrée : {error}", - "xpack.securitySolution.endpointResponseActions.suspendProcess.performApiErrorMessage": "L'erreur suivante a été rencontrée : {error}", "xpack.securitySolution.event.reason.reasonRendererTitle": "Outil de rendu d'événement : {eventRendererName} ", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "Le champ {field} est un objet, et il est composé de champs imbriqués qui peuvent être ajoutés en tant que colonne", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "Afficher la colonne {field}", @@ -28124,8 +28121,6 @@ "xpack.securitySolution.endpointManagement.noPermissionsSubText": "Vous devez disposer du rôle de superutilisateur pour utiliser cette fonctionnalité. Si vous ne disposez pas de ce rôle, ni d'autorisations pour modifier les rôles d'utilisateur, contactez votre administrateur Kibana.", "xpack.securitySolution.endpointManagemnet.noPermissionsText": "Vous ne disposez pas des autorisations Kibana requises pour utiliser Elastic Security Administration", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "Politique appliquée", - "xpack.securitySolution.endpointResponseActions.getProcesses.errorMessageTitle": "Échec de l’obtention des processus", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessageTitle": "Échec de l’exécution de l’action d’obtention des processus", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.command": "COMMANDE", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.enityId": "ID D’ENTITÉ", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.pid": "PID", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a09bdfe578cb5..1441559ad00ab 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -25677,9 +25677,6 @@ "xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "このリストには、{numberOfEntries} 件のプロセスイベントが含まれています。", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rev. {revNumber}", "xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "次の{ errorCount, plural, other {件のエラー}}が発生しました:", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessage": "次のエラーが発生しました:{error}", - "xpack.securitySolution.endpointResponseActions.killProcess.performApiErrorMessage": "次のエラーが発生しました:{error}", - "xpack.securitySolution.endpointResponseActions.suspendProcess.performApiErrorMessage": "次のエラーが発生しました:{error}", "xpack.securitySolution.event.reason.reasonRendererTitle": "イベントレンダラー:{eventRendererName} ", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "{field}フィールドはオブジェクトであり、列として追加できるネストされたフィールドに分解されます", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "{field} 列を表示", @@ -28099,8 +28096,6 @@ "xpack.securitySolution.endpointManagement.noPermissionsSubText": "この機能を使用するには、スーパーユーザーロールが必要です。スーパーユーザーロールがなく、ユーザーロールを編集する権限もない場合は、Kibana管理者に問い合わせてください。", "xpack.securitySolution.endpointManagemnet.noPermissionsText": "Elastic Security Administrationを使用するために必要なKibana権限がありません。", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "ポリシーが適用されました", - "xpack.securitySolution.endpointResponseActions.getProcesses.errorMessageTitle": "プロセスの取得アクションが失敗しました", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessageTitle": "プロセスの取得アクションの実行が失敗しました", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.command": "コマンド", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.enityId": "エンティティID", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.pid": "PID", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5ade45168ae73..d1651dddce021 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25711,9 +25711,6 @@ "xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "此列表包括 {numberOfEntries} 个进程事件。", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "修订版 {revNumber}", "xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "遇到以下{ errorCount, plural, other {错误}}:", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessage": "遇到以下错误:{error}", - "xpack.securitySolution.endpointResponseActions.killProcess.performApiErrorMessage": "遇到以下错误:{error}", - "xpack.securitySolution.endpointResponseActions.suspendProcess.performApiErrorMessage": "遇到以下错误:{error}", "xpack.securitySolution.event.reason.reasonRendererTitle": "事件渲染器:{eventRendererName} ", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "{field} 字段是对象,并分解为可以添加为列的嵌套字段", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "查看 {field} 列", @@ -28133,8 +28130,6 @@ "xpack.securitySolution.endpointManagement.noPermissionsSubText": "您必须具有超级用户角色才能使用此功能。如果您不具有超级用户角色,且无权编辑用户角色,请与 Kibana 管理员联系。", "xpack.securitySolution.endpointManagemnet.noPermissionsText": "您没有所需的 Kibana 权限,无法使用 Elastic Security 管理", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "已应用策略", - "xpack.securitySolution.endpointResponseActions.getProcesses.errorMessageTitle": "获取进程操作失败", - "xpack.securitySolution.endpointResponseActions.getProcesses.performApiErrorMessageTitle": "执行获取进程操作失败", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.command": "命令", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.enityId": "实体 ID", "xpack.securitySolution.endpointResponseActions.getProcesses.table.header.pid": "PID", From f571f8082716ecda912a2d380118138cf28d4663 Mon Sep 17 00:00:00 2001 From: Andrew Macri Date: Tue, 27 Sep 2022 14:15:53 -0600 Subject: [PATCH 099/172] [Security Solution] Disable renderer hover actions in the Rules preview flyout (#141546) ## [Security Solution] Disable hover actions on renderers in the Rules preview flyout This PR addresses issue by disabling hover actions on renderers in the _Rules preview_ flyout, per the Before and After screenshots below: ### Before ![before](https://user-images.githubusercontent.com/4459398/191833235-ed9974d8-3d31-4da1-8db6-e5c500003f6b.png) _Above: Before the fix, actions were displayed when hovering over a rendered field in the Rules preview flyout_ ### After ![after](https://user-images.githubusercontent.com/4459398/191833522-427649ab-9670-48df-8a87-1980fe8e4070.png) _Above: After the fix, actions are NOT displayed when hovering over a rendered field in the Rules preview flyout_ --- .../drag_and_drop/draggable_wrapper.test.tsx | 105 +++++++++++++++++- .../drag_and_drop/draggable_wrapper.tsx | 6 +- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx index c12643a30f943..b6a3995534d1f 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx @@ -5,16 +5,24 @@ * 2.0. */ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import React from 'react'; import type { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd'; -import { waitFor } from '@testing-library/react'; + import '../../mock/match_media'; +import { TimelineId } from '../../../../common/types'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers'; +import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants'; import { DragDropContextWrapper } from './drag_drop_context_wrapper'; -import { ConditionalPortal, DraggableWrapper, getStyle } from './draggable_wrapper'; +import { + ConditionalPortal, + disableHoverActions, + DraggableWrapper, + getStyle, +} from './draggable_wrapper'; import { useMountAppended } from '../../utils/use_mount_appended'; jest.mock('../../lib/kibana'); @@ -27,6 +35,26 @@ jest.mock('@elastic/eui', () => { }; }); +const timelineIdsWithHoverActions = [ + undefined, + TimelineId.active, + TimelineId.alternateTest, + TimelineId.casePage, + TimelineId.detectionsPage, + TimelineId.detectionsRulesDetailsPage, + TimelineId.hostsPageEvents, + TimelineId.hostsPageSessions, + TimelineId.kubernetesPageSessions, + TimelineId.networkPageEvents, + TimelineId.test, + TimelineId.usersPageEvents, +]; + +const timelineIdsNoHoverActions = [ + TimelineId.rulePreview, + ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID, +]; + describe('DraggableWrapper', () => { const dataProvider = mockDataProviders[0]; const message = 'draggable wrapper content'; @@ -36,6 +64,15 @@ describe('DraggableWrapper', () => { jest.useFakeTimers(); }); + afterEach(() => { + const portal = document.querySelector('[data-euiportal="true"]'); + if (portal != null) { + portal.innerHTML = ''; + } + + jest.useRealTimers(); + }); + describe('rendering', () => { test('it renders against the snapshot', () => { const wrapper = shallow( @@ -103,6 +140,56 @@ describe('DraggableWrapper', () => { expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(true); }); }); + + timelineIdsWithHoverActions.forEach((timelineId) => { + test(`it renders hover actions (by default) when 'isDraggable' is false and timelineId is '${timelineId}'`, async () => { + const isDraggable = false; + + const { container } = render( + + + message} + timelineId={timelineId} + /> + + + ); + + fireEvent.mouseEnter(container.querySelector('[data-test-subj="withHoverActionsButton"]')!); + + await waitFor(() => { + expect(screen.getByTestId('hover-actions-copy-button')).toBeInTheDocument(); + }); + }); + }); + + timelineIdsNoHoverActions.forEach((timelineId) => { + test(`it does NOT render hover actions when 'isDraggable' is false and timelineId is '${timelineId}'`, async () => { + const isDraggable = false; + + const { container } = render( + + + message} + timelineId={timelineId} + /> + + + ); + + fireEvent.mouseEnter(container.querySelector('[data-test-subj="withHoverActionsButton"]')!); + + await waitFor(() => { + expect(screen.queryByTestId('hover-actions-copy-button')).not.toBeInTheDocument(); + }); + }); + }); }); describe('text truncation styling', () => { @@ -192,4 +279,18 @@ describe('ConditionalPortal', () => { expect(getStyle(style, snapshot)).toHaveProperty('transitionDuration', '0.00000001s'); }); }); + + describe('disableHoverActions', () => { + timelineIdsNoHoverActions.forEach((timelineId) => + test(`it returns true when timelineId is ${timelineId}`, () => { + expect(disableHoverActions(timelineId)).toBe(true); + }) + ); + + timelineIdsWithHoverActions.forEach((timelineId) => + test(`it returns false when timelineId is ${timelineId}`, () => { + expect(disableHoverActions(timelineId)).toBe(false); + }) + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx index f972bcf463b5b..887f1635c08c6 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx @@ -18,6 +18,7 @@ import { Draggable, Droppable } from 'react-beautiful-dnd'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import { TimelineId } from '../../../../common/types'; import { dragAndDropActions } from '../../store/drag_and_drop'; import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants'; @@ -108,6 +109,9 @@ interface Props { onFilterAdded?: () => void; } +export const disableHoverActions = (timelineId: string | undefined): boolean => + [TimelineId.rulePreview, ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID].includes(timelineId ?? ''); + /** * Wraps a draggable component to handle registration / unregistration of the * data provider associated with the item being dropped @@ -370,7 +374,7 @@ const DraggableWrapperComponent: React.FC = ({ From 27483d5aa90d80d2896d92b22da148372c09195e Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 27 Sep 2022 17:18:41 -0400 Subject: [PATCH 100/172] Fix curl documentation (#141971) --- docs/user/reporting/script-example.asciidoc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/user/reporting/script-example.asciidoc b/docs/user/reporting/script-example.asciidoc index 1d8e824798e75..937e140bd67a0 100644 --- a/docs/user/reporting/script-example.asciidoc +++ b/docs/user/reporting/script-example.asciidoc @@ -3,7 +3,7 @@ URL that you use to download the report. Use the `GET` method in the HTTP reques To queue CSV report generation using the `POST` URL with cURL: -["source","sh",subs="attributes"] +[source,curl] --------------------------------------------------------- curl \ -XPOST \ <1> @@ -11,7 +11,6 @@ curl \ -H 'kbn-xsrf: true' \ <3> 'http://0.0.0.0:5601/api/reporting/generate/csv?jobParams=...' <4> --------------------------------------------------------- -// CONSOLE <1> The required `POST` method. <2> The user credentials for a user with permission to access {kib} and {report-features}. @@ -20,7 +19,7 @@ curl \ An example response for a successfully queued report: -[source,json] +[source,js] --------------------------------------------------------- { "path": "/api/reporting/jobs/download/jxzaofkc0ykpf4062305t068", <1> @@ -35,7 +34,6 @@ An example response for a successfully queued report: } } --------------------------------------------------------- -// CONSOLE <1> The relative path on the {kib} host for downloading the report. <2> (Not included in the example) Internal representation of the reporting job, as found in the `.reporting-*` index. From e4080d5b64fa563382cf8a8f9c59f3bb9808d65e Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Tue, 27 Sep 2022 17:34:49 -0500 Subject: [PATCH 101/172] [Security Solution] use endpoint rbac for isolate/unisolate host (#141810) --- x-pack/plugins/fleet/common/authz.test.ts | 5 +- .../plugins/fleet/common/constants/authz.ts | 2 +- x-pack/plugins/fleet/common/mocks.ts | 15 ++++++ .../endpoint/service/authz/authz.test.ts | 34 ++++++++++++ .../common/endpoint/service/authz/authz.ts | 8 ++- .../endpoint/use_endpoint_privileges.test.ts | 11 ++-- .../details/components/actions_menu.test.tsx | 52 ++++++++++++++----- .../view/hooks/use_endpoint_action_items.tsx | 14 ++--- .../pages/endpoint_hosts/view/index.test.tsx | 32 ++++++++---- 9 files changed, 134 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index ced783c6d4adb..cadb90651b01c 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -15,7 +15,10 @@ import { ENDPOINT_PRIVILEGES } from './constants'; const SECURITY_SOLUTION_ID = DEFAULT_APP_CATEGORIES.security.id; -function generateActions(privileges: string[] = [], overrides: Record = {}) { +function generateActions( + privileges: typeof ENDPOINT_PRIVILEGES, + overrides: Record = {} +) { return privileges.reduce((acc, privilege) => { const executePackageAction = overrides[privilege] || false; diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts index 3bf1aeb46adc8..d7a0ca4ade2eb 100644 --- a/x-pack/plugins/fleet/common/constants/authz.ts +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -23,4 +23,4 @@ export const ENDPOINT_PRIVILEGES = [ 'writeHostIsolation', 'writeProcessOperations', 'writeFileOperations', -]; +] as const; diff --git a/x-pack/plugins/fleet/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts index bc8880ed385af..d5aca8398abd8 100644 --- a/x-pack/plugins/fleet/common/mocks.ts +++ b/x-pack/plugins/fleet/common/mocks.ts @@ -7,6 +7,7 @@ import type { DeletePackagePoliciesResponse, NewPackagePolicy, PackagePolicy } from './types'; import type { FleetAuthz } from './authz'; +import { ENDPOINT_PRIVILEGES } from './constants'; export const createNewPackagePolicyMock = (): NewPackagePolicy => { return { @@ -61,6 +62,15 @@ export const deletePackagePolicyMock = (): DeletePackagePoliciesResponse => { * Creates mock `authz` object */ export const createFleetAuthzMock = (): FleetAuthz => { + const endpointActions = ENDPOINT_PRIVILEGES.reduce((acc, privilege) => { + return { + ...acc, + [privilege]: { + executePackageAction: true, + }, + }; + }, {}); + return { fleet: { all: true, @@ -80,5 +90,10 @@ export const createFleetAuthzMock = (): FleetAuthz => { readIntegrationPolicies: true, writeIntegrationPolicies: true, }, + packagePrivileges: { + endpoint: { + actions: endpointActions, + }, + }, }; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index 3434e95f29b4b..b9c0dcff2054a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -107,6 +107,40 @@ describe('Endpoint Authz service', () => { ); }); }); + + describe('endpoint rbac is enabled', () => { + describe('canIsolateHost', () => { + it('should be true if packagePrivilege.writeHostIsolation is true', () => { + fleetAuthz.packagePrivileges!.endpoint.actions.writeHostIsolation.executePackageAction = + true; + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); + expect(authz.canIsolateHost).toBe(true); + }); + + it('should be false if packagePrivilege.writeHostIsolation is false', () => { + fleetAuthz.packagePrivileges!.endpoint.actions.writeHostIsolation.executePackageAction = + false; + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); + expect(authz.canIsolateHost).toBe(false); + }); + }); + + describe('canUnIsolateHost', () => { + it('should be true if packagePrivilege.writeHostIsolation is true', () => { + fleetAuthz.packagePrivileges!.endpoint.actions.writeHostIsolation.executePackageAction = + true; + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); + expect(authz.canUnIsolateHost).toBe(true); + }); + + it('should be false if packagePrivilege.writeHostIsolation is false', () => { + fleetAuthz.packagePrivileges!.endpoint.actions.writeHostIsolation.executePackageAction = + false; + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); + expect(authz.canUnIsolateHost).toBe(false); + }); + }); + }); }); describe('getEndpointAuthzInitialState()', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index 6f578ec24d856..bc40bd11f5d79 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -28,14 +28,18 @@ export const calculateEndpointAuthz = ( const isPlatinumPlusLicense = licenseService.isPlatinumPlus(); const isEnterpriseLicense = licenseService.isEnterprise(); const hasEndpointManagementAccess = userRoles.includes('superuser'); + const canIsolateHost = isEndpointRbacEnabled + ? fleetAuthz.packagePrivileges?.endpoint?.actions?.writeHostIsolation?.executePackageAction || + false + : hasEndpointManagementAccess; return { canAccessFleet: fleetAuthz?.fleet.all ?? userRoles.includes('superuser'), canAccessEndpointManagement: hasEndpointManagementAccess, canCreateArtifactsByPolicy: hasEndpointManagementAccess && isPlatinumPlusLicense, // Response Actions - canIsolateHost: isPlatinumPlusLicense && hasEndpointManagementAccess, - canUnIsolateHost: hasEndpointManagementAccess, + canIsolateHost: isPlatinumPlusLicense && canIsolateHost, + canUnIsolateHost: canIsolateHost, canKillProcess: hasEndpointManagementAccess && isEnterpriseLicense, canSuspendProcess: hasEndpointManagementAccess && isEnterpriseLicense, canGetRunningProcesses: hasEndpointManagementAccess && isEnterpriseLicense, diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts index 29da5688357b0..a0d23820025b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts @@ -7,15 +7,19 @@ import type { RenderHookResult, RenderResult } from '@testing-library/react-hooks'; import { act, renderHook } from '@testing-library/react-hooks'; -import { useCurrentUser, useKibana } from '../../../lib/kibana'; -import { useEndpointPrivileges } from './use_endpoint_privileges'; + import { securityMock } from '@kbn/security-plugin/public/mocks'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import { createFleetAuthzMock } from '@kbn/fleet-plugin/common'; + +import type { EndpointPrivileges } from '../../../../../common/endpoint/types'; +import { useCurrentUser, useKibana } from '../../../lib/kibana'; import { licenseService } from '../../../hooks/use_license'; +import { useEndpointPrivileges } from './use_endpoint_privileges'; import { getEndpointPrivilegesInitialStateMock } from './mocks'; -import type { EndpointPrivileges } from '../../../../../common/endpoint/types'; import { getEndpointPrivilegesInitialState } from './utils'; +const useKibanaMock = useKibana as jest.Mocked; jest.mock('../../../lib/kibana'); jest.mock('../../../hooks/use_license', () => { const licenseServiceInstance = { @@ -47,6 +51,7 @@ describe('When using useEndpointPrivileges hook', () => { }); (useCurrentUser as jest.Mock).mockReturnValue(authenticatedUser); + useKibanaMock().services.fleet!.authz = createFleetAuthzMock(); licenseServiceMock.isPlatinumPlus.mockReturnValue(true); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 6fc8a99ee7320..35fe96d94d91f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -14,6 +14,9 @@ import { act } from '@testing-library/react'; import { endpointPageHttpMock } from '../../../mocks'; import { fireEvent } from '@testing-library/dom'; import { licenseService } from '../../../../../../common/hooks/use_license'; +import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; +import { initialUserPrivilegesState } from '../../../../../../common/components/user_privileges/user_privileges_context'; +import { getUserPrivilegesMockDefaultValue } from '../../../../../../common/components/user_privileges/__mocks__'; jest.mock('../../../../../../common/lib/kibana/kibana_react', () => { const originalModule = jest.requireActual('../../../../../../common/lib/kibana/kibana_react'); @@ -31,6 +34,7 @@ jest.mock('../../../../../../common/lib/kibana/kibana_react', () => { }; }); jest.mock('../../../../../../common/hooks/use_license'); +jest.mock('../../../../../../common/components/user_privileges'); describe('When using the Endpoint Details Actions Menu', () => { let render: () => Promise>; @@ -59,6 +63,8 @@ describe('When using the Endpoint Details Actions Menu', () => { waitForAction = mockedContext.middlewareSpy.waitForAction; httpMocks = endpointPageHttpMock(mockedContext.coreStart.http); + (useUserPrivileges as jest.Mock).mockReturnValue(getUserPrivilegesMockDefaultValue()); + act(() => { mockedContext.history.push( '/administration/endpoints?selected_endpoint=5fe11314-678c-413e-87a2-b4a3461878ee' @@ -80,6 +86,10 @@ describe('When using the Endpoint Details Actions Menu', () => { }; }); + afterEach(() => { + (useUserPrivileges as jest.Mock).mockClear(); + }); + it('should not show the response actions history link', async () => { await render(); expect(renderResult.queryByTestId('actionsLink')).toBeNull(); @@ -121,18 +131,38 @@ describe('When using the Endpoint Details Actions Menu', () => { describe('and endpoint host is isolated', () => { beforeEach(() => setEndpointMetadataResponse(true)); - it('should display Unisolate action', async () => { - await render(); - expect(renderResult.getByTestId('unIsolateLink')).not.toBeNull(); + describe('and user has unisolate privilege', () => { + it('should display Unisolate action', async () => { + await render(); + expect(renderResult.getByTestId('unIsolateLink')).not.toBeNull(); + }); + + it('should navigate via router when unisolate is clicked', async () => { + await render(); + act(() => { + fireEvent.click(renderResult.getByTestId('unIsolateLink')); + }); + + expect(coreStart.application.navigateToApp).toHaveBeenCalled(); + }); }); - it('should navigate via router when unisolate is clicked', async () => { - await render(); - act(() => { - fireEvent.click(renderResult.getByTestId('unIsolateLink')); + describe('and user does not have unisolate privilege', () => { + beforeEach(() => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + ...initialUserPrivilegesState().endpointPrivileges, + canIsolateHost: false, + canUnIsolateHost: false, + }, + }); }); - expect(coreStart.application.navigateToApp).toHaveBeenCalled(); + it('should not display unisolate action', async () => { + await render(); + expect(renderResult.queryByTestId('unIsolateLink')).toBeNull(); + }); }); }); @@ -143,12 +173,6 @@ describe('When using the Endpoint Details Actions Menu', () => { afterEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(true)); - it('should not show the `isolate` action', async () => { - setEndpointMetadataResponse(); - await render(); - expect(renderResult.queryByTestId('isolateLink')).toBeNull(); - }); - it('should still show `unisolate` action for endpoints that are currently isolated', async () => { setEndpointMetadataResponse(true); await render(); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 40fd81c4ab587..9a9c884a3979a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -19,7 +19,6 @@ import { agentPolicies, uiQueryParams } from '../../store/selectors'; import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; import type { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router'; import { isEndpointHostIsolated } from '../../../../../common/utils/validators'; -import { useLicense } from '../../../../../common/hooks/use_license'; import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils'; import { useDoesEndpointSupportResponder } from '../../../../../common/hooks/endpoint/use_does_endpoint_support_responder'; import { UPGRADE_ENDPOINT_FOR_RESPONDER } from '../../../../../common/translations'; @@ -36,7 +35,6 @@ export const useEndpointActionItems = ( endpointMetadata: MaybeImmutable | undefined, options?: Options ): ContextMenuItemNavByRouterProps[] => { - const isPlatinumPlus = useLicense().isPlatinumPlus(); const { getAppUrl } = useAppUrl(); const fleetAgentPolicies = useEndpointSelector(agentPolicies); const allCurrentUrlParams = useEndpointSelector(uiQueryParams); @@ -44,7 +42,8 @@ export const useEndpointActionItems = ( const isResponseActionsConsoleEnabled = useIsExperimentalFeatureEnabled( 'responseActionsConsoleEnabled' ); - const canAccessResponseConsole = useUserPrivileges().endpointPrivileges.canAccessResponseConsole; + const { canAccessResponseConsole, canIsolateHost, canUnIsolateHost } = + useUserPrivileges().endpointPrivileges; const isResponderCapabilitiesEnabled = useDoesEndpointSupportResponder(endpointMetadata); return useMemo(() => { @@ -82,8 +81,8 @@ export const useEndpointActionItems = ( const isolationActions = []; - if (isIsolated) { - // Un-isolate is always available to users regardless of license level + if (isIsolated && canUnIsolateHost) { + // Un-isolate is available to users regardless of license level if they have unisolate permissions isolationActions.push({ 'data-test-subj': 'unIsolateLink', icon: 'lockOpen', @@ -100,7 +99,7 @@ export const useEndpointActionItems = ( /> ), }); - } else if (isPlatinumPlus && isolationSupported) { + } else if (isolationSupported && canIsolateHost) { // For Platinum++ licenses, users also have ability to isolate isolationActions.push({ 'data-test-subj': 'isolateLink', @@ -260,10 +259,11 @@ export const useEndpointActionItems = ( endpointMetadata, fleetAgentPolicies, getAppUrl, - isPlatinumPlus, isResponseActionsConsoleEnabled, showEndpointResponseActionsConsole, options?.isEndpointList, isResponderCapabilitiesEnabled, + canIsolateHost, + canUnIsolateHost, ]); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index b85ad2cc7f6a5..7d8d625d97e39 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -1007,6 +1007,7 @@ describe('when on the endpoint list page', () => { let agentId: string; let agentPolicyId: string; let renderResult: ReturnType; + let endpointActionsButton: HTMLElement; // 2nd endpoint only has isolation capabilities const mockEndpointListApi = () => { @@ -1081,13 +1082,7 @@ describe('when on the endpoint list page', () => { beforeEach(async () => { mockEndpointListApi(); - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { - ...mockInitialUserPrivilegesState().endpointPrivileges, - canAccessResponseConsole: true, - }, - }); + (useUserPrivileges as jest.Mock).mockReturnValue(getUserPrivilegesMockDefaultValue()); reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/endpoints`); @@ -1097,9 +1092,7 @@ describe('when on the endpoint list page', () => { await middlewareSpy.waitForAction('serverReturnedEndpointList'); await middlewareSpy.waitForAction('serverReturnedEndpointAgentPolicies'); - const endpointActionsButton = ( - await renderResult.findAllByTestId('endpointTableRowActions') - )[0]; + endpointActionsButton = (await renderResult.findAllByTestId('endpointTableRowActions'))[0]; reactTestingLibrary.act(() => { reactTestingLibrary.fireEvent.click(endpointActionsButton); @@ -1108,7 +1101,6 @@ describe('when on the endpoint list page', () => { afterEach(() => { jest.clearAllMocks(); - (useUserPrivileges as jest.Mock).mockReturnValue(getUserPrivilegesMockDefaultValue()); }); it('shows the Responder option when all 3 processes capabilities are present in the endpoint', async () => { @@ -1141,6 +1133,24 @@ describe('when on the endpoint list page', () => { ); }); + it('hides isolate host option if canIsolateHost is false', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...mockInitialUserPrivilegesState(), + endpointPrivileges: { + ...mockInitialUserPrivilegesState().endpointPrivileges, + canIsolateHost: false, + }, + }); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(endpointActionsButton); + }); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(endpointActionsButton); + }); + const isolateLink = screen.queryByTestId('isolateLink'); + expect(isolateLink).toBeNull(); + }); + it('navigates to the Security Solution Host Details page', async () => { const hostLink = await renderResult.findByTestId('hostLink'); expect(hostLink.getAttribute('href')).toEqual( From cc9f1c640962cc55cbd9181fd4ecd1d9499a0fa7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 27 Sep 2022 17:53:37 -0600 Subject: [PATCH 102/172] [Maps] fix Go To - lat/long values outside expected range cause blank Maps app (#141873) * [Maps] fix Go To - lat/long values outside expected range cause blank Maps app * UTM form * wire UTM form * add unit tests * fix test names * fix expects * fix functional tests * review feedback --- .../set_view_control/decimal_degrees_form.tsx | 149 ++++ .../set_view_control/mgrs_form.tsx | 149 ++++ .../set_view_control/number_form_row.tsx | 6 + .../set_view_control/set_view_control.tsx | 733 +----------------- .../set_view_control/set_view_form.tsx | 134 ++++ .../set_view_control/utils.test.ts | 52 ++ .../toolbar_overlay/set_view_control/utils.ts | 91 +++ .../set_view_control/utm_form.tsx | 209 +++++ 8 files changed, 804 insertions(+), 719 deletions(-) create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/decimal_degrees_form.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/mgrs_form.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/number_form_row.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.test.ts create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.ts create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utm_form.tsx diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/decimal_degrees_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/decimal_degrees_form.tsx new file mode 100644 index 0000000000000..03b23e409b770 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/decimal_degrees_form.tsx @@ -0,0 +1,149 @@ +/* + * 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, { ChangeEvent, Component } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiButton, + EuiFieldNumber, + EuiTextAlign, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; +import { withinRange } from './utils'; + +interface Props { + settings: MapSettings; + zoom: number; + center: MapCenter; + onSubmit: (lat: number, lon: number, zoom: number) => void; +} + +interface State { + lat: number | string; + lon: number | string; + zoom: number | string; +} + +export class DecimalDegreesForm extends Component { + state: State = { + lat: this.props.center.lat, + lon: this.props.center.lon, + zoom: this.props.zoom, + }; + + _onLatChange = (evt: ChangeEvent) => { + const sanitizedValue = parseFloat(evt.target.value); + this.setState({ + lat: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + }; + + _onLonChange = (evt: ChangeEvent) => { + const sanitizedValue = parseFloat(evt.target.value); + this.setState({ + lon: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + }; + + _onZoomChange = (evt: ChangeEvent) => { + const sanitizedValue = parseFloat(evt.target.value); + this.setState({ + zoom: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + }; + + _onSubmit = () => { + const { lat, lon, zoom } = this.state; + this.props.onSubmit(lat as number, lon as number, zoom as number); + }; + + render() { + const { isInvalid: isLatInvalid, error: latError } = withinRange(this.state.lat, -90, 90); + const { isInvalid: isLonInvalid, error: lonError } = withinRange(this.state.lon, -180, 180); + const { isInvalid: isZoomInvalid, error: zoomError } = withinRange( + this.state.zoom, + this.props.settings.minZoom, + this.props.settings.maxZoom + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/mgrs_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/mgrs_form.tsx new file mode 100644 index 0000000000000..48455f89b7464 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/mgrs_form.tsx @@ -0,0 +1,149 @@ +/* + * 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 _ from 'lodash'; +import React, { ChangeEvent, Component } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiButton, + EuiFieldNumber, + EuiFieldText, + EuiTextAlign, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; +import { ddToMGRS, mgrsToDD, withinRange } from './utils'; + +interface Props { + settings: MapSettings; + zoom: number; + center: MapCenter; + onSubmit: (lat: number, lon: number, zoom: number) => void; +} + +interface State { + mgrs: string; + zoom: number | string; +} + +export class MgrsForm extends Component { + state: State = { + mgrs: ddToMGRS(this.props.center.lat, this.props.center.lon), + zoom: this.props.zoom, + }; + + _toPoint() { + return this.state.mgrs === '' ? undefined : mgrsToDD(this.state.mgrs); + } + + _isMgrsInvalid() { + const point = this._toPoint(); + return ( + point === undefined || + !point.north || + _.isNaN(point.north) || + !point.south || + _.isNaN(point.south) || + !point.east || + _.isNaN(point.east) || + !point.west || + _.isNaN(point.west) + ); + } + + _onMGRSChange = (evt: ChangeEvent) => { + this.setState({ + mgrs: _.isNull(evt.target.value) ? '' : evt.target.value, + }); + }; + + _onZoomChange = (evt: ChangeEvent) => { + const sanitizedValue = parseFloat(evt.target.value); + this.setState({ + zoom: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + }; + + _onSubmit = () => { + const point = this._toPoint(); + if (point) { + this.props.onSubmit(point.north, point.east, this.state.zoom as number); + } + }; + + render() { + const isMgrsInvalid = this._isMgrsInvalid(); + const mgrsError = isMgrsInvalid + ? i18n.translate('xpack.maps.setViewControl.mgrsInvalid', { + defaultMessage: 'MGRS is invalid', + }) + : null; + const { isInvalid: isZoomInvalid, error: zoomError } = withinRange( + this.state.zoom, + this.props.settings.minZoom, + this.props.settings.maxZoom + ); + + return ( + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/number_form_row.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/number_form_row.tsx new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/number_form_row.tsx @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx index eb0b1cfc1ddaf..906cbb8a0bb00 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx @@ -5,50 +5,11 @@ * 2.0. */ -import React, { ChangeEvent, Component, Fragment } from 'react'; -import { - EuiForm, - EuiFormRow, - EuiButton, - EuiFieldNumber, - EuiFieldText, - EuiButtonIcon, - EuiPopover, - EuiTextAlign, - EuiSpacer, - EuiPanel, -} from '@elastic/eui'; -import { EuiButtonEmpty } from '@elastic/eui'; -import { EuiRadioGroup } from '@elastic/eui'; +import React, { Component } from 'react'; +import { EuiButtonIcon, EuiPopover, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import * as usng from 'usng.js'; -import { isNaN, isNull } from 'lodash'; import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; - -export const COORDINATE_SYSTEM_DEGREES_DECIMAL = 'dd'; -export const COORDINATE_SYSTEM_MGRS = 'mgrs'; -export const COORDINATE_SYSTEM_UTM = 'utm'; - -export const DEFAULT_SET_VIEW_COORDINATE_SYSTEM = COORDINATE_SYSTEM_DEGREES_DECIMAL; - -// @ts-ignore -const converter = new usng.Converter(); - -const COORDINATE_SYSTEMS = [ - { - id: COORDINATE_SYSTEM_DEGREES_DECIMAL, - label: 'Degrees Decimal', - }, - { - id: COORDINATE_SYSTEM_UTM, - label: 'UTM', - }, - { - id: COORDINATE_SYSTEM_MGRS, - label: 'MGRS', - }, -]; +import { SetViewForm } from './set_view_form'; export interface Props { settings: MapSettings; @@ -59,73 +20,17 @@ export interface Props { interface State { isPopoverOpen: boolean; - lat: number | string; - lon: number | string; - zoom: number | string; - coord: string; - mgrs: string; - utm: { - northing: string; - easting: string; - zoneNumber: string; - zoneLetter: string | undefined; - zone: string; - }; - isCoordPopoverOpen: boolean; - prevView: string | undefined; } export class SetViewControl extends Component { state: State = { isPopoverOpen: false, - lat: 0, - lon: 0, - zoom: 0, - coord: DEFAULT_SET_VIEW_COORDINATE_SYSTEM, - mgrs: '', - utm: { - northing: '', - easting: '', - zoneNumber: '', - zoneLetter: '', - zone: '', - }, - isCoordPopoverOpen: false, - prevView: '', }; - static getDerivedStateFromProps(nextProps: Props, prevState: State) { - const nextView = getViewString(nextProps.center.lat, nextProps.center.lon, nextProps.zoom); - - const utm = convertLatLonToUTM(nextProps.center.lat, nextProps.center.lon); - const mgrs = convertLatLonToMGRS(nextProps.center.lat, nextProps.center.lon); - - if (nextView !== prevState.prevView) { - return { - lat: nextProps.center.lat, - lon: nextProps.center.lon, - zoom: nextProps.zoom, - utm, - mgrs, - prevView: nextView, - }; - } - - return null; - } - _togglePopover = () => { - if (this.state.isPopoverOpen) { - this._closePopover(); - return; - } - - this.setState({ - lat: this.props.center.lat, - lon: this.props.center.lon, - zoom: this.props.zoom, - isPopoverOpen: true, - }); + this.setState((prevState) => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); }; _closePopover = () => { @@ -134,567 +39,11 @@ export class SetViewControl extends Component { }); }; - _onCoordinateSystemChange = (coordId: string) => { - this.setState({ - coord: coordId, - }); - }; - - _onLatChange = (evt: ChangeEvent) => { - this._onChange('lat', evt); - }; - - _onLonChange = (evt: ChangeEvent) => { - this._onChange('lon', evt); - }; - - _onZoomChange = (evt: ChangeEvent) => { - const sanitizedValue = parseFloat(evt.target.value); - this.setState({ - ['zoom']: isNaN(sanitizedValue) ? '' : sanitizedValue, - }); - }; - - _onUTMZoneChange = (evt: ChangeEvent) => { - this._onUTMChange('zone', evt); - }; - - _onUTMEastingChange = (evt: ChangeEvent) => { - this._onUTMChange('easting', evt); - }; - - _onUTMNorthingChange = (evt: ChangeEvent) => { - this._onUTMChange('northing', evt); - }; - - _onMGRSChange = (evt: ChangeEvent) => { - this.setState( - { - ['mgrs']: isNull(evt.target.value) ? '' : evt.target.value, - }, - this._syncToMGRS - ); - }; - - _onUTMChange = (name: 'easting' | 'northing' | 'zone', evt: ChangeEvent) => { - const value = evt.target.value; - const updateObj = { ...this.state.utm }; - updateObj[name] = isNull(value) ? '' : value; - if (name === 'zone' && value.length > 0) { - const zoneLetter = value.substring(value.length - 1); - const zoneNumber = value.substring(0, value.length - 1); - updateObj.zoneLetter = isNaN(zoneLetter) ? zoneLetter : ''; - updateObj.zoneNumber = isNaN(zoneNumber) ? '' : zoneNumber; - } - this.setState( - { - // @ts-ignore - ['utm']: updateObj, - }, - this._syncToUTM - ); - }; - - _onChange = (name: 'lat' | 'lon', evt: ChangeEvent) => { - const sanitizedValue = parseFloat(evt.target.value); - - this.setState( - // @ts-ignore - { - [name]: isNaN(sanitizedValue) ? '' : sanitizedValue, - }, - this._syncToLatLon - ); - }; - - /** - * Sync all coordinates to the lat/lon that is set - */ - _syncToLatLon = () => { - if (this.state.lat !== '' && this.state.lon !== '') { - const utm = convertLatLonToUTM(this.state.lat, this.state.lon); - const mgrs = convertLatLonToMGRS(this.state.lat, this.state.lon); - - this.setState({ mgrs, utm }); - } else { - this.setState({ - mgrs: '', - utm: { northing: '', easting: '', zoneNumber: '', zoneLetter: '', zone: '' }, - }); - } - }; - - /** - * Sync the current lat/lon to MGRS that is set - */ - _syncToMGRS = () => { - if (this.state.mgrs !== '') { - let lon; - let lat; - - try { - const { north, east } = convertMGRStoLL(this.state.mgrs); - lat = north; - lon = east; - } catch (err) { - return; - } - - const utm = convertLatLonToUTM(lat, lon); - - this.setState({ - lat: isNaN(lat) ? '' : lat, - lon: isNaN(lon) ? '' : lon, - utm, - }); - } else { - this.setState({ - lat: '', - lon: '', - utm: { northing: '', easting: '', zoneNumber: '', zoneLetter: '', zone: '' }, - }); - } - }; - - /** - * Sync the current lat/lon to UTM that is set - */ - _syncToUTM = () => { - if (this.state.utm) { - let lat; - let lon; - try { - ({ lat, lon } = converter.UTMtoLL( - this.state.utm.northing, - this.state.utm.easting, - this.state.utm.zoneNumber - )); - } catch (err) { - return; - } - - const mgrs = convertLatLonToMGRS(lat, lon); - - this.setState({ - lat: isNaN(lat) ? '' : lat, - lon: isNaN(lon) ? '' : lon, - mgrs, - }); - } else { - this.setState({ - lat: '', - lon: '', - mgrs: '', - }); - } - }; - - _renderNumberFormRow = ({ - value, - min, - max, - onChange, - label, - dataTestSubj, - }: { - value: string | number; - min: number; - max: number; - onChange: (evt: ChangeEvent) => void; - label: string; - dataTestSubj: string; - }) => { - const isInvalid = value === '' || value > max || value < min; - const error = isInvalid ? `Must be between ${min} and ${max}` : null; - return { - isInvalid, - component: ( - - - - ), - }; - }; - - _renderMGRSFormRow = ({ - value, - onChange, - label, - dataTestSubj, - }: { - value: string; - onChange: (evt: ChangeEvent) => void; - label: string; - dataTestSubj: string; - }) => { - let point; - try { - point = convertMGRStoLL(value); - } catch (err) { - point = undefined; - } - - const isInvalid = - value === '' || - point === undefined || - !point.north || - isNaN(point.north) || - !point.south || - isNaN(point.south) || - !point.east || - isNaN(point.east) || - !point.west || - isNaN(point.west); - const error = isInvalid - ? i18n.translate('xpack.maps.setViewControl.mgrsInvalid', { - defaultMessage: 'MGRS is invalid', - }) - : null; - return { - isInvalid, - component: ( - - - - ), - }; - }; - - _renderUTMZoneRow = ({ - value, - onChange, - label, - dataTestSubj, - }: { - value: string | number; - onChange: (evt: ChangeEvent) => void; - label: string; - dataTestSubj: string; - }) => { - let point; - try { - point = converter.UTMtoLL( - this.state.utm.northing, - this.state.utm.easting, - this.state.utm.zoneNumber - ); - } catch { - point = undefined; - } - - const isInvalid = value === '' || point === undefined; - const error = isInvalid - ? i18n.translate('xpack.maps.setViewControl.utmInvalidZone', { - defaultMessage: 'UTM Zone is invalid', - }) - : null; - return { - isInvalid, - component: ( - - - - ), - }; - }; - - _renderUTMEastingRow = ({ - value, - onChange, - label, - dataTestSubj, - }: { - value: string | number; - onChange: (evt: ChangeEvent) => void; - label: string; - dataTestSubj: string; - }) => { - let point; - try { - point = converter.UTMtoLL(this.state.utm.northing, value, this.state.utm.zoneNumber); - } catch { - point = undefined; - } - const isInvalid = value === '' || point === undefined; - const error = isInvalid - ? i18n.translate('xpack.maps.setViewControl.utmInvalidEasting', { - defaultMessage: 'UTM Easting is invalid', - }) - : null; - return { - isInvalid, - component: ( - - - - ), - }; - }; - - _renderUTMNorthingRow = ({ - value, - onChange, - label, - dataTestSubj, - }: { - value: string | number; - onChange: (evt: ChangeEvent) => void; - label: string; - dataTestSubj: string; - }) => { - let point; - try { - point = converter.UTMtoLL(value, this.state.utm.easting, this.state.utm.zoneNumber); - } catch { - point = undefined; - } - const isInvalid = value === '' || point === undefined; - const error = isInvalid - ? i18n.translate('xpack.maps.setViewControl.utmInvalidNorthing', { - defaultMessage: 'UTM Northing is invalid', - }) - : null; - return { - isInvalid, - component: ( - - - - ), - }; - }; - - _onSubmit = () => { - const { lat, lon, zoom } = this.state; + _onSubmit = (lat: number, lon: number, zoom: number) => { this._closePopover(); - this.props.onSubmit({ lat: lat as number, lon: lon as number, zoom: zoom as number }); + this.props.onSubmit({ lat, lon, zoom }); }; - _renderSetViewForm() { - let isLatInvalid; - let latFormRow; - let isLonInvalid; - let lonFormRow; - let isMGRSInvalid; - let mgrsFormRow; - let isUtmZoneInvalid; - let utmZoneRow; - let isUtmEastingInvalid; - let utmEastingRow; - let isUtmNorthingInvalid; - let utmNorthingRow; - - if (this.state.coord === COORDINATE_SYSTEM_DEGREES_DECIMAL) { - const latRenderObject = this._renderNumberFormRow({ - value: this.state.lat, - min: -90, - max: 90, - onChange: this._onLatChange, - label: i18n.translate('xpack.maps.setViewControl.latitudeLabel', { - defaultMessage: 'Latitude', - }), - dataTestSubj: 'latitudeInput', - }); - - isLatInvalid = latRenderObject.isInvalid; - latFormRow = latRenderObject.component; - - const lonRenderObject = this._renderNumberFormRow({ - value: this.state.lon, - min: -180, - max: 180, - onChange: this._onLonChange, - label: i18n.translate('xpack.maps.setViewControl.longitudeLabel', { - defaultMessage: 'Longitude', - }), - dataTestSubj: 'longitudeInput', - }); - - isLonInvalid = lonRenderObject.isInvalid; - lonFormRow = lonRenderObject.component; - } else if (this.state.coord === COORDINATE_SYSTEM_MGRS) { - const mgrsRenderObject = this._renderMGRSFormRow({ - value: this.state.mgrs, - onChange: this._onMGRSChange, - label: i18n.translate('xpack.maps.setViewControl.mgrsLabel', { - defaultMessage: 'MGRS', - }), - dataTestSubj: 'mgrsInput', - }); - - isMGRSInvalid = mgrsRenderObject.isInvalid; - mgrsFormRow = mgrsRenderObject.component; - } else if (this.state.coord === COORDINATE_SYSTEM_UTM) { - const utmZoneRenderObject = this._renderUTMZoneRow({ - value: this.state.utm !== undefined ? this.state.utm.zone : '', - onChange: this._onUTMZoneChange, - label: i18n.translate('xpack.maps.setViewControl.utmZoneLabel', { - defaultMessage: 'UTM Zone', - }), - dataTestSubj: 'utmZoneInput', - }); - - isUtmZoneInvalid = utmZoneRenderObject.isInvalid; - utmZoneRow = utmZoneRenderObject.component; - - const utmEastingRenderObject = this._renderUTMEastingRow({ - value: this.state.utm !== undefined ? this.state.utm.easting : '', - onChange: this._onUTMEastingChange, - label: i18n.translate('xpack.maps.setViewControl.utmEastingLabel', { - defaultMessage: 'UTM Easting', - }), - dataTestSubj: 'utmEastingInput', - }); - - isUtmEastingInvalid = utmEastingRenderObject.isInvalid; - utmEastingRow = utmEastingRenderObject.component; - - const utmNorthingRenderObject = this._renderUTMNorthingRow({ - value: this.state.utm !== undefined ? this.state.utm.northing : '', - onChange: this._onUTMNorthingChange, - label: i18n.translate('xpack.maps.setViewControl.utmNorthingLabel', { - defaultMessage: 'UTM Northing', - }), - dataTestSubj: 'utmNorthingInput', - }); - - isUtmNorthingInvalid = utmNorthingRenderObject.isInvalid; - utmNorthingRow = utmNorthingRenderObject.component; - } - - const { isInvalid: isZoomInvalid, component: zoomFormRow } = this._renderNumberFormRow({ - value: this.state.zoom, - min: this.props.settings.minZoom, - max: this.props.settings.maxZoom, - onChange: this._onZoomChange, - label: i18n.translate('xpack.maps.setViewControl.zoomLabel', { - defaultMessage: 'Zoom', - }), - dataTestSubj: 'zoomInput', - }); - - let coordinateInputs; - if (this.state.coord === 'dd') { - coordinateInputs = ( - - {latFormRow} - {lonFormRow} - {zoomFormRow} - - ); - } else if (this.state.coord === 'dms') { - coordinateInputs = ( - - {latFormRow} - {lonFormRow} - {zoomFormRow} - - ); - } else if (this.state.coord === 'utm') { - coordinateInputs = ( - - {utmZoneRow} - {utmEastingRow} - {utmNorthingRow} - {zoomFormRow} - - ); - } else if (this.state.coord === 'mgrs') { - coordinateInputs = ( - - {mgrsFormRow} - {zoomFormRow} - - ); - } - - return ( - - { - this.setState({ isCoordPopoverOpen: false }); - }} - button={ - { - this.setState({ isCoordPopoverOpen: !this.state.isCoordPopoverOpen }); - }} - > - Coordinate System - - } - > - - - - {coordinateInputs} - - - - - - - - - - ); - } - render() { return ( { isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} > - {this._renderSetViewForm()} + ); } } - -function convertLatLonToUTM(lat: string | number, lon: string | number) { - const utmCoord = converter.LLtoUTM(lat, lon); - - let eastwest = 'E'; - if (utmCoord.easting < 0) { - eastwest = 'W'; - } - let norwest = 'N'; - if (utmCoord.northing < 0) { - norwest = 'S'; - } - - if (utmCoord !== 'undefined') { - utmCoord.zoneLetter = isNaN(lat) ? '' : converter.UTMLetterDesignator(lat); - utmCoord.zone = `${utmCoord.zoneNumber}${utmCoord.zoneLetter}`; - utmCoord.easting = Math.round(utmCoord.easting); - utmCoord.northing = Math.round(utmCoord.northing); - utmCoord.str = `${utmCoord.zoneNumber}${utmCoord.zoneLetter} ${utmCoord.easting}${eastwest} ${utmCoord.northing}${norwest}`; - } - - return utmCoord; -} - -function convertLatLonToMGRS(lat: string | number, lon: string | number) { - const mgrsCoord = converter.LLtoMGRS(lat, lon, 5); - return mgrsCoord; -} - -function getViewString(lat: number, lon: number, zoom: number) { - return `${lat},${lon},${zoom}`; -} - -function convertMGRStoUSNG(mgrs: string) { - let squareIdEastSpace = 0; - for (let i = mgrs.length - 1; i > -1; i--) { - // check if we have hit letters yet - if (isNaN(mgrs.substr(i, 1))) { - squareIdEastSpace = i + 1; - break; - } - } - const gridZoneSquareIdSpace = squareIdEastSpace ? squareIdEastSpace - 2 : -1; - const numPartLength = mgrs.substr(squareIdEastSpace).length / 2; - // add the number split space - const eastNorthSpace = squareIdEastSpace ? squareIdEastSpace + numPartLength : -1; - const stringArray = mgrs.split(''); - - stringArray.splice(eastNorthSpace, 0, ' '); - stringArray.splice(squareIdEastSpace, 0, ' '); - stringArray.splice(gridZoneSquareIdSpace, 0, ' '); - - const rejoinedArray = stringArray.join(''); - return rejoinedArray; -} - -function convertMGRStoLL(mgrs: string) { - return mgrs ? converter.USNGtoLL(convertMGRStoUSNG(mgrs)) : ''; -} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx new file mode 100644 index 0000000000000..28fe6073d7646 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Component } from 'react'; +import { EuiButtonEmpty, EuiPopover, EuiRadioGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; +import { DecimalDegreesForm } from './decimal_degrees_form'; +import { MgrsForm } from './mgrs_form'; +import { UtmForm } from './utm_form'; + +const DEGREES_DECIMAL = 'dd'; +const MGRS = 'mgrs'; +const UTM = 'utm'; + +const COORDINATE_SYSTEM_OPTIONS = [ + { + id: DEGREES_DECIMAL, + label: i18n.translate('xpack.maps.setViewControl.decimalDegreesLabel', { + defaultMessage: 'Decimal degrees', + }), + }, + { + id: UTM, + label: 'UTM', + }, + { + id: MGRS, + label: 'MGRS', + }, +]; + +interface Props { + settings: MapSettings; + zoom: number; + center: MapCenter; + onSubmit: (lat: number, lon: number, zoom: number) => void; +} + +interface State { + isPopoverOpen: boolean; + coordinateSystem: string; +} + +export class SetViewForm extends Component { + state: State = { + coordinateSystem: DEGREES_DECIMAL, + isPopoverOpen: false, + }; + + _togglePopover = () => { + this.setState((prevState) => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + }; + + _closePopover = () => { + this.setState({ + isPopoverOpen: false, + }); + }; + + _onCoordinateSystemChange = (optionId: string) => { + this._closePopover(); + this.setState({ + coordinateSystem: optionId, + }); + }; + + _renderForm() { + if (this.state.coordinateSystem === MGRS) { + return ( + + ); + } + + if (this.state.coordinateSystem === UTM) { + return ( + + ); + } + + return ( + + ); + } + + render() { + return ( +
    + + + + } + > + + + {this._renderForm()} +
    + ); + } +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.test.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.test.ts new file mode 100644 index 0000000000000..e6a6819687d10 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.test.ts @@ -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 { ddToMGRS, mgrsToDD, ddToUTM, utmToDD } from './utils'; + +describe('MGRS', () => { + test('ddToMGRS should convert lat lon to MGRS', () => { + expect(ddToMGRS(29.29926, 32.05495)).toEqual('36RVT08214151'); + }); + + test('ddToMGRS should return empty string for lat lon that does not translate to MGRS grid', () => { + expect(ddToMGRS(90, 32.05495)).toEqual(''); + }); + + test('mgrsToDD should convert MGRS to lat lon', () => { + expect(mgrsToDD('36RVT08214151')).toEqual({ + east: 32.05498649594143, + north: 29.299330195900975, + south: 29.299239224067065, + west: 32.054884373627345, + }); + }); +}); + +describe('UTM', () => { + test('ddToUTM should convert lat lon to UTM', () => { + expect(ddToUTM(29.29926, 32.05495)).toEqual({ + easting: '408216', + northing: '3241512', + zone: '36R', + }); + }); + + test('ddToUTM should return empty strings for lat lon that does not translate to UTM grid', () => { + expect(ddToUTM(90, 32.05495)).toEqual({ + northing: '', + easting: '', + zone: '', + }); + }); + + test('utmToDD should convert UTM to lat lon', () => { + expect(utmToDD('3241512', '408216', '36R')).toEqual({ + lat: 29.29925770984472, + lon: 32.05494597943409, + }); + }); +}); diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.ts new file mode 100644 index 0000000000000..7edf1428d9312 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utils.ts @@ -0,0 +1,91 @@ +/* + * 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 * as usng from 'usng.js'; + +// @ts-ignore +const converter = new usng.Converter(); + +export function withinRange(value: string | number, min: number, max: number) { + const isInvalid = value === '' || value > max || value < min; + const error = isInvalid + ? i18n.translate('xpack.maps.setViewControl.outOfRangeErrorMsg', { + defaultMessage: `Must be between {min} and {max}`, + values: { min, max }, + }) + : null; + return { isInvalid, error }; +} + +export function ddToUTM(lat: number, lon: number) { + try { + const utm = converter.LLtoUTM(lat, lon); + return { + northing: utm === converter.UNDEFINED_STR ? '' : String(Math.round(utm.northing)), + easting: utm === converter.UNDEFINED_STR ? '' : String(Math.round(utm.easting)), + zone: + utm === converter.UNDEFINED_STR + ? '' + : `${utm.zoneNumber}${converter.UTMLetterDesignator(lat)}`, + }; + } catch (e) { + return { + northing: '', + easting: '', + zone: '', + }; + } +} + +export function utmToDD(northing: string, easting: string, zoneNumber: string) { + try { + return converter.UTMtoLL(northing, easting, zoneNumber); + } catch (e) { + return undefined; + } +} + +export function ddToMGRS(lat: number, lon: number) { + try { + const mgrsCoord = converter.LLtoMGRS(lat, lon, 5); + return mgrsCoord; + } catch (e) { + return ''; + } +} + +function mgrstoUSNG(mgrs: string) { + let squareIdEastSpace = 0; + for (let i = mgrs.length - 1; i > -1; i--) { + // check if we have hit letters yet + if (isNaN(parseInt(mgrs.substr(i, 1), 10))) { + squareIdEastSpace = i + 1; + break; + } + } + const gridZoneSquareIdSpace = squareIdEastSpace ? squareIdEastSpace - 2 : -1; + const numPartLength = mgrs.substr(squareIdEastSpace).length / 2; + // add the number split space + const eastNorthSpace = squareIdEastSpace ? squareIdEastSpace + numPartLength : -1; + const stringArray = mgrs.split(''); + + stringArray.splice(eastNorthSpace, 0, ' '); + stringArray.splice(squareIdEastSpace, 0, ' '); + stringArray.splice(gridZoneSquareIdSpace, 0, ' '); + + const rejoinedArray = stringArray.join(''); + return rejoinedArray; +} + +export function mgrsToDD(mgrs: string) { + try { + return converter.USNGtoLL(mgrstoUSNG(mgrs)); + } catch (e) { + return undefined; + } +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utm_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utm_form.tsx new file mode 100644 index 0000000000000..76a3628217056 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/utm_form.tsx @@ -0,0 +1,209 @@ +/* + * 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 _ from 'lodash'; +import React, { ChangeEvent, Component } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiButton, + EuiFieldNumber, + EuiFieldText, + EuiTextAlign, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; +import { ddToUTM, utmToDD, withinRange } from './utils'; + +interface Props { + settings: MapSettings; + zoom: number; + center: MapCenter; + onSubmit: (lat: number, lon: number, zoom: number) => void; +} + +interface State { + northing: string; + easting: string; + zone: string; + zoom: number | string; +} + +export class UtmForm extends Component { + constructor(props: Props) { + super(props); + const utm = ddToUTM(this.props.center.lat, this.props.center.lon); + this.state = { + northing: utm.northing, + easting: utm.easting, + zone: utm.zone, + zoom: this.props.zoom, + }; + } + + _toPoint() { + const { northing, easting, zone } = this.state; + return northing === '' || easting === '' || zone.length < 2 + ? undefined + : utmToDD(northing, easting, zone.substring(0, zone.length - 1)); + } + + _isUtmInvalid() { + const point = this._toPoint(); + return point === undefined; + } + + _onZoneChange = (evt: ChangeEvent) => { + this.setState({ + zone: _.isNull(evt.target.value) ? '' : evt.target.value, + }); + }; + + _onEastingChange = (evt: ChangeEvent) => { + this.setState({ + easting: _.isNull(evt.target.value) ? '' : evt.target.value, + }); + }; + + _onNorthingChange = (evt: ChangeEvent) => { + this.setState({ + northing: _.isNull(evt.target.value) ? '' : evt.target.value, + }); + }; + + _onZoomChange = (evt: ChangeEvent) => { + const sanitizedValue = parseFloat(evt.target.value); + this.setState({ + zoom: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + }; + + _onSubmit = () => { + const point = this._toPoint(); + if (point) { + this.props.onSubmit(point.lat, point.lon, this.state.zoom as number); + } + }; + + render() { + const isUtmInvalid = this._isUtmInvalid(); + const northingError = + isUtmInvalid || this.state.northing === '' + ? i18n.translate('xpack.maps.setViewControl.utmInvalidNorthing', { + defaultMessage: 'UTM Northing is invalid', + }) + : null; + const eastingError = + isUtmInvalid || this.state.northing === '' + ? i18n.translate('xpack.maps.setViewControl.utmInvalidEasting', { + defaultMessage: 'UTM Easting is invalid', + }) + : null; + const zoneError = + isUtmInvalid || this.state.northing === '' + ? i18n.translate('xpack.maps.setViewControl.utmInvalidZone', { + defaultMessage: 'UTM Zone is invalid', + }) + : null; + const { isInvalid: isZoomInvalid, error: zoomError } = withinRange( + this.state.zoom, + this.props.settings.minZoom, + this.props.settings.maxZoom + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} From 427637a3b02a48a347409f77eaa34e4f53541970 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 27 Sep 2022 18:05:56 -0600 Subject: [PATCH 103/172] [RAM] Fix bulk editing snooze schedule overwriting existing snooze (#141883) * Fix odd behaviour with bulk snoozing * Fix redundant code Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/rules_client/rules_client.ts | 52 ++- .../rules_client/tests/bulk_edit.test.ts | 300 ++++++++++++++---- 2 files changed, 283 insertions(+), 69 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index ed330dbd22e08..ce1dfcc1827b6 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1797,7 +1797,7 @@ export class RulesClient { break; } if (operation.operation === 'set') { - const snoozeAttributes = getSnoozeAttributes(attributes, operation.value); + const snoozeAttributes = getBulkSnoozeAttributes(attributes, operation.value); try { verifySnoozeScheduleLimit(snoozeAttributes); } catch (error) { @@ -1819,7 +1819,7 @@ export class RulesClient { } attributes = { ...attributes, - ...getUnsnoozeAttributes(attributes, idsToDelete), + ...getBulkUnsnoozeAttributes(attributes, idsToDelete), }; } break; @@ -3254,6 +3254,33 @@ function getSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSche }; } +function getBulkSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { + // If duration is -1, instead mute all + const { id: snoozeId, duration } = snoozeSchedule; + + if (duration === -1) { + return { + muteAll: true, + snoozeSchedule: clearUnscheduledSnooze(attributes), + }; + } + + // Bulk adding snooze schedule, don't touch the existing snooze/indefinite snooze + if (snoozeId) { + const existingSnoozeSchedules = attributes.snoozeSchedule || []; + return { + muteAll: attributes.muteAll, + snoozeSchedule: [...existingSnoozeSchedules, snoozeSchedule], + }; + } + + // Bulk snoozing, don't touch the existing snooze schedules + return { + muteAll: false, + snoozeSchedule: [...clearUnscheduledSnooze(attributes), snoozeSchedule], + }; +} + function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { const snoozeSchedule = scheduleIds ? clearScheduledSnoozesById(attributes, scheduleIds) @@ -3265,6 +3292,27 @@ function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { }; } +function getBulkUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { + // Bulk removing snooze schedules, don't touch the current snooze/indefinite snooze + if (scheduleIds) { + const newSchedules = clearScheduledSnoozesById(attributes, scheduleIds); + // Unscheduled snooze is also known as snooze now + const unscheduledSnooze = + attributes.snoozeSchedule?.filter((s) => typeof s.id === 'undefined') || []; + + return { + snoozeSchedule: [...unscheduledSnooze, ...newSchedules], + muteAll: attributes.muteAll, + }; + } + + // Bulk unsnoozing, don't touch current snooze schedules that are NOT active + return { + snoozeSchedule: clearCurrentActiveSnooze(attributes), + muteAll: false, + }; +} + function clearUnscheduledSnooze(attributes: RawRule) { // Clear any snoozes that have no ID property. These are "simple" snoozes created with the quick UI, e.g. snooze for 3 days starting now return attributes.snoozeSchedule diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts index 1cafcd652349a..5e440d2e6b6d7 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts @@ -26,9 +26,11 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation })); jest.mock('../../lib/snooze/is_snooze_active', () => ({ - isSnoozeActive: jest.fn(() => true), + isSnoozeActive: jest.fn(), })); +const { isSnoozeActive } = jest.requireMock('../../lib/snooze/is_snooze_active'); + const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -310,9 +312,13 @@ describe('bulkEdit()', () => { }); describe('snoozeSchedule operations', () => { - const getSnoozeSchedule = () => { + afterEach(() => { + isSnoozeActive.mockImplementation(() => false); + }); + + const getSnoozeSchedule = (useId: boolean = true) => { return { - id: uuid.v4(), + ...(useId && { id: uuid.v4() }), duration: 28800000, rRule: { dtstart: '2010-09-19T11:49:59.329Z', @@ -321,8 +327,10 @@ describe('bulkEdit()', () => { }, }; }; - test('should add snooze', async () => { - unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getMockAttribute = (override: Record = {}) => { + return { saved_objects: [ { id: '1', @@ -339,15 +347,137 @@ describe('bulkEdit()', () => { notifyWhen: null, actions: [], snoozeSchedule: [], + ...override, }, references: [], version: '123', }, ], + }; + }; + + test('should snooze', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + const snoozePayload = getSnoozeSchedule(false); + await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + operation: 'set', + field: 'snoozeSchedule', + value: snoozePayload, + }, + ], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + expect.objectContaining({ + id: '1', + type: 'alert', + attributes: expect.objectContaining({ + snoozeSchedule: [snoozePayload], + }), + }), + ], + { overwrite: true } + ); + }); + + test('should add snooze schedule', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + + const snoozePayload = getSnoozeSchedule(); + await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + operation: 'set', + field: 'snoozeSchedule', + value: snoozePayload, + }, + ], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + expect.objectContaining({ + id: '1', + type: 'alert', + attributes: expect.objectContaining({ + snoozeSchedule: [snoozePayload], + }), + }), + ], + { overwrite: true } + ); + }); + + test('should not unsnooze a snoozed rule when bulk adding snooze schedules', async () => { + const existingSnooze = [getSnoozeSchedule(false), getSnoozeSchedule()]; + + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, + attributes: { + ...existingDecryptedRule.attributes, + snoozeSchedule: existingSnooze, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }, + ], }); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + const snoozePayload = getSnoozeSchedule(); + await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + operation: 'set', + field: 'snoozeSchedule', + value: snoozePayload, + }, + ], + }); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + expect.objectContaining({ + id: '1', + type: 'alert', + attributes: expect.objectContaining({ + snoozeSchedule: [...existingSnooze, snoozePayload], + }), + }), + ], + { overwrite: true } + ); + }); + + test('should not unsnooze an indefinitely snoozed rule when bulk adding snooze schedules', async () => { + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, + attributes: { + ...existingDecryptedRule.attributes, + muteAll: true, + snoozeSchedule: [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }, + ], + }); + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + + const snoozePayload = getSnoozeSchedule(); await rulesClient.bulkEdit({ filter: '', operations: [ @@ -366,6 +496,7 @@ describe('bulkEdit()', () => { id: '1', type: 'alert', attributes: expect.objectContaining({ + muteAll: true, snoozeSchedule: [snoozePayload], }), }), @@ -374,39 +505,74 @@ describe('bulkEdit()', () => { ); }); - test('should delete snooze', async () => { - const existingSnooze = [getSnoozeSchedule(), getSnoozeSchedule()]; + test('should unsnooze', async () => { + const existingSnooze = [getSnoozeSchedule(false), getSnoozeSchedule(), getSnoozeSchedule()]; - unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + mockCreatePointInTimeFinderAsInternalUser({ saved_objects: [ { + ...existingDecryptedRule, + attributes: { + ...existingDecryptedRule.attributes, + snoozeSchedule: existingSnooze, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }, + ], + }); + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + + await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + operation: 'delete', + field: 'snoozeSchedule', + }, + ], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + expect.objectContaining({ id: '1', type: 'alert', + attributes: expect.objectContaining({ + snoozeSchedule: [existingSnooze[1], existingSnooze[2]], + }), + }), + ], + { overwrite: true } + ); + }); + + test('should remove snooze schedules', async () => { + const existingSnooze = [getSnoozeSchedule(), getSnoozeSchedule()]; + + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, attributes: { - enabled: true, - tags: ['foo', 'test-1'], - alertTypeId: 'myType', - schedule: { interval: '1m' }, - consumer: 'myApp', - scheduledTaskId: 'task-123', - params: {}, - throttle: null, - notifyWhen: null, - actions: [], + ...existingDecryptedRule.attributes, snoozeSchedule: existingSnooze, - }, - references: [], - version: '123', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, }, ], }); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + await rulesClient.bulkEdit({ filter: '', operations: [ { operation: 'delete', field: 'snoozeSchedule', + value: [], }, ], }); @@ -426,14 +592,8 @@ describe('bulkEdit()', () => { ); }); - test('should error if adding snooze schedule to rule with 5 schedules', async () => { - const existingSnooze = [ - getSnoozeSchedule(), - getSnoozeSchedule(), - getSnoozeSchedule(), - getSnoozeSchedule(), - getSnoozeSchedule(), - ]; + test('should not unsnooze rule when removing snooze schedules', async () => { + const existingSnooze = [getSnoozeSchedule(false), getSnoozeSchedule(), getSnoozeSchedule()]; mockCreatePointInTimeFinderAsInternalUser({ saved_objects: [ @@ -448,30 +608,58 @@ describe('bulkEdit()', () => { ], }); - unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [ + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + + await rulesClient.bulkEdit({ + filter: '', + operations: [ { + operation: 'delete', + field: 'snoozeSchedule', + value: [], + }, + ], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + expect.objectContaining({ id: '1', type: 'alert', + attributes: expect.objectContaining({ + snoozeSchedule: [existingSnooze[0]], + }), + }), + ], + { overwrite: true } + ); + }); + + test('should error if adding snooze schedule to rule with 5 schedules', async () => { + const existingSnooze = [ + getSnoozeSchedule(), + getSnoozeSchedule(), + getSnoozeSchedule(), + getSnoozeSchedule(), + getSnoozeSchedule(), + ]; + + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, attributes: { - enabled: true, - tags: ['foo', 'test-1'], - alertTypeId: 'myType', - schedule: { interval: '1m' }, - consumer: 'myApp', - scheduledTaskId: 'task-123', - params: {}, - throttle: null, - notifyWhen: null, - actions: [], + ...existingDecryptedRule.attributes, snoozeSchedule: existingSnooze, - }, - references: [], - version: '123', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, }, ], }); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); + const snoozePayload = getSnoozeSchedule(); const response = await rulesClient.bulkEdit({ @@ -501,29 +689,7 @@ describe('bulkEdit()', () => { ], }); - unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [ - { - id: '1', - type: 'alert', - attributes: { - enabled: true, - tags: ['foo', 'test-1'], - alertTypeId: 'myType', - schedule: { interval: '1m' }, - consumer: 'myApp', - scheduledTaskId: 'task-123', - params: {}, - throttle: null, - notifyWhen: null, - actions: [], - snoozeSchedule: [], - }, - references: [], - version: '123', - }, - ], - }); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue(getMockAttribute()); const snoozePayload = getSnoozeSchedule(); From a8e8f3f4c58f1caa0165a2cb1669729c8b177736 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 27 Sep 2022 20:19:39 -0400 Subject: [PATCH 104/172] fix filter on kuery node (#142001) --- .../alerting/server/rules_client/rules_client.ts | 2 +- .../server/rules_client/tests/find.test.ts | 6 ++++-- .../spaces_only/tests/alerting/find.ts | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index ce1dfcc1827b6..a8367d9ff52b4 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1110,7 +1110,7 @@ export class RulesClient { filter: (authorizationFilter && filterKueryNode ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) - : authorizationFilter) ?? options.filter, + : authorizationFilter) ?? filterKueryNode, fields: fields ? this.includeFieldsRequiredForAuthentication(fields) : fields, type: 'alert', }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index e26004de99a31..4cfa13f69b84d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -176,7 +176,7 @@ describe('find()', () => { Array [ Object { "fields": undefined, - "filter": undefined, + "filter": null, "sortField": undefined, "type": "alert", }, @@ -277,7 +277,7 @@ describe('find()', () => { Array [ Object { "fields": undefined, - "filter": undefined, + "filter": null, "sortField": undefined, "type": "alert", }, @@ -730,6 +730,8 @@ describe('find()', () => { expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledWith({ fields: ['tags', 'alertTypeId', 'consumer'], + filter: null, + sortField: undefined, type: 'alert', }); expect(ensureRuleTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'rule'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 2d30465401caa..23dcc1abaea44 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { SuperTest, Test } from 'supertest'; +import { fromKueryExpression } from '@kbn/es-query'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -198,6 +199,20 @@ const findTestUtils = ( expect(response.body.data[0].params.strValue).to.eql('my b'); }); + it('should filter on kueryNode parameters', async () => { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/${ + describeType === 'public' ? 'api' : 'internal' + }/alerting/rules/_find?filter=${JSON.stringify( + fromKueryExpression('alert.attributes.params.strValue:"my b"') + )}` + ); + + expect(response.status).to.eql(200); + expect(response.body.total).to.equal(1); + expect(response.body.data[0].params.strValue).to.eql('my b'); + }); + it('should sort by parameters', async () => { const response = await supertest.get( `${getUrlPrefix(Spaces.space1.id)}/${ From ace4f42f598a2e8657c2edcfd173d2b4b0de5529 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:08:28 -0700 Subject: [PATCH 105/172] [RAM] Fix bulk snooze select all filter not matching rules list filter (#141996) * Select all filter now correctly match the rules list filter * Improve filter kuery node logic * unit tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/rules_client/rules_client.ts | 11 +- .../update_api_key_modal_confirmation.tsx | 9 +- .../hooks/use_bulk_edit_select.test.tsx | 131 ++++++++++++++++++ .../hooks/use_bulk_edit_select.tsx | 68 ++++++++- .../public/application/lib/rule_api/snooze.ts | 5 +- .../application/lib/rule_api/unsnooze.ts | 5 +- .../lib/rule_api/update_api_key.ts | 5 +- .../rule_quick_edit_buttons.test.tsx | 10 +- .../components/rule_quick_edit_buttons.tsx | 13 +- .../components/bulk_snooze_modal.tsx | 9 +- .../components/bulk_snooze_schedule_modal.tsx | 9 +- .../rules_list/components/rules_list.tsx | 38 +++-- 12 files changed, 259 insertions(+), 54 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index a8367d9ff52b4..c2a643ce8c296 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -22,7 +22,7 @@ import { } from 'lodash'; import { i18n } from '@kbn/i18n'; import { AlertConsumers } from '@kbn/rule-data-utils'; -import { fromKueryExpression, KueryNode, nodeBuilder } from '@kbn/es-query'; +import { KueryNode, nodeBuilder } from '@kbn/es-query'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Logger, @@ -1569,14 +1569,7 @@ export class RulesClient { ); } - let qNodeQueryFilter: null | KueryNode; - if (!queryFilter) { - qNodeQueryFilter = null; - } else if (typeof queryFilter === 'string') { - qNodeQueryFilter = fromKueryExpression(queryFilter); - } else { - qNodeQueryFilter = queryFilter; - } + const qNodeQueryFilter = buildKueryNodeFilter(queryFilter); const qNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : qNodeQueryFilter; let authorizationTuple; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.tsx index 0c6f3edaf3b89..c3072c123ff82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.tsx @@ -6,6 +6,7 @@ */ import { EuiConfirmModal } from '@elastic/eui'; +import { KueryNode } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState, useMemo } from 'react'; import { HttpSetup } from '@kbn/core/public'; @@ -25,7 +26,7 @@ export const UpdateApiKeyModalConfirmation = ({ }: { onCancel: () => void; idsToUpdate: string[]; - idsToUpdateFilter?: string; + idsToUpdateFilter?: KueryNode | null | undefined; numberOfSelectedRules?: number; apiUpdateApiKeyCall: ({ ids, @@ -33,7 +34,7 @@ export const UpdateApiKeyModalConfirmation = ({ filter, }: { ids?: string[]; - filter?: string; + filter?: KueryNode | null | undefined; http: HttpSetup; }) => Promise; setIsLoadingState: (isLoading: boolean) => void; @@ -50,7 +51,7 @@ export const UpdateApiKeyModalConfirmation = ({ const { showToast } = useBulkEditResponse({ onSearchPopulate }); useEffect(() => { - if (idsToUpdateFilter) { + if (typeof idsToUpdateFilter !== 'undefined') { setUpdateModalVisibility(true); } else { setUpdateModalVisibility(idsToUpdate.length > 0); @@ -58,7 +59,7 @@ export const UpdateApiKeyModalConfirmation = ({ }, [idsToUpdate, idsToUpdateFilter]); const numberOfIdsToUpdate = useMemo(() => { - if (idsToUpdateFilter) { + if (typeof idsToUpdateFilter !== 'undefined') { return numberOfSelectedRules; } return idsToUpdate.length; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx new file mode 100644 index 0000000000000..b0e92a3d3a848 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx @@ -0,0 +1,131 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useBulkEditSelect } from './use_bulk_edit_select'; +import { RuleTableItem } from '../../types'; + +const items = [ + { + id: '1', + isEditable: true, + }, + { + id: '2', + isEditable: true, + }, + { + id: '3', + isEditable: true, + }, + { + id: '4', + isEditable: true, + }, +] as RuleTableItem[]; + +describe('useBulkEditSelectTest', () => { + it('getFilter should return null when nothing is selected', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + }) + ); + + expect(result.current.getFilter()).toEqual(null); + }); + + it('getFilter should return rule list filter when nothing is selected', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + tagsFilter: ['test: 123'], + searchText: 'rules*', + }) + ); + + expect(result.current.getFilter()?.arguments.length).toEqual(2); + }); + + it('getFilter should return rule list filter when something is selected', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + tagsFilter: ['test: 123'], + searchText: 'rules*', + }) + ); + + act(() => { + result.current.onSelectRow(items[0]); + }); + + expect(result.current.getFilter()?.arguments.length).toEqual(2); + expect([...result.current.selectedIds]).toEqual([items[0].id]); + }); + + it('getFilter should return null when selecting all', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + }) + ); + + act(() => { + result.current.onSelectAll(); + }); + + expect(result.current.getFilter()).toEqual(null); + }); + + it('getFilter should return rule list filter when selecting all', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + tagsFilter: ['test: 123'], + searchText: 'rules*', + }) + ); + + act(() => { + result.current.onSelectAll(); + }); + + expect(result.current.getFilter()?.arguments.length).toEqual(2); + }); + + it('getFilter should return rule list filter and exclude ids when selecting all with excluded ids', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + tagsFilter: ['test: 123'], + searchText: 'rules*', + }) + ); + + act(() => { + result.current.onSelectAll(); + result.current.onSelectRow(items[0]); + }); + + expect(result.current.getFilter()?.arguments.length).toEqual(2); + expect(result.current.getFilter()?.arguments[1].arguments[0].arguments).toEqual([ + expect.objectContaining({ + value: 'alert.id', + }), + expect.objectContaining({ + value: 'alert:1', + }), + ]); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx index 20950d35b0264..1e2ff7496907c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx @@ -5,7 +5,9 @@ * 2.0. */ import { useReducer, useMemo, useCallback } from 'react'; -import { RuleTableItem } from '../../types'; +import { fromKueryExpression, nodeBuilder } from '@kbn/es-query'; +import { mapFiltersToKueryNode } from '../lib/rule_api/map_filters_to_kuery_node'; +import { RuleTableItem, RuleStatus } from '../../types'; interface BulkEditSelectionState { selectedIds: Set; @@ -71,9 +73,26 @@ const reducer = (state: BulkEditSelectionState, action: Action) => { interface UseBulkEditSelectProps { totalItemCount: number; items: RuleTableItem[]; + typesFilter?: string[]; + actionTypesFilter?: string[]; + tagsFilter?: string[]; + ruleExecutionStatusesFilter?: string[]; + ruleStatusesFilter?: RuleStatus[]; + searchText?: string; } -export function useBulkEditSelect({ totalItemCount = 0, items = [] }: UseBulkEditSelectProps) { +export function useBulkEditSelect(props: UseBulkEditSelectProps) { + const { + totalItemCount = 0, + items = [], + typesFilter, + actionTypesFilter, + tagsFilter, + ruleExecutionStatusesFilter, + ruleStatusesFilter, + searchText, + } = props; + const [state, dispatch] = useReducer(reducer, initialState); const itemIds = useMemo(() => { @@ -161,18 +180,55 @@ export function useBulkEditSelect({ totalItemCount = 0, items = [] }: UseBulkEdi dispatch({ type: ActionTypes.CLEAR_SELECTION }); }, []); + const getFilterKueryNode = useCallback( + (idsToExclude?: string[]) => { + const ruleFilterKueryNode = mapFiltersToKueryNode({ + typesFilter, + actionTypesFilter, + tagsFilter, + ruleExecutionStatusesFilter, + ruleStatusesFilter, + searchText, + }); + + if (idsToExclude && idsToExclude.length) { + if (ruleFilterKueryNode) { + return nodeBuilder.and([ + ruleFilterKueryNode, + fromKueryExpression( + `NOT (${idsToExclude.map((id) => `alert.id: "alert:${id}"`).join(' or ')})` + ), + ]); + } + } + + return ruleFilterKueryNode; + }, + [ + typesFilter, + actionTypesFilter, + tagsFilter, + ruleExecutionStatusesFilter, + ruleStatusesFilter, + searchText, + ] + ); + const getFilter = useCallback(() => { const { selectedIds, isAllSelected } = state; const idsArray = [...selectedIds]; if (isAllSelected) { + // Select all but nothing is selected to exclude if (idsArray.length === 0) { - return 'alert.id: *'; + return getFilterKueryNode(); } - return `NOT (${idsArray.map((id) => `alert.id: "alert:${id}"`).join(' or ')})`; + // Select all, exclude certain alerts + return getFilterKueryNode(idsArray); } - return ''; - }, [state]); + + return getFilterKueryNode(); + }, [state, getFilterKueryNode]); return useMemo(() => { return { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/snooze.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/snooze.ts index 1f020aff106f5..b82efdb5e09cf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/snooze.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/snooze.ts @@ -5,6 +5,7 @@ * 2.0. */ import { HttpSetup } from '@kbn/core/public'; +import { KueryNode } from '@kbn/es-query'; import { SnoozeSchedule, BulkEditResponse } from '../../../types'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; @@ -37,7 +38,7 @@ export async function snoozeRule({ export interface BulkSnoozeRulesProps { ids?: string[]; - filter?: string; + filter?: KueryNode | null | undefined; snoozeSchedule: SnoozeSchedule; } @@ -51,7 +52,7 @@ export function bulkSnoozeRules({ try { body = JSON.stringify({ ids: ids?.length ? ids : undefined, - filter, + ...(filter ? { filter: JSON.stringify(filter) } : {}), operations: [ { operation: 'set', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unsnooze.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unsnooze.ts index 7bfadf1e1b824..d055149fdfbd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unsnooze.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unsnooze.ts @@ -5,6 +5,7 @@ * 2.0. */ import { HttpSetup } from '@kbn/core/public'; +import { KueryNode } from '@kbn/es-query'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { BulkEditResponse } from '../../../types'; @@ -26,7 +27,7 @@ export async function unsnoozeRule({ export interface BulkUnsnoozeRulesProps { ids?: string[]; - filter?: string; + filter?: KueryNode | null | undefined; scheduleIds?: string[]; } @@ -40,7 +41,7 @@ export function bulkUnsnoozeRules({ try { body = JSON.stringify({ ids: ids?.length ? ids : undefined, - filter, + ...(filter ? { filter: JSON.stringify(filter) } : {}), operations: [ { operation: 'delete', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_api_key.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_api_key.ts index 53327bbdb1e1e..f9a6912c1d43f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_api_key.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_api_key.ts @@ -5,6 +5,7 @@ * 2.0. */ import { HttpSetup } from '@kbn/core/public'; +import { KueryNode } from '@kbn/es-query'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { BulkEditResponse } from '../../../types'; @@ -16,7 +17,7 @@ export async function updateAPIKey({ id, http }: { id: string; http: HttpSetup } export interface BulkUpdateAPIKeyProps { ids?: string[]; - filter?: string; + filter?: KueryNode | null | undefined; } export function bulkUpdateAPIKey({ @@ -28,7 +29,7 @@ export function bulkUpdateAPIKey({ try { body = JSON.stringify({ ids: ids?.length ? ids : undefined, - filter, + ...(filter ? { filter: JSON.stringify(filter) } : {}), operations: [ { operation: 'set', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx index 1605f09ebc0f5..fcf1ba99b7af4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.test.tsx @@ -43,7 +43,7 @@ describe('rule_quick_edit_buttons', () => { const wrapper = mountWithIntl( ''} + getFilter={() => null} selectedItems={[mockRule]} onPerformingAction={() => {}} onActionPerformed={() => {}} @@ -80,7 +80,7 @@ describe('rule_quick_edit_buttons', () => { const wrapper = mountWithIntl( ''} + getFilter={() => null} selectedItems={[mockRule]} onPerformingAction={() => {}} onActionPerformed={() => {}} @@ -111,7 +111,7 @@ describe('rule_quick_edit_buttons', () => { const wrapper = mountWithIntl( ''} + getFilter={() => null} selectedItems={[mockRule]} onPerformingAction={() => {}} onActionPerformed={() => {}} @@ -152,7 +152,7 @@ describe('rule_quick_edit_buttons', () => { const wrapper = mountWithIntl( ''} + getFilter={() => null} selectedItems={[mockRule]} onPerformingAction={() => {}} onActionPerformed={() => {}} @@ -202,7 +202,7 @@ describe('rule_quick_edit_buttons', () => { const wrapper = mountWithIntl( ''} + getFilter={() => null} selectedItems={[mockRule]} onPerformingAction={() => {}} onActionPerformed={() => {}} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx index c91c461993a54..f3cbae535d777 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { KueryNode } from '@kbn/es-query'; import React, { useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup, EuiIconTip } from '@elastic/eui'; @@ -21,7 +22,7 @@ import { useKibana } from '../../../../common/lib/kibana'; export type ComponentOpts = { selectedItems: RuleTableItem[]; isAllSelected?: boolean; - getFilter: () => string; + getFilter: () => KueryNode | null; onPerformingAction?: () => void; onActionPerformed?: () => void; isSnoozingRules?: boolean; @@ -35,11 +36,11 @@ export type ComponentOpts = { setRulesToUnsnooze: React.Dispatch>; setRulesToSchedule: React.Dispatch>; setRulesToUnschedule: React.Dispatch>; - setRulesToSnoozeFilter: React.Dispatch>; - setRulesToUnsnoozeFilter: React.Dispatch>; - setRulesToScheduleFilter: React.Dispatch>; - setRulesToUnscheduleFilter: React.Dispatch>; - setRulesToUpdateAPIKeyFilter: React.Dispatch>; + setRulesToSnoozeFilter: React.Dispatch>; + setRulesToUnsnoozeFilter: React.Dispatch>; + setRulesToScheduleFilter: React.Dispatch>; + setRulesToUnscheduleFilter: React.Dispatch>; + setRulesToUpdateAPIKeyFilter: React.Dispatch>; } & BulkOperationsComponentOpts; const ButtonWithTooltip = ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_modal.tsx index bbdcfae785c95..95363c5745b43 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_modal.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { KueryNode } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, @@ -30,8 +31,8 @@ import { useKibana } from '../../../../common/lib/kibana'; export type BulkSnoozeModalProps = { rulesToSnooze: RuleTableItem[]; rulesToUnsnooze: RuleTableItem[]; - rulesToSnoozeFilter?: string; - rulesToUnsnoozeFilter?: string; + rulesToSnoozeFilter?: KueryNode | null | undefined; + rulesToUnsnoozeFilter?: KueryNode | null | undefined; numberOfSelectedRules?: number; onClose: () => void; onSave: () => void; @@ -82,14 +83,14 @@ export const BulkSnoozeModal = (props: BulkSnoozeModalProps) => { const { showToast } = useBulkEditResponse({ onSearchPopulate }); const isSnoozeModalOpen = useMemo(() => { - if (rulesToSnoozeFilter) { + if (typeof rulesToSnoozeFilter !== 'undefined') { return true; } return rulesToSnooze.length > 0; }, [rulesToSnooze, rulesToSnoozeFilter]); const isUnsnoozeModalOpen = useMemo(() => { - if (rulesToUnsnoozeFilter) { + if (typeof rulesToUnsnoozeFilter !== 'undefined') { return true; } return rulesToUnsnooze.length > 0; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx index cbf009acaa9ae..d5a1fd3d62b78 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { KueryNode } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, @@ -50,8 +51,8 @@ const deleteConfirmSingle = (ruleName: string) => export type BulkSnoozeScheduleModalProps = { rulesToSchedule: RuleTableItem[]; rulesToUnschedule: RuleTableItem[]; - rulesToScheduleFilter?: string; - rulesToUnscheduleFilter?: string; + rulesToScheduleFilter?: KueryNode | null | undefined; + rulesToUnscheduleFilter?: KueryNode | null | undefined; numberOfSelectedRules?: number; onClose: () => void; onSave: () => void; @@ -83,14 +84,14 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => const { showToast } = useBulkEditResponse({ onSearchPopulate }); const isScheduleModalOpen = useMemo(() => { - if (rulesToScheduleFilter) { + if (typeof rulesToScheduleFilter !== 'undefined') { return true; } return rulesToSchedule.length > 0; }, [rulesToSchedule, rulesToScheduleFilter]); const isUnscheduleModalOpen = useMemo(() => { - if (rulesToUnscheduleFilter) { + if (typeof rulesToUnscheduleFilter !== 'undefined') { return true; } return rulesToUnschedule.length > 0; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 0061e56d48498..a06958fb3072e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { capitalize, isEmpty, sortBy } from 'lodash'; +import { KueryNode } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useEffect, useState, ReactNode, useCallback, useMemo } from 'react'; import { @@ -206,20 +207,31 @@ export const RulesList = ({ const [rulesToDelete, setRulesToDelete] = useState([]); + // TODO - tech debt: Right now we're using null and undefined to determine if we should + // render the bulk edit modal. Refactor this to only keep track of 1 set of rules and types + // to determine which modal to show const [rulesToSnooze, setRulesToSnooze] = useState([]); - const [rulesToSnoozeFilter, setRulesToSnoozeFilter] = useState(''); + const [rulesToSnoozeFilter, setRulesToSnoozeFilter] = useState(); const [rulesToUnsnooze, setRulesToUnsnooze] = useState([]); - const [rulesToUnsnoozeFilter, setRulesToUnsnoozeFilter] = useState(''); + const [rulesToUnsnoozeFilter, setRulesToUnsnoozeFilter] = useState< + KueryNode | null | undefined + >(); const [rulesToSchedule, setRulesToSchedule] = useState([]); - const [rulesToScheduleFilter, setRulesToScheduleFilter] = useState(''); + const [rulesToScheduleFilter, setRulesToScheduleFilter] = useState< + KueryNode | null | undefined + >(); const [rulesToUnschedule, setRulesToUnschedule] = useState([]); - const [rulesToUnscheduleFilter, setRulesToUnscheduleFilter] = useState(''); + const [rulesToUnscheduleFilter, setRulesToUnscheduleFilter] = useState< + KueryNode | null | undefined + >(); const [rulesToUpdateAPIKey, setRulesToUpdateAPIKey] = useState([]); - const [rulesToUpdateAPIKeyFilter, setRulesToUpdateAPIKeyFilter] = useState(''); + const [rulesToUpdateAPIKeyFilter, setRulesToUpdateAPIKeyFilter] = useState< + KueryNode | null | undefined + >(); const [isSnoozingRules, setIsSnoozingRules] = useState(false); const [isSchedulingRules, setIsSchedulingRules] = useState(false); @@ -586,6 +598,12 @@ export const RulesList = ({ } = useBulkEditSelect({ totalItemCount: rulesState.totalItemCount, items: tableItems, + searchText, + typesFilter: rulesTypesFilter, + actionTypesFilter, + ruleExecutionStatusesFilter, + ruleStatusesFilter, + tagsFilter, }); const authorizedToModifySelectedRules = useMemo(() => { @@ -602,27 +620,27 @@ export const RulesList = ({ const clearRulesToSnooze = () => { setRulesToSnooze([]); - setRulesToSnoozeFilter(''); + setRulesToSnoozeFilter(undefined); }; const clearRulesToUnsnooze = () => { setRulesToUnsnooze([]); - setRulesToUnsnoozeFilter(''); + setRulesToUnsnoozeFilter(undefined); }; const clearRulesToSchedule = () => { setRulesToSchedule([]); - setRulesToScheduleFilter(''); + setRulesToScheduleFilter(undefined); }; const clearRulesToUnschedule = () => { setRulesToUnschedule([]); - setRulesToUnscheduleFilter(''); + setRulesToUnscheduleFilter(undefined); }; const clearRulesToUpdateAPIKey = () => { setRulesToUpdateAPIKey([]); - setRulesToUpdateAPIKeyFilter(''); + setRulesToUpdateAPIKeyFilter(undefined); }; const isRulesTableLoading = useMemo(() => { From 411f80d0d8634a7daaa17c47145e0deebe4969be Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 27 Sep 2022 22:39:46 -0600 Subject: [PATCH 106/172] [api-docs] Daily api_docs build (#142030) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.devdocs.json | 56 + api_docs/core.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 4 +- api_docs/data_query.devdocs.json | 54 +- api_docs/data_query.mdx | 4 +- api_docs/data_search.mdx | 4 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 4 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.devdocs.json | 19 +- api_docs/files.mdx | 4 +- api_docs/fleet.devdocs.json | 267 ++- api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- .../kbn_core_injected_metadata_browser.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ...solution_io_ts_alerting_types.devdocs.json | 70 +- ..._securitysolution_io_ts_alerting_types.mdx | 4 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- ..._securitysolution_io_ts_types.devdocs.json | 71 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 4 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 48 + api_docs/lens.mdx | 4 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.devdocs.json | 24 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 22 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.devdocs.json | 1524 ++++++++++++++++- api_docs/triggers_actions_ui.mdx | 4 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 411 files changed, 2350 insertions(+), 627 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index c298d8a395df6..4d0790ec95184 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 6da505acae8b7..88e4157414a7f 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 4e5199b31ef9c..c3bd7eb349a07 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index df1ba878d7e68..cb7a1008b0bc5 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 84c92a1ce8643..d9218c6f9c48f 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index e10dd6028fafe..b574eadcc22f7 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 504dc027276f0..e8fa3ead871d7 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 5dc73b2b9000b..9c484f7dfc615 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index aaa279d698443..2ec486a4957e7 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 5e129b2343ca0..b204ddb7b0ee0 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 5c1a4fea8ca5c..fdf2f80ec284b 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 6314cca4f901b..33e6421081033 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 9c7112ee6e3ad..8cdffb406e1d3 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index a673eebc1e1c6..e365d6a87afe3 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 849d5fc71f968..69b2dfc22a263 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 08008a2bfe5c5..607bae8fad1fc 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -584,6 +584,10 @@ "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" + }, { "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" @@ -620,6 +624,30 @@ "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" @@ -19295,6 +19323,10 @@ "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" + }, { "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" @@ -19331,6 +19363,30 @@ "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" diff --git a/api_docs/core.mdx b/api_docs/core.mdx index d63230e7ed586..d554613d1ec1e 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 7c8ab77b2d5ac..b25052db9516d 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 507845391c779..609a9e0925333 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index ef9680b1325db..ae6e7c69b3a88 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index f849f08374ded..b8c91c02caa2d 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3211 | 33 | 2508 | 23 | +| 3213 | 33 | 2509 | 23 | ## Client diff --git a/api_docs/data_query.devdocs.json b/api_docs/data_query.devdocs.json index 2507050e905fc..23849f6641368 100644 --- a/api_docs/data_query.devdocs.json +++ b/api_docs/data_query.devdocs.json @@ -3785,10 +3785,10 @@ }, { "parentPluginId": "data", - "id": "def-common.queryStateToExpressionAst", + "id": "def-common.textBasedQueryStateToAstWithValidation", "type": "Function", "tags": [], - "label": "queryStateToExpressionAst", + "label": "textBasedQueryStateToAstWithValidation", "description": [ "\nConverts QueryState to expression AST" ], @@ -3801,15 +3801,15 @@ "section": "def-common.ExpressionAstExpression", "text": "ExpressionAstExpression" }, - ">" + " | undefined>" ], - "path": "src/plugins/data/common/query/to_expression_ast.ts", + "path": "src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-common.queryStateToExpressionAst.$1", + "id": "def-common.textBasedQueryStateToAstWithValidation.$1", "type": "Object", "tags": [], "label": "{\n filters,\n query,\n inputQuery,\n time,\n dataViewsService,\n}", @@ -3817,7 +3817,49 @@ "signature": [ "Args" ], - "path": "src/plugins/data/common/query/to_expression_ast.ts", + "path": "src/plugins/data/common/query/text_based_query_state_to_ast_with_validation.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.textBasedQueryStateToExpressionAst", + "type": "Function", + "tags": [], + "label": "textBasedQueryStateToExpressionAst", + "description": [ + "\nConverts QueryState to expression AST" + ], + "signature": [ + "({\n filters,\n query,\n inputQuery,\n time,\n timeFieldName,\n}: Args) => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionAstExpression", + "text": "ExpressionAstExpression" + } + ], + "path": "src/plugins/data/common/query/text_based_query_state_to_ast.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.textBasedQueryStateToExpressionAst.$1", + "type": "Object", + "tags": [], + "label": "{\n filters,\n query,\n inputQuery,\n time,\n timeFieldName,\n}", + "description": [], + "signature": [ + "Args" + ], + "path": "src/plugins/data/common/query/text_based_query_state_to_ast.ts", "deprecated": false, "trackAdoption": false, "isRequired": true diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index bb88b05d16a92..b81d02840aba1 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3211 | 33 | 2508 | 23 | +| 3213 | 33 | 2509 | 23 | ## Client diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 3cf065a957729..12e4806b7ea2b 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3211 | 33 | 2508 | 23 | +| 3213 | 33 | 2509 | 23 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 6fca87db82f91..1384a135bfbe8 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 9e4d3deac15c4..4b95039f7981c 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index cf43b6fa9dd9e..3608114bd15af 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index a5ea60ca6db86..c6389cc090a71 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 797cd5596c31f..69bcfa932c46f 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index ec45435a4e875..b658b5b795600 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 430ba54813520..dabf6a7945b9a 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 30143657a7b1e..643d002885ef8 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 95bc923cc9da0..ddeb12beb9a7a 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 8f51330d8bc6d..4a2a4abc7d5ef 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 7263ed936315a..104fc1d5cde65 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index f5d8586e5194b..3203b4c957545 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 326d612509b6c..e5e64a70e8012 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index e5a8bb47a0520..6d488adbda835 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 23ae523bade64..2d3bbbfe7ad85 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index d5982b5a2a865..e6eb098f3c9cd 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-ma | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 114 | 3 | 110 | 3 | +| 114 | 3 | 110 | 5 | ## Client diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 705fa73b04c1d..54a7269ef5325 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 53b40226f0d78..dff00025d47a5 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 51a1fd22bc039..ba5e5e742f4c5 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index c23d6a727bb38..331af14bbd784 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 1196f07945026..36ea696e53359 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 046b3c31f107b..97052c63f7476 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 9a3e6bd0dc825..95950d42e4473 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 76fd8a4068f19..ee6b2d7e353f4 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 337717de0a65a..c1edcc43088dd 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 32c3971bd0909..672e12f360d34 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index ca6a5be21ec3a..7fbd70637ed36 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 90854571dc71c..09f5c55834635 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 898224f9aba1a..8414051020d06 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index f6686201cb138..42b6c0963d951 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index c090283f69746..340a2de8e8e21 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index cc0dfccc59154..74c3fd828f44f 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 116d58a6218b9..7b4880c40f8d6 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index dbb19f977cc78..8c4ebd13fe23d 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 31c1b78e94389..c0963ced99a2c 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 358859310c797..7c94fcdac9025 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -438,10 +438,10 @@ "tags": [], "label": "getDownloadHref", "description": [ - "\nGet a string for downloading a file that can be passed to a button element's\nhref for download." + "\nGet a string for downloading a file that can be passed to a button element's\nhref for download.\n" ], "signature": [ - "(file: ", + "(args: Pick<", { "pluginId": "files", "scope": "common", @@ -449,7 +449,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - ") => string" + ", \"id\" | \"fileKind\">) => string" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -460,9 +460,12 @@ "id": "def-public.FilesClient.getDownloadHref.$1", "type": "Object", "tags": [], - "label": "file", - "description": [], + "label": "args", + "description": [ + "- get download URL args" + ], "signature": [ + "Pick<", { "pluginId": "files", "scope": "common", @@ -470,7 +473,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "" + ", \"id\" | \"fileKind\">" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -1129,7 +1132,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit<", + "; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { kind: string; }, \"kind\">) => Promise<", + ", \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { kind: string; }, \"kind\">) => Promise<", { "pluginId": "files", "scope": "common", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 149bec1a8acb5..96ec1e67bf2bf 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 263 | 0 | 15 | 2 | +| 263 | 0 | 14 | 2 | ## Client diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index a35508ec3930f..d068e061e494b 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -1225,6 +1225,146 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtension", + "type": "Interface", + "tags": [], + "label": "PackagePolicyCreateMultiStepExtension", + "description": [ + "Extension point registration contract for Integration Policy Create views in multi-step onboarding" + ], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtension.package", + "type": "string", + "tags": [], + "label": "package", + "description": [], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtension.view", + "type": "string", + "tags": [], + "label": "view", + "description": [], + "signature": [ + "\"package-policy-create-multi-step\"" + ], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtension.Component", + "type": "Function", + "tags": [], + "label": "Component", + "description": [], + "signature": [ + "React.ExoticComponent<(", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", + "text": "PackagePolicyCreateMultiStepExtensionComponentProps" + }, + " & React.RefAttributes>) | (", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", + "text": "PackagePolicyCreateMultiStepExtensionComponentProps" + }, + " & { children?: React.ReactNode; })> & { readonly _result: ", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtensionComponent", + "text": "PackagePolicyCreateMultiStepExtensionComponent" + }, + "; }" + ], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtension.Component.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", + "type": "Interface", + "tags": [], + "label": "PackagePolicyCreateMultiStepExtensionComponentProps", + "description": [], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps.newPolicy", + "type": "Object", + "tags": [], + "label": "newPolicy", + "description": [ + "The integration policy being created" + ], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NewPackagePolicy", + "text": "NewPackagePolicy" + } + ], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-public.PackagePolicyEditExtension", @@ -1925,10 +2065,10 @@ "id": "def-public.UIExtensionsStorage.Unnamed", "type": "IndexSignature", "tags": [], - "label": "[key: string]: Partial>", + "label": "[key: string]: Partial>", "description": [], "signature": [ - "[key: string]: Partial | React.FunctionComponent<", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", + "text": "PackagePolicyCreateMultiStepExtensionComponentProps" + }, + ">" + ], + "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-public.PackagePolicyEditExtensionComponent", @@ -2269,6 +2442,14 @@ "docId": "kibFleetPluginApi", "section": "def-public.AgentEnrollmentFlyoutFinalStepExtension", "text": "AgentEnrollmentFlyoutFinalStepExtension" + }, + " | ", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtension", + "text": "PackagePolicyCreateMultiStepExtension" } ], "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", @@ -2365,6 +2546,14 @@ "docId": "kibFleetPluginApi", "section": "def-public.AgentEnrollmentFlyoutFinalStepExtension", "text": "AgentEnrollmentFlyoutFinalStepExtension" + }, + " | ", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtension", + "text": "PackagePolicyCreateMultiStepExtension" } ], "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", @@ -3725,6 +3914,14 @@ "docId": "kibFleetPluginApi", "section": "def-public.AgentEnrollmentFlyoutFinalStepExtension", "text": "AgentEnrollmentFlyoutFinalStepExtension" + }, + " | ", + { + "pluginId": "fleet", + "scope": "public", + "docId": "kibFleetPluginApi", + "section": "def-public.PackagePolicyCreateMultiStepExtension", + "text": "PackagePolicyCreateMultiStepExtension" } ], "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", @@ -6227,7 +6424,7 @@ "section": "def-common.AuthenticatedUser", "text": "AuthenticatedUser" }, - " | undefined; force?: boolean | undefined; } | undefined, currentVersion?: string | undefined) => Promise<", + " | undefined; force?: boolean | undefined; skipUniqueNameVerification?: boolean | undefined; } | undefined, currentVersion?: string | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -6352,6 +6549,20 @@ "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.update.$5.skipUniqueNameVerification", + "type": "CompoundType", + "tags": [], + "label": "skipUniqueNameVerification", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false } ] }, @@ -9276,14 +9487,14 @@ { "parentPluginId": "fleet", "id": "def-common.FleetServerAgent.upgraded_at", - "type": "string", + "type": "CompoundType", "tags": [], "label": "upgraded_at", "description": [ "\nDate/time the Elastic Agent was last upgraded" ], "signature": [ - "string | undefined" + "string | null | undefined" ], "path": "x-pack/plugins/fleet/common/types/models/agent.ts", "deprecated": false, @@ -9305,22 +9516,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "fleet", - "id": "def-common.FleetServerAgent.upgrade_status", - "type": "CompoundType", - "tags": [], - "label": "upgrade_status", - "description": [ - "\nUpgrade status" - ], - "signature": [ - "\"completed\" | \"started\" | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "fleet", "id": "def-common.FleetServerAgent.access_api_key_id", @@ -14202,21 +14397,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "fleet", - "id": "def-common.ENDPOINT_PRIVILEGES", - "type": "Array", - "tags": [], - "label": "ENDPOINT_PRIVILEGES", - "description": [], - "signature": [ - "string[]" - ], - "path": "x-pack/plugins/fleet/common/constants/authz.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "fleet", "id": "def-common.EsAssetReference", @@ -16546,6 +16726,21 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.ENDPOINT_PRIVILEGES", + "type": "Object", + "tags": [], + "label": "ENDPOINT_PRIVILEGES", + "description": [], + "signature": [ + "readonly [\"writeEndpointList\", \"readEndpointList\", \"writeTrustedApplications\", \"readTrustedApplications\", \"writeHostIsolationExceptions\", \"readHostIsolationExceptions\", \"writeBlocklist\", \"readBlocklist\", \"writeEventFilters\", \"readEventFilters\", \"writePolicyManagement\", \"readPolicyManagement\", \"writeActionsLogManagement\", \"readActionsLogManagement\", \"writeHostIsolation\", \"writeProcessOperations\", \"writeFileOperations\"]" + ], + "path": "x-pack/plugins/fleet/common/constants/authz.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.EPM_API_ROUTES", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 173f9f76092f4..6f966dbb5a316 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 988 | 3 | 888 | 17 | +| 996 | 3 | 893 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index c689891e7444b..63433541ece2b 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 5dfb91864c060..c28c8b2fef872 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 62db97259ee59..8c285e037a355 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 87bd3e83a63b9..2d1523f302cb2 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index eee8cda64e245..46b6303673ab9 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index fd32cbab99193..d86809062de00 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 00175e67b30b5..0a75d4c550c42 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 33ee5123bb5cd..1b2ce0d5a84c2 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index bf08287ddc424..3ca0d95b596d9 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index d4cb51aa5139f..7e469da7a40b8 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 389117150bd55..9e1d8daed9b7c 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 749a781cf0e47..294c0b5ebb04b 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index c0f7031e47a7a..e991378eefb61 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1595bab913e80..61ec0f495851f 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index ba7823ee64ac1..af69ccaa66458 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 105ff1b565b71..c80fb067196d2 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index ef9a534fa3470..c8fd4ef979406 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 6a263b0defbb9..102c627e9ef5e 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index defb49cbe69ea..88ae4fc21aca0 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 0a80a8f9d7c64..951b6896fa528 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index f87f80a173bee..a679b1a252dad 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 465473d3ef7cd..c330c66a3b511 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 8bc8181b1c666..4cf5662b020cc 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 26ac1ad8da546..4396f3ffe02e1 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 79c65aec69dbb..1857d82ee013a 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 4b5645cc4b132..f3779a0488fb4 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index fb74c109385e8..e2d60d46020af 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 6dfdbdc6a7e19..e0475bc85ecd2 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index e5787da85b661..32514d096d20f 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 9f30996b585d7..b93c70c8a0eb5 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 97256b7516108..78d758c0f2fe3 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 12779749648df..812ca543dea50 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 89fc744b1c703..c29081c8b626d 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index bad36ac54b215..739c7e7af5969 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 5db786b7754f0..2c0999666ff1f 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 0141718a2ccd2..f58a16ef732ad 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 2d21be701e133..ced023377219a 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 1dc30aff04e5d..cbea4bff1bf86 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 7e999f21f3e1a..b1580098a3652 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index c957561aa4eca..f704df4a7f345 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 6ba6188770d4c..4767d8fa50538 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 09bb2339c9782..7c6ce13416170 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index a036a5bb51c0e..17402d105d5b5 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 42db1a2581375..48cb4145874c7 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index cd0875458844d..28228f4c74761 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 468ebd13bf1b3..09afb9dd896c7 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index c986f9acf0ef3..bf845366f8e62 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index d6cd91c67140a..bf1d2e6b25458 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 7a6ba37310aaf..a2eb49783d4be 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 24b234f7f811d..aa1d950326fc9 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index c5359e8345a38..3d8690f8d6f7d 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index b847a51cd7a0d..78563e898c03a 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index cc02f6073ea91..4b44cbfe07571 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index b1d46f4e5fe8c..f8052fad772a5 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index ee601e6352e35..79da95fae9e67 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 6eec129fe2615..ad16ad6aea2e6 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 983047e82691a..7fe5dba92aa5c 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 0190b56cf9739..2547deec251f6 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 60d444ced08da..d96ff821c4365 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index e9f8e03e21a24..74dee4cd3fdcc 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index cd76ecc1e5de7..da6a21361ea25 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index adcf635bd0895..617a402d35fcc 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index d8f11b0a78906..1217a7b8fa20d 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 04891d7eee772..0ccaad98b392b 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index a211477391f89..14eccd7c10c34 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index cabbd8bcaf86c..f5f017918ee4a 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index c6a7f734219d8..477917148b690 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 7af48bcbf5f1b..399790680e068 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index ca2a47bc99331..318e2f19b36d7 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 5c6a4b84856c7..b4e402b1d85a7 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index dd0e97e30fc7c..1263eb43812bf 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 2d4f36ed093a2..efd3c4dce7d8e 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 587243bb64061..4b8b92e29d47e 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 96b79793ed095..12f2b1b89aaa5 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index d8cfe4d9f349d..22703ff1d56c5 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index fe90efd441051..58d7084730b09 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index e7b47436fa761..9f241f79f3092 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 8fa86908f719d..2d3ce9691dbc3 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 76ca7ae6c111b..db3e364e96ff7 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 3e17e9ed94637..cbeb87f2524be 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 01d683396e2b7..e838dfc832fd6 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 891c8d8767547..6bdcb4cd9e37a 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index a935d019adeb4..afb5974de9a1d 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 04f71c530c9a5..8e28ebbe2ff12 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 53f1d9fe670a0..463b56f364292 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index f8ff0087dbbfc..bf4cf64d7a58a 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index c93bb2546524b..4047f8a6f4847 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 6121b09f8c1b4..650426f493570 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 49dffda39dcc6..0df0a232ed7e3 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 74c3216b75ebc..404f13d3ca12a 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 50ebcbe4058b9..524b236008cfb 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index c7649dcf597cf..94a0469ff1071 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 7e8106d7e3c66..aac9e17df421b 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 32e6b9184dbe1..e83f6e8553d82 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 4e5cbf65974a7..a89d07338f781 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 93c620cca65a7..3575cc0d18a98 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 13734215059b0..ce24eb49860c5 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 08dbe296b4323..92e9d0e07cfe5 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index b88675d50c204..17e4abfa2a1aa 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index fc53dc77eda1c..d8c9f151e2f4f 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 2526e6dc2b6b9..574ff5014691b 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 8b557e2e502b2..62c8d4f2358e5 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 74231286ffe09..8deee157e3d42 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index a4e8cb4241bef..cf1126fe73e2a 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index f56eecbd69552..bc4d09292edbf 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 2067fdbc72fda..485e124d97e42 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index a7e7a11ae05ea..4b17dbaac8c84 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index febc5d3038933..36c1e8c0f9a42 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 47cc583e2df0e..a1a8ce47aed54 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 9e16d8304decd..6b4a55f82676d 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 5db83c7383b67..d1494db236370 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 8fafac5831639..ad35d06977cc0 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 9fbffa9c5df93..5b5b8a9421411 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index d253278b97f15..bf95da355c9e9 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 5dd3d790c3eaa..3a07f7a676064 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index ed0c7c4e240ce..130bddee31d14 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 5c44022f60828..fef5e6c2f9a85 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index ef4b9a440b592..724251e5e502f 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 559df72a076e4..3837e623f29c4 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 6160c98032fef..0f7f597d06d38 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 5765d85478d62..42846c64f33d6 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 09d09fe708575..a52c3697e57dd 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 5416044cac958..238252f104b3c 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 962d718e0042b..d5b97eb1a0cbb 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 07d55b8bdc9ad..20a753b66ea99 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index f31451fde9e77..c074d0f80cb88 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 97d1f20eba7af..949af62592ecb 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 1c1ec6407d249..6fa09c36e31f1 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 678cf37d24e81..7c588cdc511e2 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index da93d1e0b8552..447e323f6f6c3 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 73d6c70c1a9ac..9931b55fa4f21 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 2b499a47657b8..deb24928a107a 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index fcec7c16e5f85..3bfa422bdff98 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 197152a6dae02..1a306a18a3eb1 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 80320e7312485..a34e632cf249f 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 63fa1e47ac1bf..59d4d1841c7fa 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 127679f751476..f0610e31ec803 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index f633f52c6174d..cd9ba3c71908b 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index bc446d004b249..f2dd4437d43e1 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index ecbb1447d1e5c..88c329e9ae344 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index c694b3c9f6584..df7eaa590f6db 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 39099151043aa..5534b4652be59 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 73e0b05504386..ccf532e33ba38 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 0012db4e553fb..5b78e21ba58ce 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 1ef8c6abfc97a..f4f9cc899056b 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 7c9440c92271b..86d04a0b4ff7f 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index bd5556b6ce877..52e756da1a554 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 364b20ab80d66..432a7d95007ec 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index aa7990a556b57..8ccca542c4b7d 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index d4a0a789572fb..ab86530abfbbd 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 826ee5d1b3459..9de9031c75db8 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index e68a43d0ce9fe..9c59e5a04b8e4 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 8c938db4082e1..02d479962f929 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 0cbb8955ac42e..71eada8e5603f 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 67a42c6099d97..b8b744e259c54 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 791006e8cea6b..a89cdf92e9ac2 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index b1ae46812b1d2..290774ac1f9eb 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index ef778256b7997..004e652235478 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 545b597ef2b8b..84190ae50348c 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 99402f8c657ce..2f362f1ede3ff 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index a5081a8a512eb..e214233f02eb7 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index b490e18b99885..b8ac7249cb2af 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 01b13b900344a..4f5cca38779c3 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 90ea1b666c8aa..bc10003b1203b 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index a9fb17b3cd06f..d95c8ad7295ed 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 0afac1bca8d36..06ce6dd999351 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index d2e4558115131..4bcac88980eb1 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 42e02f9783509..42846a27d0d57 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 5169411f49a2a..5db4302dd4cd2 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 5872d911b8f47..968955efd32a8 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 8f8299fb3972b..e047906625c28 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 93faea017a0fb..c7d4d0fe3d260 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index f29fe40eb21d1..30e9b543d9d8d 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 2d7b26292e0e8..8590060b07965 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index da7db13bb38b1..1e69af86cfda4 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 3d7f8993dd9fb..695ed989dfe99 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index db9354454f22f..b329278d82729 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 5bee2fdbf5f66..9a2cfbe08a28b 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index f6c2a308c267e..2e0b9742678dc 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 52dcd1a4217e9..0373e71bbf94c 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index e8d91e141f195..f0a54f4a6740d 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 93882b49ca42b..a2bfedd08171e 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 5955c7f01158f..2a9f62b4f3f36 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index e50741bdb3a60..b1b28470de32e 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 24eedfb0387a4..b675d4d8f4da0 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 1d4fddcc02250..b7abe8b512959 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 59d37e8a5d09f..e7a5d6fe0f7ec 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 5e218789e57f1..f716a25e4408f 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 15647319a90f0..25ba31dee6a69 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 56039851af766..c36a372e85d74 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 807a82e9cbff7..6051ff9b96e16 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index dfb4aea12e2d7..0554879f31ad2 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 28487fab71400..c005d1ea66f6e 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 01244be181bf2..b2cd34e94a506 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 7d70916d448e4..1b91d601c2246 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 63d9dc96362af..13444331d9fcc 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 6b1049cee9be2..d6db9caa5dd01 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index a175904d02573..12ab660fc1764 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index ca5e2566d884c..a0fbd8c5ef3c9 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 78c0d9fbb8369..6808dca6dbe35 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index ffdc48a0fb0d3..8da4778d8b81f 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index a1f168d3fc91d..e9cf6c1838c51 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 7da86649ddf39..4ec4c4ef7c9eb 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 29b3596a8bc29..757c4018832a9 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index fb729fc0747ed..11961ef3d602b 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 54d57200bd5a1..a980b80bd7a38 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index ba91201a52c6c..b1051ed927c86 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 493ca235ab7a9..98d5196dc24f1 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index b16b234ee0f50..a04f5630c37b4 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index f7d3bada225b9..af33c58333e71 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 65a1a0d424f15..2a373248eac93 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index ef453ad073e4f..61d440950cbf3 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index f441cc10aa506..4ca4605d2eb99 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index c81350b38632b..99858a653681a 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 8e6bdc15f9807..c74046c4b1fa8 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json index b42cebbc4fb84..f9869d88c91cd 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json @@ -877,21 +877,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ThrottleOrUndefinedOrNull", - "type": "Type", - "tags": [], - "label": "ThrottleOrUndefinedOrNull", - "description": [], - "signature": [ - "string | null | undefined" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.Type", @@ -1383,24 +1368,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.DefaultThrottleNull", - "type": "Object", - "tags": [], - "label": "DefaultThrottleNull", - "description": [ - "\nTypes the DefaultThrottleNull as:\n - If null or undefined, then a null will be set" - ], - "signature": [ - "Type", - "" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/default_throttle_null/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.DefaultToString", @@ -2921,7 +2888,14 @@ "label": "throttle", "description": [], "signature": [ - "StringC" + "UnionC", + "<[", + "LiteralC", + "<\"no_actions\">, ", + "LiteralC", + "<\"rule\">, ", + "Type", + "]>" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", "deprecated": false, @@ -2938,31 +2912,15 @@ "signature": [ "UnionC", "<[", - "StringC", - ", ", - "NullC", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.throttleOrNullOrUndefined", - "type": "Object", - "tags": [], - "label": "throttleOrNullOrUndefined", - "description": [], - "signature": [ "UnionC", "<[", - "StringC", - ", ", + "LiteralC", + "<\"no_actions\">, ", + "LiteralC", + "<\"rule\">, ", + "Type", + "]>, ", "NullC", - ", ", - "UndefinedC", "]>" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 4066f50c33520..5f200ca33cd52 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 148 | 0 | 129 | 0 | +| 145 | 0 | 127 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 720ede9fa9e87..75beb67b94d03 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.devdocs.json b/api_docs/kbn_securitysolution_io_ts_types.devdocs.json index 2ec94e9e8c1c0..ef934492d28ef 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_types.devdocs.json @@ -405,6 +405,41 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-types", + "id": "def-common.TimeDuration", + "type": "Function", + "tags": [], + "label": "TimeDuration", + "description": [], + "signature": [ + "({ allowedUnits, allowedDurations }: TimeDurationType) => ", + "Type", + "" + ], + "path": "packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-io-ts-types", + "id": "def-common.TimeDuration.$1", + "type": "CompoundType", + "tags": [], + "label": "{ allowedUnits, allowedDurations }", + "description": [], + "signature": [ + "TimeDurationType" + ], + "path": "packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [], @@ -644,12 +679,30 @@ "label": "TimeDurationC", "description": [], "signature": [ + "({ allowedUnits, allowedDurations }: TimeDurationType) => ", "Type", "" ], "path": "packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts", "deprecated": false, "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-io-ts-types", + "id": "def-common.TimeDurationC.$1", + "type": "CompoundType", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "TimeDurationWithAllowedDurations | TimeDurationWithAllowedUnits" + ], + "path": "packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], "initialIsOpen": false }, { @@ -1042,24 +1095,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-types", - "id": "def-common.TimeDuration", - "type": "Object", - "tags": [], - "label": "TimeDuration", - "description": [ - "\nTypes the TimeDuration as:\n - A string that is not empty, and composed of a positive integer greater than 0 followed by a unit of time\n - in the format {safe_integer}{timeUnit}, e.g. \"30s\", \"1m\", \"2h\"" - ], - "signature": [ - "Type", - "" - ], - "path": "packages/kbn-securitysolution-io-ts-types/src/time_duration/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-types", "id": "def-common.UUID", diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index f2220252df9f4..2525d36a83930 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 63 | 0 | 33 | 0 | +| 65 | 0 | 36 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 4ddd805beceec..1729eb1e31923 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 837b102f35b7c..e7ea35711a930 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 5e117e7fdd5ff..061d996305ac9 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index a0d87f4a94bbb..19a2ed45a48ba 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index a42cbdfe87318..247718cc1d2a7 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index c978507107c7d..53f33ec3d564b 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index d7cd694497c7f..0be9d3e0886f9 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index c6c4f053e9525..b3d353cc6987e 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index fc18c7757d1d9..aefc65e7066a3 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 2c9d51b468868..25cf89691a720 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 1a40ade046a8a..aaf7c6592481a 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 6d9064ab9d9ac..b12d28f86530e 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index e28b92febd738..4fafd24671035 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 3515b5ee35bf4..401b8763ce31c 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index ba2f64392e72c..25b21295a4ebd 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 6942dcbd38fca..5867910e33d25 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 69a6794fbccd5..757324760d8eb 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index a8465df7126e6..19d3fcf3f0acc 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 9e930a85deb81..f75c2846a0475 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index d96c9079cf5fd..89d4d761d558c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 86595fb7cfb1f..2056f2314bce9 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 9132796651764..495d91eac2545 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index e5b6a7845bbeb..7767a12066d24 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 09f6ed5cf1fa0..e0d65073b737e 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index ef0e7d2b60bf3..8c798dd34a9a4 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 43ef0de76bb28..243de44838e10 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index eecbe82f148c7..38664c4b62fdf 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 001790b9086e2..3f237d268f073 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 42efbc3a489cc..3ff43ae1b6263 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 71404d9e00a6f..48e497a99004b 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index a24b3d7d985a9..15f626088ad23 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index b833764dfacbc..2e0d73cc43623 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index bf5525e9d8ea8..e9accf72f5a02 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 16c867044187d..6309733dc1bab 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index ef2f54962c583..c770dd8671956 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 8fa73d4c68dad..4b531126964e6 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 8a049d4872b72..8073cadd73d80 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index f1ba970a48042..c05c4d734c490 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 2ff62c2d04f35..660e36ef4374e 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index b77c7bfc44e93..93828baf05442 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index f81111a502799..fc2ed806bd5a7 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index eaf2d94c9a05d..fe8a35b282802 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 96f5d4d6ab2a7..b072b928a5fee 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index db3273ea8883f..2cc7dbb077e4d 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index fe4e9cade439f..4c380a6aada55 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 9744d7e40941f..4658f66ec9f02 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 5bf8d7c641755..5c785be7541a2 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 24144f46636bc..058bbf15a8534 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index fe7f2f02b8192..ffad848d71c2b 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 7855bb68d62db..b7a7e0d49898e 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 5adb3b961a126..9c4fac9fc2223 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 879d594892e8b..a08a1f8c35b21 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 13a63d5a655f6..484f22d4d3fad 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 17c5bc67927ee..86360f220dc24 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 661dda80a0227..db2dd293c3e4b 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 3a54ee39d7116..a8b6bbd605637 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 62af983d79330..e8b72f7cee2d1 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index c101218ee3f45..fc9a2e294a610 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 4fc56c34842e9..4d4561547ed2a 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -1527,6 +1527,24 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "lens", + "id": "def-public.DatasourcePublicAPI.isTextBasedLanguage", + "type": "Function", + "tags": [], + "label": "isTextBasedLanguage", + "description": [ + "\nReturns true if this is a text based language datasource" + ], + "signature": [ + "() => boolean" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.DatasourcePublicAPI.getFilters", @@ -11347,6 +11365,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "lens", + "id": "def-common.ENABLE_SQL", + "type": "string", + "tags": [], + "label": "ENABLE_SQL", + "description": [], + "signature": [ + "\"discover:enableSql\"" + ], + "path": "x-pack/plugins/lens/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "lens", "id": "def-common.FormatFactory", @@ -11495,6 +11528,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "lens", + "id": "def-common.OriginalColumn", + "type": "Type", + "tags": [], + "label": "OriginalColumn", + "description": [], + "signature": [ + "{ id: string; label: string; } & ({ operationType: \"date_histogram\"; sourceField: string; } | { operationType: string; sourceField: never; })" + ], + "path": "x-pack/plugins/lens/common/expressions/map_to_columns/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "lens", "id": "def-common.PieChartType", diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index e6eb90ca75ba8..75d2cd54a2d8b 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 646 | 0 | 558 | 42 | +| 649 | 0 | 560 | 42 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 745b2e56c1c9d..42f4d0805fc3c 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 3aa129254bb54..ca3e9e1a23c12 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index f4406dfe719d8..67cd87e28a5ce 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 6aa4f4f225193..c0fba6cbabbb7 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 92b5f7e806788..7a332ae0f68be 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 2e5da06f70dd9..c821d5118393a 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index a2dd183ea48bc..30e6fc5864b60 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 55b3f0eff7da0..83e81edff74a4 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index dbf1d15ca06df..8b27dbc3d7849 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 16eb11d15bbf2..d0c0e4b930125 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index e9f3d869a2077..c8d4e9b1fbf7c 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 95e0ce94137e7..79f080105b7b9 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 7d399eaa890b1..d4d3483ef247c 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -3704,11 +3704,29 @@ "description": [], "signature": [ "{ get: (id: string) => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "; list: () => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "[]; register: (objectType: ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ") => void; has: (id: string) => boolean; }" ], "path": "x-pack/plugins/observability/public/plugin.ts", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index afdb6cd4fbb3c..4c2ca8b17a13f 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index a634c2e2206cc..942900853abdb 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 00a92aa5b8132..bcddc85faf5c2 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
    public API | Number of teams | |--------------|----------|------------------------| -| 473 | 395 | 38 | +| 474 | 395 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 31772 | 179 | 21354 | 1002 | +| 31860 | 179 | 21432 | 1007 | ## Plugin Directory @@ -47,7 +47,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 103 | 0 | 84 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 144 | 0 | 139 | 10 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3211 | 33 | 2508 | 23 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3213 | 33 | 2509 | 23 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 15 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | @@ -60,7 +60,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 42 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | -| | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 114 | 3 | 110 | 3 | +| | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 114 | 3 | 110 | 5 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | The Event Annotation service contains expressions for event annotations | 174 | 0 | 174 | 3 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 106 | 0 | 106 | 10 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | @@ -80,8 +80,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 15 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 988 | 3 | 888 | 17 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 14 | 2 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 996 | 3 | 893 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -101,7 +101,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 418 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 646 | 0 | 558 | 42 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 649 | 0 | 560 | 42 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -152,7 +152,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 346 | 33 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 437 | 1 | 416 | 45 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 513 | 1 | 486 | 48 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 132 | 0 | 91 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 61 | 0 | 59 | 2 | @@ -389,9 +389,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | Security Solution auto complete | 56 | 1 | 41 | 1 | | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | | | [Owner missing] | Security Solution utilities for React hooks | 15 | 0 | 7 | 0 | -| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 148 | 0 | 129 | 0 | +| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 145 | 0 | 127 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 505 | 1 | 492 | 0 | -| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 63 | 0 | 33 | 0 | +| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 65 | 0 | 36 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 28 | 0 | 21 | 0 | | | [Owner missing] | security solution list REST API | 67 | 0 | 64 | 0 | | | [Owner missing] | security solution list constants to use across plugins such lists, security_solution, cases, etc... | 33 | 0 | 17 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 971ed39aee0ed..191e4e563cb34 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 8d2be8c0f9dd7..fb048ac82442b 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 916ffb9e1e4e0..c0b505ed6394d 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index aa8b69de45190..4088e39f97108 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 7ecab5c862bf0..4f8cf9c8e8dbf 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 42e1a51c2ed9f..15c672f3f6fee 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 4c4a4a4be8d7f..001d245e9adf3 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index d180b9df678f8..51bc9b36316c4 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index d8ee1bd800f99..3a6b9b0a9e738 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index a57ba63fec003..8fb7925eb7808 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 785d7f8ea6a32..9a0469a70f642 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index bf65a9aa5c648..8abc78f1fdc7e 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 5e294d8d6ea59..a39c3adbc8200 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 4c436c7b23e66..6eaf962f670fa 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 5bc1fcd058003..8cb0692d0c854 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 952c20eac620f..e6f2900462223 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 9cf5459a07a3e..c59c106fce254 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 23f8e18b5e278..ad4253bd0d7e5 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 7ba452349a66b..e6f9de45dbb58 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 194b818f081bf..8a45ec4089ad2 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 95118ea8b8058..94f8821728343 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index b3467e6a698dc..cb3df93effb09 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index aabbd1805b61d..a71b61831fb69 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index d35966ac57c9c..b84502a5d8c55 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 1dbef1ee43f39..dfdbd20746f57 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 438e8d307b1de..edb998b57d6f5 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index f032f6eee5e0a..c23dd99eb95e3 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 0b1523b1a10d3..5e8ae624dfe82 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index b584556618afe..80ac38a8ed990 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 14ef93c594cc2..ecb0a115ea511 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index b66dc03b52acd..70b733e2c869e 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 59be983bb00f5..316d5941b9d23 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -303,6 +303,38 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ButtonGroupField", + "type": "Function", + "tags": [], + "label": "ButtonGroupField", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/button_group_field.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ButtonGroupField.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.CreateConnectorFlyout", @@ -832,6 +864,145 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.hasMustacheTokens", + "type": "Function", + "tags": [], + "label": "hasMustacheTokens", + "description": [], + "signature": [ + "(str: string) => boolean" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.hasMustacheTokens.$1", + "type": "string", + "tags": [], + "label": "str", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.HiddenField", + "type": "Function", + "tags": [], + "label": "HiddenField", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + "Props", + "> & { readonly type: (props: ", + "Props", + ") => JSX.Element; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/hidden_field.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.HiddenField.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.JsonEditorWithMessageVariables", + "type": "Function", + "tags": [], + "label": "JsonEditorWithMessageVariables", + "description": [], + "signature": [ + "({ buttonTitle, messageVariables, paramsProperty, inputTargetValue, label, errors, areaLabel, onDocumentsChange, helpText, onBlur, showButtonTitle, euiCodeEditorProps, }: React.PropsWithChildren) => JSX.Element" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.JsonEditorWithMessageVariables.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n buttonTitle,\n messageVariables,\n paramsProperty,\n inputTargetValue,\n label,\n errors,\n areaLabel,\n onDocumentsChange,\n helpText,\n onBlur,\n showButtonTitle,\n euiCodeEditorProps = {},\n}", + "description": [], + "signature": [ + "React.PropsWithChildren" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.JsonFieldWrapper", + "type": "Function", + "tags": [], + "label": "JsonFieldWrapper", + "description": [], + "signature": [ + "({ field, ...rest }: Props) => JSX.Element" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/json_field_wrapper.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.JsonFieldWrapper.$1", + "type": "Object", + "tags": [], + "label": "{ field, ...rest }", + "description": [], + "signature": [ + "Props" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/json_field_wrapper.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.loadActionErrorLog", @@ -1398,6 +1569,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.MustacheTextFieldWrapper", + "type": "Function", + "tags": [], + "label": "MustacheTextFieldWrapper", + "description": [], + "signature": [ + "({ field, euiFieldProps, idAria, ...rest }: Props) => JSX.Element" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/mustache_text_field_wrapper.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.MustacheTextFieldWrapper.$1", + "type": "Object", + "tags": [], + "label": "{ field, euiFieldProps, idAria, ...rest }", + "description": [], + "signature": [ + "Props" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/mustache_text_field_wrapper.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.muteRule", @@ -1490,6 +1694,70 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.PasswordField", + "type": "Function", + "tags": [], + "label": "PasswordField", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/password_field.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.PasswordField.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.SimpleConnectorForm", + "type": "Function", + "tags": [], + "label": "SimpleConnectorForm", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.SimpleConnectorForm.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.snoozeRule", @@ -1614,72 +1882,185 @@ }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ThresholdExpression", + "id": "def-public.templateActionVariable", "type": "Function", "tags": [], - "label": "ThresholdExpression", + "label": "templateActionVariable", "description": [], "signature": [ - "(props: ", - "ThresholdExpressionProps", - ") => JSX.Element" + "(variable: ", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.ActionVariable", + "text": "ActionVariable" + }, + ") => string" ], - "path": "x-pack/plugins/triggers_actions_ui/public/common/expression_items/index.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/template_action_variable.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "triggersActionsUi", - "id": "def-public.ThresholdExpression.$1", - "type": "Uncategorized", + "id": "def-public.templateActionVariable.$1", + "type": "Object", "tags": [], - "label": "props", + "label": "variable", "description": [], "signature": [ - "T" + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.ActionVariable", + "text": "ActionVariable" + } ], - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx", + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/template_action_variable.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.unmuteRule", + "id": "def-public.TextAreaWithMessageVariables", "type": "Function", "tags": [], - "label": "unmuteRule", + "label": "TextAreaWithMessageVariables", "description": [], "signature": [ - "({ id, http }: { id: string; http: ", - "HttpSetup", - "; }) => Promise" + "({ messageVariables, paramsProperty, index, inputTargetValue, isDisabled, editAction, label, errors, }: React.PropsWithChildren) => JSX.Element" ], - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unmute.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "triggersActionsUi", - "id": "def-public.unmuteRule.$1", - "type": "Object", + "id": "def-public.TextAreaWithMessageVariables.$1", + "type": "CompoundType", "tags": [], - "label": "{ id, http }", + "label": "{\n messageVariables,\n paramsProperty,\n index,\n inputTargetValue,\n isDisabled = false,\n editAction,\n label,\n errors,\n}", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unmute.ts", + "signature": [ + "React.PropsWithChildren" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.unmuteRule.$1.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TextFieldWithMessageVariables", + "type": "Function", + "tags": [], + "label": "TextFieldWithMessageVariables", + "description": [], + "signature": [ + "({ buttonTitle, messageVariables, paramsProperty, index, inputTargetValue, editAction, errors, formRowProps, defaultValue, wrapField, showButtonTitle, }: React.PropsWithChildren) => JSX.Element" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TextFieldWithMessageVariables.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n buttonTitle,\n messageVariables,\n paramsProperty,\n index,\n inputTargetValue,\n editAction,\n errors,\n formRowProps,\n defaultValue,\n wrapField = false,\n showButtonTitle,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ThresholdExpression", + "type": "Function", + "tags": [], + "label": "ThresholdExpression", + "description": [], + "signature": [ + "(props: ", + "ThresholdExpressionProps", + ") => JSX.Element" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/common/expression_items/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ThresholdExpression.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "T" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.unmuteRule", + "type": "Function", + "tags": [], + "label": "unmuteRule", + "description": [], + "signature": [ + "({ id, http }: { id: string; http: ", + "HttpSetup", + "; }) => Promise" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unmute.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.unmuteRule.$1", + "type": "Object", + "tags": [], + "label": "{ id, http }", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unmute.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.unmuteRule.$1.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/unmute.ts", "deprecated": false, "trackAdoption": false @@ -1776,6 +2157,106 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.updateActionConnector", + "type": "Function", + "tags": [], + "label": "updateActionConnector", + "description": [], + "signature": [ + "({\n http,\n connector,\n id,\n}: { http: ", + "HttpSetup", + "; connector: Pick<", + "ActionConnectorWithoutId", + ", Record>, \"name\" | \"config\" | \"secrets\">; id: string; }) => Promise<", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionConnector", + "text": "ActionConnector" + }, + ", Record>>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.updateActionConnector.$1", + "type": "Object", + "tags": [], + "label": "{\n http,\n connector,\n id,\n}", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.updateActionConnector.$1.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + "HttpSetup" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.updateActionConnector.$1.connector", + "type": "Object", + "tags": [], + "label": "connector", + "description": [], + "signature": [ + "{ name: string; config: Record; secrets: Record; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.updateActionConnector.$1.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.useConnectorContext", + "type": "Function", + "tags": [], + "label": "useConnectorContext", + "description": [], + "signature": [ + "() => ", + "ConnectorContextValue" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/context/use_connector_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.useLoadRuleTypes", @@ -1819,6 +2300,41 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.useTypedKibana", + "type": "Function", + "tags": [], + "label": "useTypedKibana", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "kibanaReact", + "scope": "public", + "docId": "kibKibanaReactPluginApi", + "section": "def-public.KibanaReactContextValue", + "text": "KibanaReactContextValue" + }, + " & ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.TriggersAndActionsUiServices", + "text": "TriggersAndActionsUiServices" + }, + ">" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.ValueExpression", @@ -1891,95 +2407,651 @@ "interfaces": [ { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType", + "id": "def-public.ActionConnectorFieldsProps", "type": "Interface", "tags": [], - "label": "ActionType", + "label": "ActionConnectorFieldsProps", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.id", - "type": "string", + "id": "def-public.ActionConnectorFieldsProps.readOnly", + "type": "boolean", "tags": [], - "label": "id", + "label": "readOnly", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.name", - "type": "string", + "id": "def-public.ActionConnectorFieldsProps.isEdit", + "type": "boolean", "tags": [], - "label": "name", + "label": "isEdit", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.enabled", - "type": "boolean", + "id": "def-public.ActionConnectorFieldsProps.registerPreSubmitValidator", + "type": "Function", "tags": [], - "label": "enabled", + "label": "registerPreSubmitValidator", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "signature": [ + "(validator: ", + "ConnectorValidationFunc", + ") => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, - "trackAdoption": false - }, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionConnectorFieldsProps.registerPreSubmitValidator.$1", + "type": "Function", + "tags": [], + "label": "validator", + "description": [], + "signature": [ + "ConnectorValidationFunc" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps", + "type": "Interface", + "tags": [], + "label": "ActionParamsProps", + "description": [], + "signature": [ { - "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.enabledInConfig", - "type": "boolean", + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionParamsProps", + "text": "ActionParamsProps" + }, + "" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.actionParams", + "type": "Object", + "tags": [], + "label": "actionParams", + "description": [], + "signature": [ + "{ [P in keyof TParams]?: TParams[P] | undefined; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.index", + "type": "number", + "tags": [], + "label": "index", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.editAction", + "type": "Function", + "tags": [], + "label": "editAction", + "description": [], + "signature": [ + "(key: string, value: ", + "SavedObjectAttribute", + ", index: number) => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.editAction.$1", + "type": "string", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.editAction.$2", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "SavedObjectAttribute" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.editAction.$3", + "type": "number", + "tags": [], + "label": "index", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.errors", + "type": "Object", + "tags": [], + "label": "errors", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.IErrorObject", + "text": "IErrorObject" + } + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.messageVariables", + "type": "Array", + "tags": [], + "label": "messageVariables", + "description": [], + "signature": [ + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.ActionVariable", + "text": "ActionVariable" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.defaultMessage", + "type": "string", + "tags": [], + "label": "defaultMessage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.actionConnector", + "type": "CompoundType", + "tags": [], + "label": "actionConnector", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionConnector", + "text": "ActionConnector" + }, + ", Record> | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.isLoading", + "type": "CompoundType", + "tags": [], + "label": "isLoading", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.isDisabled", + "type": "CompoundType", + "tags": [], + "label": "isDisabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionParamsProps.showEmailSubjectAndMessage", + "type": "CompoundType", + "tags": [], + "label": "showEmailSubjectAndMessage", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType", + "type": "Interface", + "tags": [], + "label": "ActionType", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.enabledInConfig", + "type": "boolean", + "tags": [], + "label": "enabledInConfig", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.enabledInLicense", + "type": "boolean", + "tags": [], + "label": "enabledInLicense", + "description": [], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.minimumLicenseRequired", + "type": "CompoundType", + "tags": [], + "label": "minimumLicenseRequired", + "description": [], + "signature": [ + "\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\"" + ], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionType.supportedFeatureIds", + "type": "Array", + "tags": [], + "label": "supportedFeatureIds", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/actions/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel", + "type": "Interface", + "tags": [], + "label": "ActionTypeModel", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, + "" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.iconClass", + "type": "CompoundType", + "tags": [], + "label": "iconClass", + "description": [], + "signature": [ + "string | React.ComponentType<{}>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.selectMessage", + "type": "string", + "tags": [], + "label": "selectMessage", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.actionTypeTitle", + "type": "string", + "tags": [], + "label": "actionTypeTitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.validateParams", + "type": "Function", + "tags": [], + "label": "validateParams", + "description": [], + "signature": [ + "(actionParams: ActionParams) => Promise<", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.GenericValidationResult", + "text": "GenericValidationResult" + }, + ">" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.validateParams.$1", + "type": "Uncategorized", + "tags": [], + "label": "actionParams", + "description": [], + "signature": [ + "ActionParams" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.actionConnectorFields", + "type": "CompoundType", + "tags": [], + "label": "actionConnectorFields", + "description": [], + "signature": [ + "React.LazyExoticComponent> | null" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.actionParamsFields", + "type": "Function", + "tags": [], + "label": "actionParamsFields", + "description": [], + "signature": [ + "React.ExoticComponent<(", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionParamsProps", + "text": "ActionParamsProps" + }, + " & React.RefAttributes, any, any>>) | (", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionParamsProps", + "text": "ActionParamsProps" + }, + " & { children?: React.ReactNode; })> & { readonly _result: React.ComponentType<", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionParamsProps", + "text": "ActionParamsProps" + }, + ">; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.actionParamsFields.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.defaultActionParams", + "type": "Object", "tags": [], - "label": "enabledInConfig", + "label": "defaultActionParams", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "signature": [ + "Partial | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.enabledInLicense", - "type": "boolean", + "id": "def-public.ActionTypeModel.defaultRecoveredActionParams", + "type": "Object", "tags": [], - "label": "enabledInLicense", + "label": "defaultRecoveredActionParams", "description": [], - "path": "x-pack/plugins/actions/common/types.ts", + "signature": [ + "Partial | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.minimumLicenseRequired", - "type": "CompoundType", + "id": "def-public.ActionTypeModel.customConnectorSelectItem", + "type": "Object", "tags": [], - "label": "minimumLicenseRequired", + "label": "customConnectorSelectItem", "description": [], "signature": [ - "\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\"" + "CustomConnectorSelectionItem | undefined" ], - "path": "x-pack/plugins/actions/common/types.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "triggersActionsUi", - "id": "def-public.ActionType.supportedFeatureIds", - "type": "Array", + "id": "def-public.ActionTypeModel.isExperimental", + "type": "CompoundType", "tags": [], - "label": "supportedFeatureIds", + "label": "isExperimental", "description": [], "signature": [ - "string[]" + "boolean | undefined" ], - "path": "x-pack/plugins/actions/common/types.ts", + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false } @@ -2805,6 +3877,45 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ConfigFieldSchema", + "type": "Interface", + "tags": [], + "label": "ConfigFieldSchema", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ConfigFieldSchema", + "text": "ConfigFieldSchema" + }, + " extends ", + "CommonFieldSchema" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ConfigFieldSchema.isUrlField", + "type": "CompoundType", + "tags": [], + "label": "isUrlField", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.FieldBrowserOptions", @@ -3010,6 +4121,44 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.GenericValidationResult", + "type": "Interface", + "tags": [], + "label": "GenericValidationResult", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.GenericValidationResult", + "text": "GenericValidationResult" + }, + "" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.GenericValidationResult.errors", + "type": "Object", + "tags": [], + "label": "errors", + "description": [], + "signature": [ + "{ [P in Extract]: unknown; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.IErrorObject", @@ -3218,11 +4367,29 @@ "description": [], "signature": [ "{ get: (id: string) => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "; list: () => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "[]; register: (objectType: ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ") => void; has: (id: string) => boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", @@ -4147,6 +5314,45 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.SecretsFieldSchema", + "type": "Interface", + "tags": [], + "label": "SecretsFieldSchema", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.SecretsFieldSchema", + "text": "SecretsFieldSchema" + }, + " extends ", + "CommonFieldSchema" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.SecretsFieldSchema.isPasswordField", + "type": "CompoundType", + "tags": [], + "label": "isPasswordField", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/components/simple_connector_form.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.TriggersAndActionsUiServices", @@ -4390,11 +5596,29 @@ "description": [], "signature": [ "{ get: (id: string) => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "; list: () => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "[]; register: (objectType: ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ") => void; has: (id: string) => boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/application/app.tsx", @@ -4603,6 +5827,18 @@ } ], "enums": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertProvidedActionVariables", + "type": "Enum", + "tags": [], + "label": "AlertProvidedActionVariables", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.COMPARATORS", @@ -4690,11 +5926,29 @@ "description": [], "signature": [ "{ get: (id: string) => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "; list: () => ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, "[]; register: (objectType: ", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ") => void; has: (id: string) => boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", @@ -4717,6 +5971,63 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ALERT_HISTORY_PREFIX", + "type": "string", + "tags": [], + "label": "ALERT_HISTORY_PREFIX", + "description": [], + "signature": [ + "\"kibana-alert-history-\"" + ], + "path": "x-pack/plugins/actions/common/alert_history_schema.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertHistoryDefaultIndexName", + "type": "string", + "tags": [], + "label": "AlertHistoryDefaultIndexName", + "description": [], + "path": "x-pack/plugins/actions/common/alert_history_schema.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertHistoryDocumentTemplate", + "type": "CompoundType", + "tags": [], + "label": "AlertHistoryDocumentTemplate", + "description": [], + "signature": [ + "Readonly<{ event: { kind: string; }; kibana?: { alert: { actionGroupName?: string | undefined; actionGroup?: string | undefined; context?: { [x: string]: Record; } | undefined; id?: string | undefined; }; } | undefined; rule?: { type?: string | undefined; space?: string | undefined; params?: { [x: string]: Record; } | undefined; name?: string | undefined; id?: string | undefined; } | undefined; message?: unknown; tags?: string[] | undefined; '@timestamp': string; }> | null" + ], + "path": "x-pack/plugins/actions/common/alert_history_schema.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertHistoryEsIndexConnectorId", + "type": "string", + "tags": [], + "label": "AlertHistoryEsIndexConnectorId", + "description": [], + "signature": [ + "\"preconfigured-alert-history-es-index\"" + ], + "path": "x-pack/plugins/actions/common/alert_history_schema.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.AlertsTableConfigurationRegistryContract", @@ -4784,6 +6095,51 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.connectorDeprecatedMessage", + "type": "string", + "tags": [], + "label": "connectorDeprecatedMessage", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ConnectorFormSchema", + "type": "Type", + "tags": [], + "label": "ConnectorFormSchema", + "description": [ + "\nThe following type is equivalent to:\n\ninterface ConnectorFormSchema {\n id?: string,\n name?: string,\n actionTypeId: string,\n isDeprecated: boolean,\n config: Config,\n secrets: Secrets,\n}" + ], + "signature": [ + "Pick<", + "UserConfiguredActionConnector", + ", \"config\" | \"secrets\" | \"actionTypeId\" | \"isDeprecated\"> & Partial, \"name\" | \"id\">>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.deprecatedMessage", + "type": "string", + "tags": [], + "label": "deprecatedMessage", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.GetRenderCellValue", @@ -5801,7 +7157,13 @@ "signature": [ "TypeRegistry", "<", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ">" ], "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", @@ -5873,7 +7235,13 @@ "signature": [ "TypeRegistry", "<", - "ActionTypeModel", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.ActionTypeModel", + "text": "ActionTypeModel" + }, ">" ], "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 0a3cbb630285f..cfa8f8965332e 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 437 | 1 | 416 | 45 | +| 513 | 1 | 486 | 48 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 2a01df283087a..25fd9d32aac51 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index f46cdf9d62ee1..f85563ce18a73 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index f70e303ebe4d7..2aae0fbddf27b 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 2e10e0a54e2cd..261ef25b80e9b 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 18b782fa7081b..1e75d7da1deb9 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 7a9ded502796d..d2cdaf4d89c84 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 34bbb721a3728..5f6baf6d86c5f 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index c3afd8d86827c..470423b8266a7 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 271d79629a0b7..ff57731114146 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index f28ab39c716fb..1da13d1fcc094 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 32a96558c72f8..d30f1b6a560aa 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 827eb1f6f0969..f8d894bdaa7de 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index d9fc5b663679c..deb819de77df3 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index e8dfbf76d6ffa..042fb74b10af4 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index c962200fe1fdc..2458fbb4d9dee 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index abe648760bf12..606b4dcf05e16 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 4260c1aaeb253..e56cd5d8f509d 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index f03c9b37afbb4..9ac63dfe30ccc 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index c4ea2e6c61e76..e5907aaf8a8a5 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-09-27 +date: 2022-09-28 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From e24fc503f661091b7dac115695453bb3b7f4e902 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 28 Sep 2022 08:59:09 +0200 Subject: [PATCH 107/172] Move `RequestHandlerContext` types and implementation to packages (#141759) * create empty packages * continue moving things * adapt usages * [CI] Auto-commit changed files from 'node scripts/generate codeowners' * renaming packages... * adapting usages again * [CI] Auto-commit changed files from 'node scripts/generate codeowners' * move preboot context to packages too * add internal test mocks * update READMEs Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 2 + package.json | 4 + packages/BUILD.bazel | 4 + .../BUILD.bazel | 116 ++++++++++++++++++ .../README.md | 3 + .../index.ts | 10 ++ .../jest.config.js | 13 ++ .../kibana.jsonc | 7 ++ .../package.json | 8 ++ .../src}/core_route_handler_context.test.ts | 33 ++--- .../src/core_route_handler_context.ts | 63 ++++++++++ .../src/index.ts | 12 ++ ...preboot_core_route_handler_context.test.ts | 6 +- .../preboot_core_route_handler_context.ts | 21 ++-- .../core_route_handler_context_params.mock.ts | 21 ++++ .../src/test_helpers/index.ts | 10 ++ ..._core_route_handler_context_params.mock.ts | 15 +++ .../tsconfig.json | 17 +++ .../BUILD.bazel | 110 +++++++++++++++++ .../README.md | 3 + .../index.ts | 16 +++ .../jest.config.js | 13 ++ .../kibana.jsonc | 7 ++ .../package.json | 8 ++ .../src/index.ts | 18 +++ .../src/preboot_request_handler_context.ts | 31 +++++ .../src/request_handler_context.ts | 41 +++---- .../tsconfig.json | 17 +++ .../http_resources/http_resources_service.ts | 2 +- src/core/server/http_resources/types.ts | 2 +- src/core/server/index.ts | 43 ++----- src/core/server/server.ts | 11 +- yarn.lock | 16 +++ 33 files changed, 607 insertions(+), 96 deletions(-) create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/BUILD.bazel create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/README.md create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/index.ts create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/jest.config.js create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/package.json rename {src/core/server => packages/core/http/core-http-request-handler-context-server-internal/src}/core_route_handler_context.test.ts (86%) create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/src/index.ts rename {src/core/server => packages/core/http/core-http-request-handler-context-server-internal/src}/preboot_core_route_handler_context.test.ts (85%) rename {src/core/server => packages/core/http/core-http-request-handler-context-server-internal/src}/preboot_core_route_handler_context.ts (71%) create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/core_route_handler_context_params.mock.ts create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/index.ts create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/preboot_core_route_handler_context_params.mock.ts create mode 100644 packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json create mode 100644 packages/core/http/core-http-request-handler-context-server/BUILD.bazel create mode 100644 packages/core/http/core-http-request-handler-context-server/README.md create mode 100644 packages/core/http/core-http-request-handler-context-server/index.ts create mode 100644 packages/core/http/core-http-request-handler-context-server/jest.config.js create mode 100644 packages/core/http/core-http-request-handler-context-server/kibana.jsonc create mode 100644 packages/core/http/core-http-request-handler-context-server/package.json create mode 100644 packages/core/http/core-http-request-handler-context-server/src/index.ts create mode 100644 packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts rename src/core/server/core_route_handler_context.ts => packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts (53%) create mode 100644 packages/core/http/core-http-request-handler-context-server/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2c1a3e323a993..d802eb4024a08 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -743,6 +743,8 @@ packages/core/http/core-http-browser-mocks @elastic/kibana-core packages/core/http/core-http-common @elastic/kibana-core packages/core/http/core-http-context-server-internal @elastic/kibana-core packages/core/http/core-http-context-server-mocks @elastic/kibana-core +packages/core/http/core-http-request-handler-context-server @elastic/kibana-core +packages/core/http/core-http-request-handler-context-server-internal @elastic/kibana-core packages/core/http/core-http-router-server-internal @elastic/kibana-core packages/core/http/core-http-router-server-mocks @elastic/kibana-core packages/core/http/core-http-server @elastic/kibana-core diff --git a/package.json b/package.json index 6c0913394e246..ebbff20d91733 100644 --- a/package.json +++ b/package.json @@ -213,6 +213,8 @@ "@kbn/core-http-common": "link:bazel-bin/packages/core/http/core-http-common", "@kbn/core-http-context-server-internal": "link:bazel-bin/packages/core/http/core-http-context-server-internal", "@kbn/core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks", + "@kbn/core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server", + "@kbn/core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal", "@kbn/core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal", "@kbn/core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks", "@kbn/core-http-server": "link:bazel-bin/packages/core/http/core-http-server", @@ -934,6 +936,8 @@ "@types/kbn__core-http-common": "link:bazel-bin/packages/core/http/core-http-common/npm_module_types", "@types/kbn__core-http-context-server-internal": "link:bazel-bin/packages/core/http/core-http-context-server-internal/npm_module_types", "@types/kbn__core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks/npm_module_types", + "@types/kbn__core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server/npm_module_types", + "@types/kbn__core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal/npm_module_types", "@types/kbn__core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal/npm_module_types", "@types/kbn__core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks/npm_module_types", "@types/kbn__core-http-server": "link:bazel-bin/packages/core/http/core-http-server/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 62cc4b4ce9ad2..455b851a4936f 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -79,6 +79,8 @@ filegroup( "//packages/core/http/core-http-common:build", "//packages/core/http/core-http-context-server-internal:build", "//packages/core/http/core-http-context-server-mocks:build", + "//packages/core/http/core-http-request-handler-context-server:build", + "//packages/core/http/core-http-request-handler-context-server-internal:build", "//packages/core/http/core-http-router-server-internal:build", "//packages/core/http/core-http-router-server-mocks:build", "//packages/core/http/core-http-server:build", @@ -410,6 +412,8 @@ filegroup( "//packages/core/http/core-http-common:build_types", "//packages/core/http/core-http-context-server-internal:build_types", "//packages/core/http/core-http-context-server-mocks:build_types", + "//packages/core/http/core-http-request-handler-context-server:build_types", + "//packages/core/http/core-http-request-handler-context-server-internal:build_types", "//packages/core/http/core-http-router-server-internal:build_types", "//packages/core/http/core-http-router-server-mocks:build_types", "//packages/core/http/core-http-server:build_types", diff --git a/packages/core/http/core-http-request-handler-context-server-internal/BUILD.bazel b/packages/core/http/core-http-request-handler-context-server-internal/BUILD.bazel new file mode 100644 index 0000000000000..82040b5fb1ad8 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/BUILD.bazel @@ -0,0 +1,116 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-http-request-handler-context-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-http-request-handler-context-server-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/core/elasticsearch/core-elasticsearch-server-internal", + "//packages/core/saved-objects/core-saved-objects-server-internal", + "//packages/core/deprecations/core-deprecations-server-internal", + "//packages/core/ui-settings/core-ui-settings-server-internal", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-request-handler-context-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server-internal:npm_module_types", + "//packages/core/deprecations/core-deprecations-server-internal:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/http/core-http-request-handler-context-server-internal/README.md b/packages/core/http/core-http-request-handler-context-server-internal/README.md new file mode 100644 index 0000000000000..aa27c116c4f6d --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-request-handler-context-server-internal + +This package contains the internal types and implementation of Core's `CoreRequestHandlerContext` diff --git a/packages/core/http/core-http-request-handler-context-server-internal/index.ts b/packages/core/http/core-http-request-handler-context-server-internal/index.ts new file mode 100644 index 0000000000000..2ee514f67265b --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { CoreRouteHandlerContext, PrebootCoreRouteHandlerContext } from './src'; +export type { CoreRouteHandlerContextParams, PrebootCoreRouteHandlerContextParams } from './src'; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/jest.config.js b/packages/core/http/core-http-request-handler-context-server-internal/jest.config.js new file mode 100644 index 0000000000000..68a5f2b3a03a8 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/http/core-http-request-handler-context-server-internal'], +}; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc b/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc new file mode 100644 index 0000000000000..98b452aec0d97 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-request-handler-context-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-request-handler-context-server-internal/package.json b/packages/core/http/core-http-request-handler-context-server-internal/package.json new file mode 100644 index 0000000000000..672bb6ce72715 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-http-request-handler-context-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/core_route_handler_context.test.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.test.ts similarity index 86% rename from src/core/server/core_route_handler_context.test.ts rename to packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.test.ts index ace0144eae54f..f8e906650fcf1 100644 --- a/src/core/server/core_route_handler_context.test.ts +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.test.ts @@ -7,13 +7,14 @@ */ import { CoreRouteHandlerContext } from './core_route_handler_context'; -import { coreMock, httpServerMock } from './mocks'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { createCoreRouteHandlerContextParamsMock } from './test_helpers'; describe('#elasticsearch', () => { describe('#client', () => { test('returns the results of coreStart.elasticsearch.client.asScoped', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client = context.elasticsearch.client; @@ -22,7 +23,7 @@ describe('#elasticsearch', () => { test('lazily created', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); expect(coreStart.elasticsearch.client.asScoped).not.toHaveBeenCalled(); @@ -33,7 +34,7 @@ describe('#elasticsearch', () => { test('only creates one instance', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client1 = context.elasticsearch.client; @@ -50,7 +51,7 @@ describe('#savedObjects', () => { describe('#client', () => { test('returns the results of coreStart.savedObjects.getScopedClient', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client = context.savedObjects.client; @@ -59,7 +60,7 @@ describe('#savedObjects', () => { test('lazily created', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const savedObjects = context.savedObjects; @@ -71,7 +72,7 @@ describe('#savedObjects', () => { test('only creates one instance', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client1 = context.savedObjects.client; @@ -86,7 +87,7 @@ describe('#savedObjects', () => { describe('#typeRegistry', () => { test('returns the results of coreStart.savedObjects.getTypeRegistry', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const typeRegistry = context.savedObjects.typeRegistry; @@ -95,7 +96,7 @@ describe('#savedObjects', () => { test('lazily created', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); expect(coreStart.savedObjects.getTypeRegistry).not.toHaveBeenCalled(); @@ -106,7 +107,7 @@ describe('#savedObjects', () => { test('only creates one instance', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const typeRegistry1 = context.savedObjects.typeRegistry; @@ -123,7 +124,7 @@ describe('#uiSettings', () => { describe('#client', () => { test('returns the results of coreStart.uiSettings.asScopedToClient', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client = context.uiSettings.client; @@ -132,7 +133,7 @@ describe('#uiSettings', () => { test('lazily created', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); expect(coreStart.uiSettings.asScopedToClient).not.toHaveBeenCalled(); @@ -143,7 +144,7 @@ describe('#uiSettings', () => { test('only creates one instance', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client1 = context.uiSettings.client; @@ -160,7 +161,7 @@ describe('#deprecations', () => { describe('#client', () => { test('returns the results of coreStart.deprecations.asScopedToClient', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client = context.deprecations.client; @@ -169,7 +170,7 @@ describe('#deprecations', () => { test('lazily created', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); expect(coreStart.deprecations.asScopedToClient).not.toHaveBeenCalled(); @@ -180,7 +181,7 @@ describe('#deprecations', () => { test('only creates one instance', () => { const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); + const coreStart = createCoreRouteHandlerContextParamsMock(); const context = new CoreRouteHandlerContext(coreStart, request); const client1 = context.deprecations.client; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts new file mode 100644 index 0000000000000..6cc54130a575c --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { CoreRequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import { + CoreElasticsearchRouteHandlerContext, + type InternalElasticsearchServiceStart, +} from '@kbn/core-elasticsearch-server-internal'; +import { + CoreSavedObjectsRouteHandlerContext, + type InternalSavedObjectsServiceStart, +} from '@kbn/core-saved-objects-server-internal'; +import { + CoreDeprecationsRouteHandlerContext, + type InternalDeprecationsServiceStart, +} from '@kbn/core-deprecations-server-internal'; +import { + CoreUiSettingsRouteHandlerContext, + type InternalUiSettingsServiceStart, +} from '@kbn/core-ui-settings-server-internal'; + +/** + * Subset of `InternalCoreStart` used by {@link CoreRouteHandlerContext} + * @internal + */ +export interface CoreRouteHandlerContextParams { + elasticsearch: InternalElasticsearchServiceStart; + savedObjects: InternalSavedObjectsServiceStart; + uiSettings: InternalUiSettingsServiceStart; + deprecations: InternalDeprecationsServiceStart; +} + +/** + * The concrete implementation for Core's route handler context. + * + * @internal + */ +export class CoreRouteHandlerContext implements CoreRequestHandlerContext { + readonly elasticsearch: CoreElasticsearchRouteHandlerContext; + readonly savedObjects: CoreSavedObjectsRouteHandlerContext; + readonly uiSettings: CoreUiSettingsRouteHandlerContext; + readonly deprecations: CoreDeprecationsRouteHandlerContext; + + constructor(coreStart: CoreRouteHandlerContextParams, request: KibanaRequest) { + this.elasticsearch = new CoreElasticsearchRouteHandlerContext(coreStart.elasticsearch, request); + this.savedObjects = new CoreSavedObjectsRouteHandlerContext(coreStart.savedObjects, request); + this.uiSettings = new CoreUiSettingsRouteHandlerContext( + coreStart.uiSettings, + this.savedObjects + ); + this.deprecations = new CoreDeprecationsRouteHandlerContext( + coreStart.deprecations, + this.elasticsearch, + this.savedObjects + ); + } +} diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/index.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/index.ts new file mode 100644 index 0000000000000..48d93f78b2a48 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { CoreRouteHandlerContext } from './core_route_handler_context'; +export type { CoreRouteHandlerContextParams } from './core_route_handler_context'; +export { PrebootCoreRouteHandlerContext } from './preboot_core_route_handler_context'; +export type { PrebootCoreRouteHandlerContextParams } from './preboot_core_route_handler_context'; diff --git a/src/core/server/preboot_core_route_handler_context.test.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.test.ts similarity index 85% rename from src/core/server/preboot_core_route_handler_context.test.ts rename to packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.test.ts index 8d090d8644637..75bfc39bfdc9a 100644 --- a/src/core/server/preboot_core_route_handler_context.test.ts +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.test.ts @@ -7,12 +7,12 @@ */ import { PrebootCoreRouteHandlerContext } from './preboot_core_route_handler_context'; -import { coreMock } from './mocks'; +import { createPrebootCoreRouteHandlerContextParamsMock } from './test_helpers'; describe('#uiSettings', () => { describe('#client', () => { test('returns the results of corePreboot.uiSettings.createDefaultsClient', () => { - const corePreboot = coreMock.createInternalPreboot(); + const corePreboot = createPrebootCoreRouteHandlerContextParamsMock(); const context = new PrebootCoreRouteHandlerContext(corePreboot); const client = context.uiSettings.client; @@ -21,7 +21,7 @@ describe('#uiSettings', () => { }); test('only creates one instance', () => { - const corePreboot = coreMock.createInternalPreboot(); + const corePreboot = createPrebootCoreRouteHandlerContextParamsMock(); const context = new PrebootCoreRouteHandlerContext(corePreboot); const client1 = context.uiSettings.client; diff --git a/src/core/server/preboot_core_route_handler_context.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.ts similarity index 71% rename from src/core/server/preboot_core_route_handler_context.ts rename to packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.ts index ec5ef594180d1..c0bf184d8c985 100644 --- a/src/core/server/preboot_core_route_handler_context.ts +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/preboot_core_route_handler_context.ts @@ -8,13 +8,17 @@ // eslint-disable-next-line max-classes-per-file import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; -import type { InternalCorePreboot } from './internal_types'; +import type { + PrebootUiSettingsRequestHandlerContext, + PrebootCoreRequestHandlerContext, +} from '@kbn/core-http-request-handler-context-server'; +import type { InternalUiSettingsServicePreboot } from '@kbn/core-ui-settings-server-internal'; /** - * @public + * @internal */ -export interface PrebootUiSettingsRequestHandlerContext { - client: IUiSettingsClient; +export interface PrebootCoreRouteHandlerContextParams { + uiSettings: InternalUiSettingsServicePreboot; } /** @@ -25,13 +29,6 @@ class PrebootCoreUiSettingsRouteHandlerContext implements PrebootUiSettingsReque constructor(public readonly client: IUiSettingsClient) {} } -/** - * @public - */ -export interface PrebootCoreRequestHandlerContext { - uiSettings: PrebootUiSettingsRequestHandlerContext; -} - /** * Implementation of {@link PrebootCoreRequestHandlerContext}. * @internal @@ -39,7 +36,7 @@ export interface PrebootCoreRequestHandlerContext { export class PrebootCoreRouteHandlerContext implements PrebootCoreRequestHandlerContext { readonly uiSettings: PrebootUiSettingsRequestHandlerContext; - constructor(private readonly corePreboot: InternalCorePreboot) { + constructor(private readonly corePreboot: PrebootCoreRouteHandlerContextParams) { this.uiSettings = new PrebootCoreUiSettingsRouteHandlerContext( this.corePreboot.uiSettings.createDefaultsClient() ); diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/core_route_handler_context_params.mock.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/core_route_handler_context_params.mock.ts new file mode 100644 index 0000000000000..eaf46eae3c8b6 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/core_route_handler_context_params.mock.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks'; + +export const createCoreRouteHandlerContextParamsMock = () => { + return { + elasticsearch: elasticsearchServiceMock.createInternalStart(), + savedObjects: savedObjectsServiceMock.createInternalStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createInternalStartContract(), + }; +}; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/index.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/index.ts new file mode 100644 index 0000000000000..5556547d4602f --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { createCoreRouteHandlerContextParamsMock } from './core_route_handler_context_params.mock'; +export { createPrebootCoreRouteHandlerContextParamsMock } from './preboot_core_route_handler_context_params.mock'; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/preboot_core_route_handler_context_params.mock.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/preboot_core_route_handler_context_params.mock.ts new file mode 100644 index 0000000000000..dc4eab6b55672 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/test_helpers/preboot_core_route_handler_context_params.mock.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; + +export const createPrebootCoreRouteHandlerContextParamsMock = () => { + return { + uiSettings: uiSettingsServiceMock.createPrebootContract(), + }; +}; diff --git a/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json b/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/http/core-http-request-handler-context-server/BUILD.bazel b/packages/core/http/core-http-request-handler-context-server/BUILD.bazel new file mode 100644 index 0000000000000..45c5ebc08776f --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/BUILD.bazel @@ -0,0 +1,110 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-http-request-handler-context-server" +PKG_REQUIRE_NAME = "@kbn/core-http-request-handler-context-server" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server:npm_module_types", + "//packages/core/deprecations/core-deprecations-server:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/http/core-http-request-handler-context-server/README.md b/packages/core/http/core-http-request-handler-context-server/README.md new file mode 100644 index 0000000000000..b7b191f7345f3 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-request-handler-context-server + +This package contains the public types for Core's server-side `RequestHandlerContext` http subdomain. diff --git a/packages/core/http/core-http-request-handler-context-server/index.ts b/packages/core/http/core-http-request-handler-context-server/index.ts new file mode 100644 index 0000000000000..125c466d25d2c --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { + RequestHandlerContext, + CoreRequestHandlerContext, + CustomRequestHandlerContext, + PrebootCoreRequestHandlerContext, + PrebootRequestHandlerContext, + PrebootUiSettingsRequestHandlerContext, +} from './src'; diff --git a/packages/core/http/core-http-request-handler-context-server/jest.config.js b/packages/core/http/core-http-request-handler-context-server/jest.config.js new file mode 100644 index 0000000000000..dc60767ed0880 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/http/core-http-request-handler-context-server'], +}; diff --git a/packages/core/http/core-http-request-handler-context-server/kibana.jsonc b/packages/core/http/core-http-request-handler-context-server/kibana.jsonc new file mode 100644 index 0000000000000..3fba38b6444e4 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-request-handler-context-server", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-request-handler-context-server/package.json b/packages/core/http/core-http-request-handler-context-server/package.json new file mode 100644 index 0000000000000..da85fc826828d --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-http-request-handler-context-server", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/http/core-http-request-handler-context-server/src/index.ts b/packages/core/http/core-http-request-handler-context-server/src/index.ts new file mode 100644 index 0000000000000..2ab372a0ab975 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { + RequestHandlerContext, + CoreRequestHandlerContext, + CustomRequestHandlerContext, +} from './request_handler_context'; +export type { + PrebootRequestHandlerContext, + PrebootCoreRequestHandlerContext, + PrebootUiSettingsRequestHandlerContext, +} from './preboot_request_handler_context'; diff --git a/packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts b/packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts new file mode 100644 index 0000000000000..62caefe5619c6 --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { RequestHandlerContextBase } from '@kbn/core-http-server'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; + +/** + * @public + */ +export interface PrebootUiSettingsRequestHandlerContext { + client: IUiSettingsClient; +} + +/** + * @public + */ +export interface PrebootCoreRequestHandlerContext { + uiSettings: PrebootUiSettingsRequestHandlerContext; +} + +/** + * @public + */ +export interface PrebootRequestHandlerContext extends RequestHandlerContextBase { + core: Promise; +} diff --git a/src/core/server/core_route_handler_context.ts b/packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts similarity index 53% rename from src/core/server/core_route_handler_context.ts rename to packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts index 14cc99b501436..07704752bd8ed 100644 --- a/src/core/server/core_route_handler_context.ts +++ b/packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts @@ -6,16 +6,11 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; +import type { RequestHandlerContextBase } from '@kbn/core-http-server'; import type { ElasticsearchRequestHandlerContext } from '@kbn/core-elasticsearch-server'; -import { CoreElasticsearchRouteHandlerContext } from '@kbn/core-elasticsearch-server-internal'; import type { SavedObjectsRequestHandlerContext } from '@kbn/core-saved-objects-server'; -import { CoreSavedObjectsRouteHandlerContext } from '@kbn/core-saved-objects-server-internal'; import type { DeprecationsRequestHandlerContext } from '@kbn/core-deprecations-server'; -import { CoreDeprecationsRouteHandlerContext } from '@kbn/core-deprecations-server-internal'; import type { UiSettingsRequestHandlerContext } from '@kbn/core-ui-settings-server'; -import { CoreUiSettingsRouteHandlerContext } from '@kbn/core-ui-settings-server-internal'; -import type { InternalCoreStart } from './internal_types'; /** * The `core` context provided to route handler. @@ -39,27 +34,19 @@ export interface CoreRequestHandlerContext { } /** - * The concrete implementation for Core's route handler context. + * Base context passed to a route handler, containing the `core` context part. * - * @internal + * @public */ -export class CoreRouteHandlerContext implements CoreRequestHandlerContext { - readonly elasticsearch: CoreElasticsearchRouteHandlerContext; - readonly savedObjects: CoreSavedObjectsRouteHandlerContext; - readonly uiSettings: CoreUiSettingsRouteHandlerContext; - readonly deprecations: CoreDeprecationsRouteHandlerContext; - - constructor(coreStart: InternalCoreStart, request: KibanaRequest) { - this.elasticsearch = new CoreElasticsearchRouteHandlerContext(coreStart.elasticsearch, request); - this.savedObjects = new CoreSavedObjectsRouteHandlerContext(coreStart.savedObjects, request); - this.uiSettings = new CoreUiSettingsRouteHandlerContext( - coreStart.uiSettings, - this.savedObjects - ); - this.deprecations = new CoreDeprecationsRouteHandlerContext( - coreStart.deprecations, - this.elasticsearch, - this.savedObjects - ); - } +export interface RequestHandlerContext extends RequestHandlerContextBase { + core: Promise; } + +/** + * Mixin allowing plugins to define their own request handler contexts. + * + * @public + */ +export type CustomRequestHandlerContext = RequestHandlerContext & { + [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; +}; diff --git a/packages/core/http/core-http-request-handler-context-server/tsconfig.json b/packages/core/http/core-http-request-handler-context-server/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/http/core-http-request-handler-context-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index 7e95dc9e302d9..5db20bf45e409 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -18,7 +18,7 @@ import type { InternalHttpServiceSetup, InternalHttpServicePreboot, } from '@kbn/core-http-server-internal'; -import { RequestHandlerContext } from '..'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import { InternalRenderingServicePreboot, InternalRenderingServiceSetup } from '../rendering'; import { InternalHttpResourcesSetup, diff --git a/src/core/server/http_resources/types.ts b/src/core/server/http_resources/types.ts index 397f03098dd13..246a7394c3ade 100644 --- a/src/core/server/http_resources/types.ts +++ b/src/core/server/http_resources/types.ts @@ -15,7 +15,7 @@ import type { KibanaResponseFactory, RequestHandler, } from '@kbn/core-http-server'; -import type { RequestHandlerContext } from '..'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; /** * Allows to configure HTTP response parameters diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 6e72b1d2623cf..020975c15fb1b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -42,7 +42,6 @@ import type { ExecutionContextStart, } from '@kbn/core-execution-context-server'; import type { - RequestHandlerContextBase, IRouter, RequestHandler, KibanaResponseFactory, @@ -69,12 +68,11 @@ import type { CoreUsageDataStart, CoreUsageDataSetup } from '@kbn/core-usage-dat import type { I18nServiceSetup } from '@kbn/core-i18n-server'; import type { StatusServiceSetup } from '@kbn/core-status-server'; import type { UiSettingsServiceSetup, UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; - +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import { HttpResources } from './http_resources'; -import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; -import type { CoreRequestHandlerContext } from './core_route_handler_context'; -import type { PrebootCoreRequestHandlerContext } from './preboot_core_route_handler_context'; +import { PluginsServiceSetup, PluginsServiceStart } from './plugins'; +export type { PluginOpaqueId } from '@kbn/core-base-common'; export type { CoreUsageStats, CoreUsageData, @@ -462,33 +460,13 @@ export type { AnalyticsServicePreboot, AnalyticsServiceStart, } from '@kbn/core-analytics-server'; - -export type { CoreRequestHandlerContext } from './core_route_handler_context'; - -/** - * Base context passed to a route handler, containing the `core` context part. - * - * @public - */ -export interface RequestHandlerContext extends RequestHandlerContextBase { - core: Promise; -} - -/** - * @internal - */ -export interface PrebootRequestHandlerContext extends RequestHandlerContextBase { - core: Promise; -} - -/** - * Mixin allowing plugins to define their own request handler contexts. - * - * @public - */ -export type CustomRequestHandlerContext = RequestHandlerContext & { - [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; -}; +export type { + RequestHandlerContext, + CoreRequestHandlerContext, + CustomRequestHandlerContext, + PrebootRequestHandlerContext, + PrebootCoreRequestHandlerContext, +} from '@kbn/core-http-request-handler-context-server'; /** * Context passed to the `setup` method of `preboot` plugins. @@ -599,7 +577,6 @@ export type { HttpResources, PluginsServiceSetup, PluginsServiceStart, - PluginOpaqueId, }; /** diff --git a/src/core/server/server.ts b/src/core/server/server.ts index aa747a1023b35..e333e8b81a404 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -58,16 +58,21 @@ import { import { CoreUsageDataService } from '@kbn/core-usage-data-server-internal'; import { StatusService, statusConfig } from '@kbn/core-status-server-internal'; import { UiSettingsService, uiSettingsConfig } from '@kbn/core-ui-settings-server-internal'; +import { + CoreRouteHandlerContext, + PrebootCoreRouteHandlerContext, +} from '@kbn/core-http-request-handler-context-server-internal'; +import type { + RequestHandlerContext, + PrebootRequestHandlerContext, +} from '@kbn/core-http-request-handler-context-server'; import { CoreApp } from './core_app'; import { HttpResourcesService } from './http_resources'; import { RenderingService } from './rendering'; import { PluginsService, config as pluginsConfig } from './plugins'; import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from './internal_types'; -import { CoreRouteHandlerContext } from './core_route_handler_context'; -import { PrebootCoreRouteHandlerContext } from './preboot_core_route_handler_context'; import { DiscoveredPlugins } from './plugins'; -import type { RequestHandlerContext, PrebootRequestHandlerContext } from '.'; const coreId = Symbol('core'); const rootConfigPath = ''; diff --git a/yarn.lock b/yarn.lock index f809bc0cac463..085f55146d4ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2954,6 +2954,14 @@ version "0.0.0" uid "" +"@kbn/core-http-request-handler-context-server-internal@link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal": + version "0.0.0" + uid "" + +"@kbn/core-http-request-handler-context-server@link:bazel-bin/packages/core/http/core-http-request-handler-context-server": + version "0.0.0" + uid "" + "@kbn/core-http-router-server-internal@link:bazel-bin/packages/core/http/core-http-router-server-internal": version "0.0.0" uid "" @@ -7075,6 +7083,14 @@ version "0.0.0" uid "" +"@types/kbn__core-http-request-handler-context-server-internal@link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-http-request-handler-context-server@link:bazel-bin/packages/core/http/core-http-request-handler-context-server/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-http-router-server-internal@link:bazel-bin/packages/core/http/core-http-router-server-internal/npm_module_types": version "0.0.0" uid "" From 5c2476a5bbb1296bb0a8c2adb3fa830fbb459f7f Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Wed, 28 Sep 2022 00:07:02 -0700 Subject: [PATCH 108/172] Fix getFilter not returning excluded ids when there is no rules list filter (#142033) --- .../hooks/use_bulk_edit_select.test.tsx | 16 ++++++++++++++++ .../application/hooks/use_bulk_edit_select.tsx | 11 +++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx index b0e92a3d3a848..07bf3516fbdef 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx @@ -86,6 +86,22 @@ describe('useBulkEditSelectTest', () => { expect(result.current.getFilter()).toEqual(null); }); + it('getFilter should return rule list filter when selecting all with excluded ids', async () => { + const { result } = renderHook(() => + useBulkEditSelect({ + items, + totalItemCount: 4, + }) + ); + + act(() => { + result.current.onSelectAll(); + result.current.onSelectRow(items[0]); + }); + + expect(result.current.getFilter()?.arguments.length).toEqual(1); + }); + it('getFilter should return rule list filter when selecting all', async () => { const { result } = renderHook(() => useBulkEditSelect({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx index 1e2ff7496907c..31c248ae1254e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.tsx @@ -192,14 +192,13 @@ export function useBulkEditSelect(props: UseBulkEditSelectProps) { }); if (idsToExclude && idsToExclude.length) { + const excludeFilter = fromKueryExpression( + `NOT (${idsToExclude.map((id) => `alert.id: "alert:${id}"`).join(' or ')})` + ); if (ruleFilterKueryNode) { - return nodeBuilder.and([ - ruleFilterKueryNode, - fromKueryExpression( - `NOT (${idsToExclude.map((id) => `alert.id: "alert:${id}"`).join(' or ')})` - ), - ]); + return nodeBuilder.and([ruleFilterKueryNode, excludeFilter]); } + return excludeFilter; } return ruleFilterKueryNode; From e8ebc23dca3be8c2bcda15cda7991e9e5aff4df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 28 Sep 2022 09:08:36 +0200 Subject: [PATCH 109/172] [fleet] Use snapshot registry in dev mode (#141797) * [fleet] Use snapshot registry in dev mode * Update registry_url.ts * Update registry_url.ts * Fix jest test --- .../plugins/fleet/server/services/epm/registry/index.test.ts | 1 + .../plugins/fleet/server/services/epm/registry/registry_url.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts index 6504b6548f078..bf5fe89574b8a 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts @@ -35,6 +35,7 @@ jest.mock('../..', () => ({ getKibanaBranch: () => 'main', getKibanaVersion: () => '99.0.0', getConfig: () => ({}), + getIsProductionMode: () => false, }, })); diff --git a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts index 407a61ad7a912..31bb776ee1d69 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts @@ -21,7 +21,8 @@ const SNAPSHOT_REGISTRY_URL_CDN = 'https://epr-snapshot.elastic.co'; const getDefaultRegistryUrl = (): string => { const branch = appContextService.getKibanaBranch(); - if (branch === 'main') { + const isProduction = appContextService.getIsProductionMode(); + if (!isProduction || branch === 'main') { return SNAPSHOT_REGISTRY_URL_CDN; } else if (appContextService.getKibanaVersion().includes('-SNAPSHOT')) { return STAGING_REGISTRY_URL_CDN; From 2b094777593e715aeac6fb9678eb48ccd03b5a7a Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 28 Sep 2022 09:06:15 +0100 Subject: [PATCH 110/172] [ML] Improving log pattern analysis messaging (#141130) * [ML] Improving log pattern analysis messaging * moving to separate component * translations * fixing unselection when time range changes * changes based on review --- .../log_categorization/information_text.tsx | 101 ++++++++++++++++++ .../log_categorization_page.tsx | 14 ++- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/aiops/public/components/log_categorization/information_text.tsx diff --git a/x-pack/plugins/aiops/public/components/log_categorization/information_text.tsx b/x-pack/plugins/aiops/public/components/log_categorization/information_text.tsx new file mode 100644 index 0000000000000..bf80b55fee37c --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/information_text.tsx @@ -0,0 +1,101 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; + +interface Props { + eventRateLength: number; + fieldSelected: boolean; + categoriesLength: number | null; + loading: boolean; +} + +export const InformationText: FC = ({ + eventRateLength, + fieldSelected, + categoriesLength, + loading, +}) => { + if (loading === true) { + return null; + } + return ( + <> + {eventRateLength === 0 ? ( + + + + } + titleSize="xs" + body={ +

    + +

    + } + data-test-subj="aiopsNoWindowParametersEmptyPrompt" + /> + ) : null} + + {eventRateLength > 0 && categoriesLength === null ? ( + + + + } + titleSize="xs" + body={ +

    + +

    + } + data-test-subj="aiopsNoWindowParametersEmptyPrompt" + /> + ) : null} + + {eventRateLength > 0 && categoriesLength !== null && categoriesLength === 0 ? ( + + + + } + titleSize="xs" + body={ +

    + +

    + } + data-test-subj="aiopsNoWindowParametersEmptyPrompt" + /> + ) : null} + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 845a4239f1104..4eb6da0e58b9f 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -40,6 +40,7 @@ import { useCategorizeRequest } from './use_categorize_request'; import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request'; import { CategoryTable } from './category_table'; import { DocumentCountChart } from './document_count_chart'; +import { InformationText } from './information_text'; export interface LogCategorizationPageProps { dataView: DataView; @@ -160,6 +161,7 @@ export const LogCategorizationPage: FC = ({ docCount, })) ); + setCategories(null); setTotalCount(documentStats.totalCount); } }, [documentStats, earliest, latest, searchQueryLanguage, searchString, searchQuery]); @@ -210,6 +212,7 @@ export const LogCategorizationPage: FC = ({ ]); const onFieldChange = (value: EuiComboBoxOptionOption[] | undefined) => { + setCategories(null); setSelectedField(value && value.length ? value[0].label : undefined); }; @@ -313,8 +316,17 @@ export const LogCategorizationPage: FC = ({ ) : null} + {loading === true ? : null} - {categories !== null ? ( + + + + {selectedField !== undefined && categories !== null && categories.length > 0 ? ( Date: Wed, 28 Sep 2022 10:11:37 +0200 Subject: [PATCH 111/172] [Fleet] Fix for bulk update tags, updated API tests (#141905) * added action_status check for api tests * fix for update tags quickly * fixed test * updated tag labels * close bulk action menu when closing Add / remove tags popover * fixed test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/bulk_actions.tsx | 1 + .../components/tags_add_remove.test.tsx | 53 +++++++++++++++++++ .../components/tags_add_remove.tsx | 25 ++++++--- .../fleet/server/services/agents/actions.ts | 1 + .../apis/agents/reassign.ts | 17 ++++-- .../apis/agents/unenroll.ts | 7 +++ .../apis/agents/update_agent_tags.ts | 48 +++++++++++++---- .../apis/agents/upgrade.ts | 13 +++-- 8 files changed, 142 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index bb0ec90f2b883..10ced1a5c0323 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -228,6 +228,7 @@ export const AgentBulkActions: React.FunctionComponent = ({ }} onClosePopover={() => { setIsTagAddVisible(false); + closeMenu(); }} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx index 8c4f9f3003c82..465db5236338c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx @@ -270,6 +270,59 @@ describe('TagsAddRemove', () => { ); }); + it('should add new tag twice quickly when not found in search and button clicked - bulk selection', () => { + mockBulkUpdateTags.mockImplementation((agents, tagsToAdd, tagsToRemove, onSuccess) => + onSuccess(false) + ); + + const result = renderComponent(undefined, 'query'); + const searchInput = result.getByRole('combobox'); + + fireEvent.input(searchInput, { + target: { value: 'newTag' }, + }); + + fireEvent.click(result.getAllByText('Create a new tag "newTag"')[0].closest('button')!); + + fireEvent.input(searchInput, { + target: { value: 'newTag2' }, + }); + + fireEvent.click(result.getAllByText('Create a new tag "newTag2"')[0].closest('button')!); + + expect(mockBulkUpdateTags).toHaveBeenCalledWith( + 'query', + ['newTag2', 'newTag'], + [], + expect.anything(), + 'Tag created', + 'Tag creation failed' + ); + }); + + it('should remove tags twice quickly on bulk selection', () => { + selectedTags = ['tag1', 'tag2']; + mockBulkUpdateTags.mockImplementation((agents, tagsToAdd, tagsToRemove, onSuccess) => + onSuccess(false) + ); + + const result = renderComponent(undefined, ''); + const getTag = (name: string) => result.getByText(name); + + fireEvent.click(getTag('tag1')); + + fireEvent.click(getTag('tag2')); + + expect(mockBulkUpdateTags).toHaveBeenCalledWith( + '', + [], + ['tag2', 'tag1'], + expect.anything(), + undefined, + undefined + ); + }); + it('should make tag options button visible on mouse enter', async () => { const result = renderComponent('agent1'); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx index a03ec3808e9ab..70b4da44dad68 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useEffect, useState, useMemo, useCallback } from 'react'; -import { difference } from 'lodash'; +import { difference, uniq } from 'lodash'; import styled from 'styled-components'; import type { EuiSelectableOption } from '@elastic/eui'; import { @@ -95,8 +95,9 @@ export const TagsAddRemove: React.FC = ({ if (hasCompleted) { return onTagsUpdated(); } - const newSelectedTags = difference(selectedTags, tagsToRemove).concat(tagsToAdd); - const allTagsWithNew = allTags.includes(tagsToAdd[0]) ? allTags : allTags.concat(tagsToAdd); + const selected = labels.filter((tag) => tag.checked === 'on').map((tag) => tag.label); + const newSelectedTags = difference(selected, tagsToRemove).concat(tagsToAdd); + const allTagsWithNew = uniq(allTags.concat(newSelectedTags)); const allTagsWithRemove = isRenameOrDelete ? difference(allTagsWithNew, tagsToRemove) : allTagsWithNew; @@ -109,8 +110,8 @@ export const TagsAddRemove: React.FC = ({ successMessage?: string, errorMessage?: string ) => { - const newSelectedTags = difference(selectedTags, tagsToRemove).concat(tagsToAdd); if (agentId) { + const newSelectedTags = difference(selectedTags, tagsToRemove).concat(tagsToAdd); updateTagsHook.updateTags( agentId, newSelectedTags, @@ -119,10 +120,22 @@ export const TagsAddRemove: React.FC = ({ errorMessage ); } else { + // sending updated tags to add/remove, in case multiple actions are done quickly and the first one is not yet propagated + const updatedTagsToAdd = tagsToAdd.concat( + labels + .filter((tag) => tag.checked === 'on' && !selectedTags.includes(tag.label)) + .map((tag) => tag.label) + ); + const updatedTagsToRemove = tagsToRemove.concat( + labels + .filter((tag) => tag.checked !== 'on' && selectedTags.includes(tag.label)) + .map((tag) => tag.label) + ); + updateTagsHook.bulkUpdateTags( agents!, - tagsToAdd, - tagsToRemove, + updatedTagsToAdd, + updatedTagsToRemove, (hasCompleted) => handleTagsUpdated(tagsToAdd, tagsToRemove, hasCompleted), successMessage, errorMessage diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index 4a6c772b69b94..17c745bfd285f 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -134,6 +134,7 @@ export async function bulkCreateAgentActionResults( await esClient.bulk({ index: AGENT_ACTIONS_RESULTS_INDEX, body: bulkBody, + refresh: 'wait_for', }); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 1283c9433ebba..2dd546511dd9a 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -121,9 +121,18 @@ export default function (providerContext: FtrProviderContext) { ]); expect(agent2data.body.item.policy_id).to.eql('policy2'); expect(agent3data.body.item.policy_id).to.eql('policy2'); + + const { body } = await supertest + .get(`/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + const actionStatus = body.items[0]; + + expect(actionStatus.status).to.eql('FAILED'); + expect(actionStatus.nbAgentsActionCreated).to.eql(2); + expect(actionStatus.nbAgentsFailed).to.eql(3); }); - it('should allow to reassign multiple agents by id -- mixed invalid, hosted, etc', async () => { + it('should return error when none of the agents can be reassigned -- mixed invalid, hosted, etc', async () => { // agent1 is enrolled in policy1. set policy1 to hosted await supertest .put(`/api/fleet/agent_policies/policy1`) @@ -131,13 +140,15 @@ export default function (providerContext: FtrProviderContext) { .send({ name: 'Test policy', namespace: 'default', is_managed: true }) .expect(200); - await supertest + const { body } = await supertest .post(`/api/fleet/agents/bulk_reassign`) .set('kbn-xsrf', 'xxx') .send({ agents: ['agent2', 'INVALID_ID', 'agent3'], policy_id: 'policy2', - }); + }) + .expect(400); + expect(body.message).to.eql('No agents to reassign, already assigned or hosted agents'); const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index fe31806038a5c..7f778d77f1a51 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -140,6 +140,13 @@ export default function (providerContext: FtrProviderContext) { expect(typeof agent3data.body.item.unenrollment_started_at).to.be('undefined'); expect(typeof agent3data.body.item.unenrolled_at).to.be('undefined'); expect(agent2data.body.item.active).to.eql(true); + + const { body } = await supertest + .get(`/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + const actionStatus = body.items[0]; + expect(actionStatus.status).to.eql('FAILED'); + expect(actionStatus.nbAgentsFailed).to.eql(2); }); it('/agents/bulk_unenroll should allow to unenroll multiple agents by id from an regular agent policy', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts b/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts index 88079ae5e1aff..ff11076addcec 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/update_agent_tags.ts @@ -88,27 +88,46 @@ export default function (providerContext: FtrProviderContext) { }); it('should bulk update tags of multiple agents by kuery in batches', async () => { - await supertest + const { body: actionBody } = await supertest .post(`/api/fleet/agents/bulk_update_agent_tags`) .set('kbn-xsrf', 'xxx') .send({ agents: 'active: true', tagsToAdd: ['newTag'], tagsToRemove: ['existingTag'], - batchSize: 2, + batchSize: 3, }) .expect(200); + const actionId = actionBody.actionId; + + const verifyActionResult = async () => { + const { body } = await supertest.get(`/api/fleet/agents`).set('kbn-xsrf', 'xxx'); + expect(body.total).to.eql(4); + body.items.forEach((agent: any) => { + expect(agent.tags.includes('newTag')).to.be(true); + expect(agent.tags.includes('existingTag')).to.be(false); + }); + }; + await new Promise((resolve, reject) => { - setTimeout(async () => { - const { body } = await supertest.get(`/api/fleet/agents`).set('kbn-xsrf', 'xxx'); - expect(body.total).to.eql(4); - body.items.forEach((agent: any) => { - expect(agent.tags.includes('newTag')).to.be(true); - expect(agent.tags.includes('existingTag')).to.be(false); - }); - resolve({}); - }, 2000); + let attempts = 0; + const intervalId = setInterval(async () => { + if (attempts > 4) { + clearInterval(intervalId); + reject('action timed out'); + } + ++attempts; + const { + body: { items: actionStatuses }, + } = await supertest.get(`/api/fleet/agents/action_status`).set('kbn-xsrf', 'xxx'); + const action = actionStatuses.find((a: any) => a.actionId === actionId); + if (action && action.nbAgentsAck === 4) { + clearInterval(intervalId); + await verifyActionResult(); + resolve({}); + } + }, 1000); }).catch((e) => { throw e; }); @@ -156,6 +175,13 @@ export default function (providerContext: FtrProviderContext) { expect(agent1data.body.item.tags.includes('newTag')).to.be(false); expect(agent2data.body.item.tags.includes('newTag')).to.be(true); + + const { body } = await supertest + .get(`/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + const actionStatus = body.items[0]; + expect(actionStatus.status).to.eql('FAILED'); + expect(actionStatus.nbAgentsFailed).to.eql(1); }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index e2762e21f33ad..e8dad8624021f 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -608,7 +608,7 @@ export default function (providerContext: FtrProviderContext) { .send({ agents: 'active:true', version: fleetServerVersion, - batchSize: 2, + batchSize: 3, }) .expect(200); @@ -626,7 +626,7 @@ export default function (providerContext: FtrProviderContext) { await new Promise((resolve, reject) => { let attempts = 0; const intervalId = setInterval(async () => { - if (attempts > 2) { + if (attempts > 4) { clearInterval(intervalId); reject('action timed out'); } @@ -636,7 +636,7 @@ export default function (providerContext: FtrProviderContext) { } = await supertest.get(`/api/fleet/agents/action_status`).set('kbn-xsrf', 'xxx'); const action = actionStatuses.find((a: any) => a.actionId === actionId); // 2 upgradeable - if (action && action.nbAgentsActionCreated === 2) { + if (action && action.nbAgentsActionCreated === 2 && action.nbAgentsFailed === 3) { clearInterval(intervalId); await verifyActionResult(); resolve({}); @@ -1032,6 +1032,13 @@ export default function (providerContext: FtrProviderContext) { expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); + + const { body } = await supertest + .get(`/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + const actionStatus = body.items[0]; + expect(actionStatus.status).to.eql('FAILED'); + expect(actionStatus.nbAgentsFailed).to.eql(1); }); it('enrolled in a hosted agent policy bulk upgrade with force flag should respond with 200 and update the agent SOs', async () => { From 7d69aae2696696342382b5f66267826dd9967cb6 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 28 Sep 2022 09:54:33 +0100 Subject: [PATCH 112/172] [ML] Fixing case of log pattern analysis title (#142034) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/ml/public/application/aiops/log_categorization.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx index e1d816d61357a..899006b5918dd 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx @@ -34,7 +34,7 @@ export const LogCategorizationPage: FC = () => { From 8f793cb83c167da32d696715e5b1f1a65f8c1c6c Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 28 Sep 2022 12:39:26 +0300 Subject: [PATCH 113/172] [TSVB][Lens] Navigate to Lens TSVB Metric (#140878) * Added tsvb metric converting to Lens. * Allowed to convert to lens with invalid color rules. * Fixed static value converting. * Fixed tests. * Added unit tests for getPalette. * Added functional tests for converting to Lens. Co-authored-by: Yaroslav Kuznietsov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/convert_to_lens/index.test.ts | 2 +- .../public/convert_to_lens/index.ts | 4 + .../lib/configurations/metric/index.ts | 67 +++++ .../lib/configurations/metric/palette.test.ts | 177 ++++++++++++ .../lib/configurations/metric/palette.ts | 214 ++++++++++++++ .../convert_to_lens/lib/convert/index.ts | 2 +- .../lib/convert/static_value.ts | 22 +- .../convert_to_lens/lib/convert/types.ts | 2 +- .../lib/metrics/supported_metrics.ts | 7 +- .../lib/series/metrics_columns.test.ts | 14 +- .../lib/series/metrics_columns.ts | 16 +- .../convert_to_lens/metric/index.test.ts | 272 ++++++++++++++++++ .../public/convert_to_lens/metric/index.ts | 130 +++++++++ .../convert_to_lens/metric/utils.test.ts | 104 +++++++ .../public/convert_to_lens/metric/utils.ts | 65 +++++ .../convert_to_lens/timeseries/index.ts | 4 +- .../public/convert_to_lens/top_n/index.ts | 4 +- .../public/convert_to_lens/types.ts | 8 +- .../convert_to_lens/types/configurations.ts | 24 +- .../visualizations/metric/suggestions.test.ts | 8 + .../visualizations/metric/suggestions.ts | 2 +- .../visualizations/metric/visualization.tsx | 40 ++- .../apps/lens/group3/tsvb_open_in_lens.ts | 13 +- 23 files changed, 1179 insertions(+), 22 deletions(-) create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts create mode 100644 src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts index 435335fe9dd25..309f066b18f29 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts @@ -40,7 +40,7 @@ describe('convertTSVBtoLensConfiguration', () => { test('should return null for a not supported chart', async () => { const metricModel = { ...model, - type: 'metric', + type: 'markdown', } as Panel; const triggerOptions = await convertTSVBtoLensConfiguration(metricModel); expect(triggerOptions).toBeNull(); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts index 5b92c0ab21668..a64118a1cb507 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts @@ -21,6 +21,10 @@ const getConvertFnByType = (type: PANEL_TYPES) => { const { convertToLens } = await import('./top_n'); return convertToLens; }, + [PANEL_TYPES.METRIC]: async () => { + const { convertToLens } = await import('./metric'); + return convertToLens; + }, }; return convertionFns[type]?.(); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts new file mode 100644 index 0000000000000..d1f24485d7646 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { Metric, Panel, Series } from '../../../../../common/types'; +import { Column, Layer } from '../../convert'; +import { getSeriesAgg } from '../../series'; +import { getPalette } from './palette'; + +const getMetricWithCollapseFn = (series: Series | undefined) => { + if (!series) { + return; + } + const { metrics, seriesAgg } = getSeriesAgg(series.metrics); + const visibleMetric = metrics[metrics.length - 1]; + return { metric: visibleMetric, collapseFn: seriesAgg }; +}; + +const findMetricColumn = (metric: Metric | undefined, columns: Column[]) => { + if (!metric) { + return; + } + + return columns.find((column) => 'meta' in column && column.meta.metricId === metric.id); +}; + +export const getConfigurationForMetric = ( + model: Panel, + layer: Layer, + bucket?: Column +): MetricVisConfiguration | null => { + const [primarySeries, secondarySeries] = model.series.filter(({ hidden }) => !hidden); + + const primaryMetricWithCollapseFn = getMetricWithCollapseFn(primarySeries); + + if (!primaryMetricWithCollapseFn || !primaryMetricWithCollapseFn.metric) { + return null; + } + + const secondaryMetricWithCollapseFn = getMetricWithCollapseFn(secondarySeries); + const primaryColumn = findMetricColumn(primaryMetricWithCollapseFn.metric, layer.columns); + const secondaryColumn = findMetricColumn(secondaryMetricWithCollapseFn?.metric, layer.columns); + + if (primaryMetricWithCollapseFn.collapseFn && secondaryMetricWithCollapseFn?.collapseFn) { + return null; + } + + const palette = getPalette(model.background_color_rules ?? []); + if (palette === null) { + return null; + } + + return { + layerId: layer.layerId, + layerType: 'data', + metricAccessor: primaryColumn?.columnId, + secondaryMetricAccessor: secondaryColumn?.columnId, + breakdownByAccessor: bucket?.columnId, + palette, + collapseFn: primaryMetricWithCollapseFn.collapseFn ?? secondaryMetricWithCollapseFn?.collapseFn, + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts new file mode 100644 index 0000000000000..827dc15ff171b --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getPalette } from './palette'; + +describe('getPalette', () => { + const invalidRules = [ + { id: 'some-id-0' }, + { id: 'some-id-1', value: 10 }, + { id: 'some-id-2', operator: 'gte' }, + { id: 'some-id-3', color: '#000' }, + { id: 'some-id-4', background_color: '#000' }, + ]; + test('should return undefined if no filled rules was provided', () => { + expect(getPalette([])).toBeUndefined(); + expect(getPalette(invalidRules)).toBeUndefined(); + }); + + test('should return undefined if only one valid rule is provided and it is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gt', value: 100, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); + + test('should return custom palette if only one valid rule is provided and it is lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + ]) + ).toEqual({ + name: 'custom', + params: { + colorStops: [{ color: '#000000', stop: 100 }], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 1, + stops: [{ color: '#000000', stop: 100 }], + }, + type: 'palette', + }); + }); + + test('should return undefined if more than two types of rules', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); + + test('should return undefined if two types of rules and last rule is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); + + test('should return undefined if all rules are lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lte', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); + + test('should return undefined if two types of rules and all except last one are lt and last one is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'gte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); + + test('should return custom palette if two types of rules and all except last one is lt and last one is lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, + ]) + ).toEqual({ + name: 'custom', + params: { + colorStops: [ + { color: '#000000', stop: -Infinity }, + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 4, + stops: [ + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); + }); + + test('should return custom palette if last one is lte and all previous are gte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + ]) + ).toEqual({ + name: 'custom', + params: { + colorStops: [ + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: 100, + rangeType: 'number', + reverse: false, + steps: 2, + stops: [ + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts new file mode 100644 index 0000000000000..55741c57595e7 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import color from 'color'; +import { ColorStop, CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; +import { uniqBy } from 'lodash'; +import { Panel } from '../../../../../common/types'; + +const Operators = { + GTE: 'gte', + GT: 'gt', + LTE: 'lte', + LT: 'lt', +} as const; + +type ColorStopsWithMinMax = Pick< + CustomPaletteParams, + 'colorStops' | 'stops' | 'steps' | 'rangeMax' | 'rangeMin' | 'continuity' +>; + +const getColorStopsWithMinMaxForAllGteOrWithLte = ( + rules: Exclude, + tailOperator: string +): ColorStopsWithMinMax => { + const lastRule = rules[rules.length - 1]; + const lastRuleColor = (lastRule.background_color ?? lastRule.color)!; + + const colorStops = rules.reduce((colors, rule, index, rulesArr) => { + const rgbColor = (rule.background_color ?? rule.color)!; + if (index === rulesArr.length - 1 && tailOperator === Operators.LTE) { + return colors; + } + // if last operation is LTE, color of gte should be replaced by lte + if (index === rulesArr.length - 2 && tailOperator === Operators.LTE) { + return [ + ...colors, + { + color: color(lastRuleColor).hex(), + stop: rule.value!, + }, + ]; + } + return [ + ...colors, + { + color: color(rgbColor).hex(), + stop: rule.value!, + }, + ]; + }, []); + + const stops = colorStops.reduce((prevStops, colorStop, index, colorStopsArr) => { + if (index === colorStopsArr.length - 1) { + return [ + ...prevStops, + { + color: colorStop.color, + stop: tailOperator === Operators.LTE ? lastRule.value! : colorStop.stop + 1, + }, + ]; + } + return [...prevStops, { color: colorStop.color, stop: colorStopsArr[index + 1].stop }]; + }, []); + + const [rule] = rules; + return { + rangeMin: rule.value, + rangeMax: tailOperator === Operators.LTE ? lastRule.value : Infinity, + colorStops, + stops, + steps: colorStops.length, + continuity: tailOperator === Operators.LTE ? 'none' : 'above', + }; +}; + +const getColorStopsWithMinMaxForLtWithLte = ( + rules: Exclude +): ColorStopsWithMinMax => { + const lastRule = rules[rules.length - 1]; + const colorStops = rules.reduce((colors, rule, index, rulesArr) => { + if (index === 0) { + return [{ color: color((rule.background_color ?? rule.color)!).hex(), stop: -Infinity }]; + } + const rgbColor = (rule.background_color ?? rule.color)!; + return [ + ...colors, + { + color: color(rgbColor).hex(), + stop: rulesArr[index - 1].value!, + }, + ]; + }, []); + + const stops = colorStops.reduce((prevStops, colorStop, index, colorStopsArr) => { + if (index === colorStopsArr.length - 1) { + return [ + ...prevStops, + { + color: colorStop.color, + stop: lastRule.value!, + }, + ]; + } + return [...prevStops, { color: colorStop.color, stop: colorStopsArr[index + 1].stop }]; + }, []); + + return { + rangeMin: -Infinity, + rangeMax: lastRule.value, + colorStops, + stops, + steps: colorStops.length + 1, + continuity: 'below', + }; +}; + +const getColorStopWithMinMaxForLte = ( + rule: Exclude[number] +): ColorStopsWithMinMax => { + const colorStop = { + color: color((rule.background_color ?? rule.color)!).hex(), + stop: rule.value!, + }; + return { + rangeMin: -Infinity, + rangeMax: rule.value!, + colorStops: [colorStop], + stops: [colorStop], + steps: 1, + continuity: 'below', + }; +}; + +const getCustomPalette = ( + colorStopsWithMinMax: ColorStopsWithMinMax +): PaletteOutput => { + return { + name: 'custom', + params: { + continuity: 'all', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: Infinity, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + ...colorStopsWithMinMax, + }, + type: 'palette', + }; +}; + +export const getPalette = ( + rules: Exclude +): PaletteOutput | null | undefined => { + const validRules = + rules.filter( + ({ operator, color: textColor, value, background_color: bColor }) => + operator && (bColor ?? textColor) && value !== undefined + ) ?? []; + + validRules.sort((rule1, rule2) => { + return rule1.value! - rule2.value!; + }); + + const kindOfRules = uniqBy(validRules, 'operator'); + + if (!kindOfRules.length) { + return; + } + + // lnsMetric is supporting lte only, if one rule is defined + if (validRules.length === 1) { + if (validRules[0].operator !== Operators.LTE) { + return; + } + return getCustomPalette(getColorStopWithMinMaxForLte(validRules[0])); + } + + const headRules = validRules.slice(0, -1); + const tailRule = validRules[validRules.length - 1]; + const kindOfHeadRules = uniqBy(headRules, 'operator'); + + if ( + kindOfHeadRules.length > 1 || + (kindOfHeadRules[0].operator !== tailRule.operator && tailRule.operator !== Operators.LTE) + ) { + return; + } + + const [rule] = kindOfHeadRules; + + if (rule.operator === Operators.LTE) { + return; + } + + if (rule.operator === Operators.LT) { + if (tailRule.operator !== Operators.LTE) { + return; + } + return getCustomPalette(getColorStopsWithMinMaxForLtWithLte(validRules)); + } + + if (rule.operator === Operators.GTE) { + return getCustomPalette( + getColorStopsWithMinMaxForAllGteOrWithLte(validRules, tailRule.operator!) + ); + } +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts index 36f05c440bdc2..e03701e6ea153 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts @@ -17,7 +17,7 @@ export { export { convertToCumulativeSumColumns } from './cumulative_sum'; export { convertFilterRatioToFormulaColumn } from './filter_ratio'; export { convertToLastValueColumn } from './last_value'; -export { convertToStaticValueColumn } from './static_value'; +export { convertToStaticValueColumn, convertStaticValueToFormulaColumn } from './static_value'; export { convertToFiltersColumn } from './filters'; export { convertToDateHistogramColumn } from './date_histogram'; export { convertToTermsColumn } from './terms'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts index c4400f72b289b..e03a9d7821364 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts @@ -7,9 +7,10 @@ */ import { StaticValueParams } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { CommonColumnsConverterArgs, StaticValueColumn } from './types'; +import { CommonColumnsConverterArgs, FormulaColumn, StaticValueColumn } from './types'; import type { Metric } from '../../../../common/types'; import { createColumn, getFormat } from './column'; +import { createFormulaColumn } from './formula'; export const convertToStaticValueParams = ({ value }: Metric): StaticValueParams => ({ value, @@ -37,3 +38,22 @@ export const convertToStaticValueColumn = ( }, }; }; + +export const convertStaticValueToFormulaColumn = ( + { series, metrics, dataView }: CommonColumnsConverterArgs, + { + visibleSeriesCount = 0, + reducedTimeRange, + }: { visibleSeriesCount?: number; reducedTimeRange?: string } = {} +): FormulaColumn | null => { + // Lens support reference lines only when at least one layer data exists + if (visibleSeriesCount === 1) { + return null; + } + const currentMetric = metrics[metrics.length - 1]; + return createFormulaColumn(currentMetric.value ?? '', { + series, + metric: currentMetric, + dataView, + }); +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts index d21291dc2622a..e5b862a0fe70f 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts @@ -85,7 +85,7 @@ export type MovingAverageColumn = GenericColumnWithMeta; export type StaticValueColumn = GenericColumnWithMeta; -type ColumnsWithoutMeta = FiltersColumn | TermsColumn | DateHistogramColumn; +export type ColumnsWithoutMeta = FiltersColumn | TermsColumn | DateHistogramColumn; export type AnyColumnWithReferences = GenericColumnWithMeta; type CommonColumns = Exclude; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index 933e7e344b7fd..76d15793f4516 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -62,7 +62,12 @@ export type SupportedMetrics = LocalSupportedMetrics & { [Key in UnsupportedSupportedMetrics]?: null; }; -const supportedPanelTypes: readonly PANEL_TYPES[] = [PANEL_TYPES.TIMESERIES, PANEL_TYPES.TOP_N]; +const supportedPanelTypes: readonly PANEL_TYPES[] = [ + PANEL_TYPES.TIMESERIES, + PANEL_TYPES.TOP_N, + PANEL_TYPES.METRIC, +]; + const supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[] = [ TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, TIME_RANGE_DATA_MODES.LAST_VALUE, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts index 5ca1fe71a0ada..4461072c8df62 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts @@ -20,6 +20,7 @@ const mockConvertToCounterRateColumn = jest.fn(); const mockConvertOtherAggsToFormulaColumn = jest.fn(); const mockConvertToLastValueColumn = jest.fn(); const mockConvertToStaticValueColumn = jest.fn(); +const mockConvertStaticValueToFormulaColumn = jest.fn(); const mockConvertToStandartDeviationColumn = jest.fn(); const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); @@ -32,6 +33,7 @@ jest.mock('../convert', () => ({ convertOtherAggsToFormulaColumn: jest.fn(() => mockConvertOtherAggsToFormulaColumn()), convertToLastValueColumn: jest.fn(() => mockConvertToLastValueColumn()), convertToStaticValueColumn: jest.fn(() => mockConvertToStaticValueColumn()), + convertStaticValueToFormulaColumn: jest.fn(() => mockConvertStaticValueToFormulaColumn()), convertToStandartDeviationColumn: jest.fn(() => mockConvertToStandartDeviationColumn()), convertMetricAggregationColumnWithoutSpecialParams: jest.fn(() => mockConvertMetricAggregationColumnWithoutSpecialParams() @@ -138,8 +140,18 @@ describe('getMetricsColumns', () => { mockConvertToLastValueColumn, ], [ - 'call convertToStaticValueColumn if metric type is static', + 'call convertStaticValueToFormulaColumn if metric type is static', [createSeries({ metrics: [{ type: TSVB_METRIC_TYPES.STATIC, id: '1' }] }), dataView, 1], + mockConvertStaticValueToFormulaColumn, + ], + [ + 'call convertToStaticValueColumn if metric type is static and isStaticValueColumnSupported is true', + [ + createSeries({ metrics: [{ type: TSVB_METRIC_TYPES.STATIC, id: '1' }] }), + dataView, + 1, + { isStaticValueColumnSupported: true }, + ], mockConvertToStaticValueColumn, ], [ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts index 07294a3a61aaa..8f7d4ded0d076 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts @@ -21,6 +21,7 @@ import { convertFilterRatioToFormulaColumn, convertToLastValueColumn, convertToStaticValueColumn, + convertStaticValueToFormulaColumn, convertMetricAggregationColumnWithoutSpecialParams, convertToCounterRateColumn, convertToStandartDeviationColumn, @@ -31,7 +32,10 @@ export const getMetricsColumns = ( series: Series, dataView: DataView, visibleSeriesCount: number, - reducedTimeRange?: string + { + isStaticValueColumnSupported = false, + reducedTimeRange, + }: { reducedTimeRange?: string; isStaticValueColumnSupported?: boolean } = {} ): Column[] | null => { const { metrics: validMetrics, seriesAgg } = getSeriesAgg( series.metrics as [Metric, ...Metric[]] @@ -117,10 +121,12 @@ export const getMetricsColumns = ( return getValidColumns(column); } case 'static': { - const column = convertToStaticValueColumn(columnsConverterArgs, { - visibleSeriesCount, - reducedTimeRange, - }); + const column = isStaticValueColumnSupported + ? convertToStaticValueColumn(columnsConverterArgs, { + visibleSeriesCount, + reducedTimeRange, + }) + : convertStaticValueToFormulaColumn(columnsConverterArgs); return getValidColumns(column); } case 'std_deviation': { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts new file mode 100644 index 0000000000000..9407599573d9d --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts @@ -0,0 +1,272 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { convertToLens } from '.'; +import { createPanel, createSeries } from '../lib/__mocks__'; + +const mockGetMetricsColumns = jest.fn(); +const mockGetBucketsColumns = jest.fn(); +const mockGetConfigurationForMetric = jest.fn(); +const mockIsValidMetrics = jest.fn(); +const mockGetDatasourceValue = jest + .fn() + .mockImplementation(() => Promise.resolve(stubLogstashDataView)); +const mockGetDataSourceInfo = jest.fn(); + +jest.mock('../../services', () => ({ + getDataViewsStart: jest.fn(() => mockGetDatasourceValue), +})); + +jest.mock('../lib/series', () => ({ + getMetricsColumns: jest.fn(() => mockGetMetricsColumns()), + getBucketsColumns: jest.fn(() => mockGetBucketsColumns()), +})); + +jest.mock('../lib/configurations/metric', () => ({ + getConfigurationForMetric: jest.fn(() => mockGetConfigurationForMetric()), +})); + +jest.mock('../lib/metrics', () => ({ + isValidMetrics: jest.fn(() => mockIsValidMetrics()), + getReducedTimeRange: jest.fn().mockReturnValue('10'), +})); + +jest.mock('../lib/datasource', () => ({ + getDataSourceInfo: jest.fn(() => mockGetDataSourceInfo()), +})); + +describe('convertToLens', () => { + const model = createPanel({ + series: [ + createSeries({ + metrics: [ + { id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }, + { id: 'some-id-1', type: METRIC_TYPES.COUNT }, + ], + }), + ], + }); + + const bucket = { + isBucketed: true, + isSplit: true, + operationType: 'terms', + params: { + exclude: [], + excludeIsRegex: true, + include: [], + includeIsRegex: true, + orderAgg: { + columnId: 'some-id-0', + dataType: 'number', + isBucketed: true, + isSplit: false, + operationType: 'average', + params: {}, + sourceField: 'bytes', + }, + orderBy: { columnId: 'some-id-0', type: 'column' }, + orderDirection: 'asc', + otherBucket: false, + parentFormat: { id: 'terms' }, + secondaryFields: [], + size: 3, + }, + sourceField: 'bytes', + }; + + const bucket2 = { + isBucketed: true, + isSplit: true, + operationType: 'terms', + params: { + exclude: [], + excludeIsRegex: true, + include: [], + includeIsRegex: true, + orderAgg: { + columnId: 'some-id-1', + dataType: 'number', + isBucketed: true, + isSplit: false, + operationType: 'average', + params: {}, + sourceField: 'bytes', + }, + orderBy: { columnId: 'some-id-1', type: 'column' }, + orderDirection: 'desc', + otherBucket: false, + parentFormat: { id: 'terms' }, + secondaryFields: [], + size: 10, + }, + sourceField: 'bytes', + }; + + const metric = { + meta: { metricId: 'some-id-0' }, + operationType: 'last_value', + params: { showArrayValues: false, sortField: '@timestamp' }, + reducedTimeRange: '10m', + }; + + beforeEach(() => { + mockIsValidMetrics.mockReturnValue(true); + mockGetDataSourceInfo.mockReturnValue({ + indexPatternId: 'test-index-pattern', + timeField: 'timeField', + indexPattern: { id: 'test-index-pattern' }, + }); + mockGetMetricsColumns.mockReturnValue([{}]); + mockGetBucketsColumns.mockReturnValue([{}]); + mockGetConfigurationForMetric.mockReturnValue({}); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null for invalid metrics', async () => { + mockIsValidMetrics.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported metrics', async () => { + mockGetMetricsColumns.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockGetMetricsColumns).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported buckets', async () => { + mockGetBucketsColumns.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return state for valid model', async () => { + const result = await convertToLens(model); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsMetric'); + expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); + expect(mockGetConfigurationForMetric).toBeCalledTimes(1); + }); + + test('should skip hidden series', async () => { + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + }), + ], + }) + ); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsMetric'); + expect(mockIsValidMetrics).toBeCalledTimes(0); + }); + + test('should return null if multiple indexPatterns are provided', async () => { + mockGetDataSourceInfo.mockReturnValueOnce({ + indexPatternId: 'test-index-pattern-1', + timeField: 'timeField', + indexPattern: { id: 'test-index-pattern-1' }, + }); + + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }) + ); + expect(result).toBeNull(); + }); + + test('should return null if visible series is 2 and bucket is 1', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }) + ); + expect(result).toBeNull(); + }); + + test('should return null if visible series is 2 and two not unique buckets', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([bucket2]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }) + ); + expect(result).toBeNull(); + }); + + test('should return state if visible series is 2 and two unique buckets', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }) + ); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsMetric'); + expect(mockGetConfigurationForMetric).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts new file mode 100644 index 0000000000000..25f55b5a1c44c --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { DataView, parseTimeShift } from '@kbn/data-plugin/common'; +import { getIndexPatternIds } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { PANEL_TYPES } from '../../../common/enums'; +import { getDataViewsStart } from '../../services'; +import { getDataSourceInfo } from '../lib/datasource'; +import { getMetricsColumns, getBucketsColumns } from '../lib/series'; +import { getConfigurationForMetric as getConfiguration } from '../lib/configurations/metric'; +import { getReducedTimeRange, isValidMetrics } from '../lib/metrics'; +import { ConvertTsvbToLensVisualization } from '../types'; +import { ColumnsWithoutMeta, Layer as ExtendedLayer } from '../lib/convert'; +import { excludeMetaFromLayers, getUniqueBuckets } from './utils'; + +const MAX_SERIES = 2; +const MAX_BUCKETS = 2; + +export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { + const dataViews = getDataViewsStart(); + const seriesNum = model.series.filter((series) => !series.hidden).length; + + const indexPatternIds = new Set(); + const visibleSeries = model.series.filter(({ hidden }) => !hidden); + let currentIndexPattern: DataView | null = null; + for (const series of visibleSeries) { + const datasourceInfo = await getDataSourceInfo( + model.index_pattern, + model.time_field, + Boolean(series.override_index_pattern), + series.series_index_pattern, + series.series_time_field, + dataViews + ); + + if (!datasourceInfo) { + return null; + } + + const { indexPatternId, indexPattern } = datasourceInfo; + indexPatternIds.add(indexPatternId); + currentIndexPattern = indexPattern; + } + + if (indexPatternIds.size > 1) { + return null; + } + + const [indexPatternId] = indexPatternIds.values(); + + const buckets = []; + const metrics = []; + + // handle multiple layers/series + for (const series of visibleSeries) { + // not valid time shift + if (series.offset_time && parseTimeShift(series.offset_time) === 'invalid') { + return null; + } + + if (!isValidMetrics(series.metrics, PANEL_TYPES.METRIC, series.time_range_mode)) { + return null; + } + + const reducedTimeRange = getReducedTimeRange(model, series, timeRange); + + // handle multiple metrics + const metricsColumns = getMetricsColumns(series, currentIndexPattern!, seriesNum, { + reducedTimeRange, + }); + if (metricsColumns === null) { + return null; + } + + const bucketsColumns = getBucketsColumns( + model, + series, + metricsColumns, + currentIndexPattern!, + false + ); + + if (bucketsColumns === null) { + return null; + } + + buckets.push(...bucketsColumns); + metrics.push(...metricsColumns); + } + + let uniqueBuckets = buckets; + if (visibleSeries.length === MAX_SERIES && buckets.length) { + if (buckets.length !== MAX_BUCKETS) { + return null; + } + + uniqueBuckets = getUniqueBuckets(buckets as ColumnsWithoutMeta[]); + if (uniqueBuckets.length !== 1) { + return null; + } + } + + const [bucket] = uniqueBuckets; + + const extendedLayer: ExtendedLayer = { + indexPatternId: indexPatternId as string, + layerId: uuid(), + columns: [...metrics, ...(bucket ? [bucket] : [])], + columnOrder: [], + }; + + const configuration = getConfiguration(model, extendedLayer, bucket); + if (!configuration) { + return null; + } + + const layers = Object.values(excludeMetaFromLayers({ 0: extendedLayer })); + return { + type: 'lnsMetric', + layers, + configuration, + indexPatternIds: getIndexPatternIds(layers), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts new file mode 100644 index 0000000000000..8f880dfe95c5e --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, DateHistogramColumn, TermsColumn } from '../lib/convert'; +import { getUniqueBuckets } from './utils'; + +describe('getUniqueBuckets', () => { + const bucket2: TermsColumn = { + columnId: '12', + dataType: 'string', + isBucketed: true, + isSplit: true, + operationType: 'terms', + params: { + exclude: [], + excludeIsRegex: true, + include: [], + includeIsRegex: true, + orderAgg: { + columnId: 'some-id-1', + dataType: 'number', + isBucketed: true, + isSplit: false, + operationType: 'average', + params: {}, + sourceField: 'bytes', + }, + orderBy: { columnId: 'some-id-1', type: 'column' }, + orderDirection: 'desc', + otherBucket: false, + parentFormat: { id: 'terms' }, + secondaryFields: [], + size: 10, + }, + sourceField: 'bytes', + }; + + it('should return unique buckets', () => { + expect(getUniqueBuckets([bucket2, bucket2])).toEqual([bucket2]); + }); + + it('should ignore columnIds', () => { + const bucketWithOtherColumnIds = { + ...bucket2, + columnId: '22', + params: { + ...bucket2.params, + orderAgg: { ...bucket2.params.orderAgg, columnId: '---1' } as Column, + orderBy: { ...bucket2.params.orderBy, columnId: '---2' } as { + type: 'column'; + columnId: string; + }, + }, + }; + expect(getUniqueBuckets([bucket2, bucketWithOtherColumnIds])).toEqual([bucket2]); + }); + + it('should respect differences of terms', () => { + const bucketWithOtherColumnIds = { + ...bucket2, + columnId: '22', + params: { + ...bucket2.params, + orderAgg: { ...bucket2.params.orderAgg, columnId: '---1' } as Column, + orderBy: { ...bucket2.params.orderBy, columnId: '---2' } as { + type: 'column'; + columnId: string; + }, + }, + sourceField: 'name', + }; + expect(getUniqueBuckets([bucket2, bucketWithOtherColumnIds])).toEqual([ + bucket2, + bucketWithOtherColumnIds, + ]); + }); + + it('should respect differences of other buckets', () => { + const bucket: DateHistogramColumn = { + dataType: 'number', + isBucketed: true, + isSplit: false, + operationType: 'date_histogram', + params: { dropPartials: false, includeEmptyRows: true, interval: 'auto' }, + sourceField: 'field1', + columnId: '1', + }; + const bucket1 = { + ...bucket, + columnId: '22', + }; + expect(getUniqueBuckets([bucket, bucket1])).toEqual([bucket]); + const bucket3 = { + ...bucket, + field: 'some-other-field', + }; + expect(getUniqueBuckets([bucket, bucket3])).toEqual([bucket, bucket3]); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts new file mode 100644 index 0000000000000..8df1b0f40f8be --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { uniqWith } from 'lodash'; +import deepEqual from 'react-fast-compare'; +import { Layer, Operations, TermsColumn } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Layer as ExtendedLayer, excludeMetaFromColumn, ColumnsWithoutMeta } from '../lib/convert'; + +export const excludeMetaFromLayers = ( + layers: Record +): Record => { + const newLayers: Record = {}; + Object.entries(layers).forEach(([layerId, layer]) => { + const columns = layer.columns.map(excludeMetaFromColumn); + newLayers[layerId] = { ...layer, columns }; + }); + + return newLayers; +}; + +const excludeColumnIdsFromBucket = (bucket: ColumnsWithoutMeta) => { + const { columnId, ...restBucket } = bucket; + if (bucket.operationType === Operations.TERMS) { + const { orderBy, orderAgg, ...restParams } = bucket.params; + let orderByWithoutColumn: Omit = orderBy; + if ('columnId' in orderBy) { + const { columnId: orderByColumnId, ...restOrderBy } = orderBy; + orderByWithoutColumn = restOrderBy; + } + + let orderAggWithoutColumn: Omit | undefined = + orderAgg; + if (orderAgg) { + const { columnId: cId, ...restOrderAgg } = orderAgg; + orderAggWithoutColumn = restOrderAgg; + } + + return { + ...restBucket, + params: { + ...restParams, + orderBy: orderByWithoutColumn, + orderAgg: orderAggWithoutColumn, + }, + }; + } + return restBucket; +}; + +export const getUniqueBuckets = (buckets: ColumnsWithoutMeta[]) => + uniqWith(buckets, (bucket1, bucket2) => { + if (bucket1.operationType !== bucket2.operationType) { + return false; + } + + const bucketWithoutColumnIds1 = excludeColumnIdsFromBucket(bucket1); + const bucketWithoutColumnIds2 = excludeColumnIdsFromBucket(bucket2); + + return deepEqual(bucketWithoutColumnIds1, bucketWithoutColumnIds2); + }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index 4610310c1b378..8cbbbf0f9e739 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -86,7 +86,9 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel return null; } // handle multiple metrics - const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum); + const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum, { + isStaticValueColumnSupported: true, + }); if (metricsColumns === null) { return null; } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 630a9e5cf4c97..020aaec28f573 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -65,7 +65,9 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR const reducedTimeRange = getReducedTimeRange(model, series, timeRange); // handle multiple metrics - const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum, reducedTimeRange); + const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum, { + reducedTimeRange, + }); if (!metricsColumns) { return null; } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts index acee3c2a1e83a..69a90c7864eb3 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts @@ -6,14 +6,18 @@ * Side Public License, v 1. */ -import { NavigateToLensContext, XYConfiguration } from '@kbn/visualizations-plugin/common'; +import { + MetricVisConfiguration, + NavigateToLensContext, + XYConfiguration, +} from '@kbn/visualizations-plugin/common'; import { TimeRange } from '@kbn/data-plugin/common'; import type { Panel } from '../../common/types'; export type ConvertTsvbToLensVisualization = ( model: Panel, timeRange?: TimeRange -) => Promise | null>; +) => Promise | null>; export interface Filter { kql?: string | { [key: string]: any } | undefined; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 8c348fec4c2fa..fc1e440db3506 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts'; +import { HorizontalAlignment, LayoutDirection, Position, VerticalAlignment } from '@elastic/charts'; import { $Values } from '@kbn/utility-types'; -import type { PaletteOutput } from '@kbn/coloring'; +import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { KibanaQueryOutput } from '@kbn/data-plugin/common'; import { LegendSize } from '../../constants'; @@ -199,4 +199,22 @@ export interface TableVisConfiguration { paging?: PagingState; } -export type Configuration = XYConfiguration | TableVisConfiguration; +export interface MetricVisConfiguration { + layerId: string; + layerType: 'data'; + metricAccessor?: string; + secondaryMetricAccessor?: string; + maxAccessor?: string; + breakdownByAccessor?: string; + // the dimensions can optionally be single numbers + // computed by collapsing all rows + collapseFn?: string; + subtitle?: string; + secondaryPrefix?: string; + progressDirection?: LayoutDirection; + color?: string; + palette?: PaletteOutput; + maxCols?: number; +} + +export type Configuration = XYConfiguration | TableVisConfiguration | MetricVisConfiguration; diff --git a/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts index 47229650e05f7..45f332776a4d0 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts @@ -79,6 +79,10 @@ describe('metric suggestions', () => { ...metricColumn, columnId: 'metric-column2', }, + { + ...metricColumn, + columnId: 'metric-column3', + }, ], changeType: 'unchanged', }, @@ -99,6 +103,10 @@ describe('metric suggestions', () => { ...metricColumn, columnId: 'metric-column2', }, + { + ...metricColumn, + columnId: 'metric-column3', + }, ], changeType: 'unchanged', }, diff --git a/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts b/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts index ae40bf83574f4..c0354d4db65e0 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts @@ -11,7 +11,7 @@ import { layerTypes } from '../../../common'; import { metricLabel, MetricVisualizationState, supportedDataTypes } from './visualization'; const MAX_BUCKETED_COLUMNS = 1; -const MAX_METRIC_COLUMNS = 1; +const MAX_METRIC_COLUMNS = 2; // primary and secondary metric const hasLayerMismatch = (keptLayerIds: string[], table: TableSuggestion) => keptLayerIds.length > 1 || (keptLayerIds.length && table.layerId !== keptLayerIds[0]); diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index ed1efa900cf24..b58068b3ec202 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -19,13 +19,20 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { IconChartMetric } from '@kbn/chart-icons'; import { LayerType } from '../../../common'; import { getSuggestions } from './suggestions'; -import { Visualization, OperationMetadata, DatasourceLayers, AccessorConfig } from '../../types'; +import { + Visualization, + OperationMetadata, + DatasourceLayers, + AccessorConfig, + Suggestion, +} from '../../types'; import { layerTypes } from '../../../common'; import { GROUP_ID, LENS_METRIC_ID } from './constants'; import { DimensionEditor } from './dimension_editor'; import { Toolbar } from './toolbar'; import { generateId } from '../../id_generator'; import { FormatSelectorOptions } from '../../indexpattern_datasource/dimension_panel/format_selector'; +import { IndexPatternLayer } from '../../indexpattern_datasource/types'; export const DEFAULT_MAX_COLUMNS = 3; @@ -50,6 +57,16 @@ export interface MetricVisualizationState { maxCols?: number; } +interface MetricDatasourceState { + [prop: string]: unknown; + layers: IndexPatternLayer[]; +} + +export interface MetricSuggestion extends Suggestion { + datasourceState: MetricDatasourceState; + visualizationState: MetricVisualizationState; +} + export const supportedDataTypes = new Set(['number']); // TODO - deduplicate with gauges? @@ -484,4 +501,25 @@ export const getMetricVisualization = ({ noPadding: true, }; }, + + getSuggestionFromConvertToLensContext({ suggestions, context }) { + const allSuggestions = suggestions as MetricSuggestion[]; + return { + ...allSuggestions[0], + datasourceState: { + ...allSuggestions[0].datasourceState, + layers: allSuggestions.reduce( + (acc, s) => ({ + ...acc, + ...s.datasourceState.layers, + }), + {} + ), + }, + visualizationState: { + ...allSuggestions[0].visualizationState, + ...context.configuration, + }, + }; + }, }); diff --git a/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts b/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts index a7acd8bf5ba1c..173ab1c4fdf04 100644 --- a/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts +++ b/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts @@ -102,9 +102,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.clickDataTab('metric'); }); - it('should hide the "Edit Visualization in Lens" menu item', async () => { + it('should show the "Edit Visualization in Lens" menu item', async () => { const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(false); + expect(button).to.eql(true); + }); + + it('should convert to Lens', async () => { + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('mtrVis'); + + const metricData = await lens.getMetricVisualizationData(); + expect(metricData[0].title).to.eql('Count of records'); }); }); From 6993716021ae9683994feefcaaf949554beb3649 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Wed, 28 Sep 2022 12:08:58 +0200 Subject: [PATCH 114/172] skip failed tests (#142040) --- .../cypress/e2e/detection_rules/related_integrations.cy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts index b5c6b5cd341ec..02ccff0c265dd 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts @@ -122,14 +122,14 @@ describe('Related integrations', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); }); - it('should display a badge with the installed integrations on the rule management page', () => { + it.skip('should display a badge with the installed integrations on the rule management page', () => { cy.get(INTEGRATIONS_POPOVER).should( 'have.text', `${rule.enabledIntegrations}/${rule.integrations.length} integrations` ); }); - it('should display a popover when clicking the badge with the installed integrations on the rule management page', () => { + it.skip('should display a popover when clicking the badge with the installed integrations on the rule management page', () => { openIntegrationsPopover(); cy.get(INTEGRATIONS_POPOVER_TITLE).should( @@ -148,7 +148,7 @@ describe('Related integrations', () => { }); }); - it('should display the integrations on the definition section', () => { + it.skip('should display the integrations on the definition section', () => { goToTheRuleDetailsOf(rule.name); cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); From 042c76687c46d16a227def1c04199af391bf4e31 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 28 Sep 2022 12:34:30 +0200 Subject: [PATCH 115/172] [ML] Update the Notification indicator tooltip, add functional tests (#141775) --- .../ml_page/notifications_indicator.tsx | 46 +++++-- .../components/notifications_list.tsx | 6 +- .../routes/schemas/notifications_schema.ts | 16 --- x-pack/test/api_integration/apis/ml/index.ts | 1 + .../ml/notifications/count_notifications.ts | 55 ++++++++ .../ml/notifications/get_notifications.ts | 102 +++++++++++++++ .../apis/ml/notifications/index.ts | 15 +++ .../functional/apps/ml/short_tests/index.ts | 1 + .../ml/short_tests/notifications/index.ts | 16 +++ .../notifications/notification_list.ts | 96 ++++++++++++++ x-pack/test/functional/services/ml/api.ts | 61 +++++++++ .../services/ml/common_table_service.ts | 118 ++++++++++++++++++ x-pack/test/functional/services/ml/index.ts | 8 +- .../functional/services/ml/notifications.ts | 48 +++++++ 14 files changed, 557 insertions(+), 32 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts create mode 100644 x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts create mode 100644 x-pack/test/api_integration/apis/ml/notifications/index.ts create mode 100644 x-pack/test/functional/apps/ml/short_tests/notifications/index.ts create mode 100644 x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts create mode 100644 x-pack/test/functional/services/ml/common_table_service.ts create mode 100644 x-pack/test/functional/services/ml/notifications.ts diff --git a/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx b/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx index 20d771f5654d4..d0e3516af3db0 100644 --- a/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx @@ -16,7 +16,10 @@ import { EuiToolTip, } from '@elastic/eui'; import { combineLatest, of, timer } from 'rxjs'; -import { catchError, filter, switchMap } from 'rxjs/operators'; +import { catchError, switchMap } from 'rxjs/operators'; +import moment from 'moment'; +import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; +import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter'; import { useAsObservable } from '../../hooks'; import { NotificationsCountResponse } from '../../../../common/types/notifications'; import { useMlKibana } from '../../contexts/kibana'; @@ -31,21 +34,26 @@ export const NotificationsIndicator: FC = () => { mlServices: { mlApiServices }, }, } = useMlKibana(); - const [lastCheckedAt] = useStorage(ML_NOTIFICATIONS_LAST_CHECKED_AT); + const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE); + const [lastCheckedAt] = useStorage(ML_NOTIFICATIONS_LAST_CHECKED_AT); const lastCheckedAt$ = useAsObservable(lastCheckedAt); + /** Holds the value used for the actual request */ + const [lastCheckRequested, setLastCheckRequested] = useState(); const [notificationsCounts, setNotificationsCounts] = useState(); useEffect(function startPollingNotifications() { - const subscription = combineLatest([ - lastCheckedAt$.pipe(filter((v): v is number => !!v)), - timer(0, NOTIFICATIONS_CHECK_INTERVAL), - ]) + const subscription = combineLatest([lastCheckedAt$, timer(0, NOTIFICATIONS_CHECK_INTERVAL)]) .pipe( - switchMap(([lastChecked]) => - mlApiServices.notifications.countMessages$({ lastCheckedAt: lastChecked }) - ), + switchMap(([lastChecked]) => { + const lastCheckedAtQuery = lastChecked ?? moment().subtract(7, 'd').valueOf(); + setLastCheckRequested(lastCheckedAtQuery); + // Use the latest check time or 7 days ago by default. + return mlApiServices.notifications.countMessages$({ + lastCheckedAt: lastCheckedAtQuery, + }); + }), catchError((error) => { // Fail silently for now return of({} as NotificationsCountResponse); @@ -80,12 +88,22 @@ export const NotificationsIndicator: FC = () => { content={ } > - {errorsAndWarningCount} + + {errorsAndWarningCount} +
    ) : null} @@ -96,7 +114,8 @@ export const NotificationsIndicator: FC = () => { content={ } > @@ -106,6 +125,7 @@ export const NotificationsIndicator: FC = () => { aria-label={i18n.translate('xpack.ml.notificationsIndicator.unreadIcon', { defaultMessage: 'Unread notifications indicator.', })} + data-test-subj={'mlNotificationsIndicator'} />
    diff --git a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx index 811b1c43bce37..9ea6aa1b70f00 100644 --- a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx +++ b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx @@ -162,6 +162,7 @@ export const NotificationsList: FC = () => { const columns: Array> = [ { + id: 'timestamp', field: 'timestamp', name: , sortable: true, @@ -175,7 +176,7 @@ export const NotificationsList: FC = () => { name: , sortable: true, truncateText: false, - 'data-test-subj': 'mlNotificationLabel', + 'data-test-subj': 'mlNotificationLevel', render: (value: MlNotificationMessageLevel) => { return {value}; }, @@ -194,7 +195,7 @@ export const NotificationsList: FC = () => { }, { field: 'job_id', - name: , + name: , sortable: true, truncateText: false, 'data-test-subj': 'mlNotificationEntity', @@ -320,6 +321,7 @@ export const NotificationsList: FC = () => { }, }, }, + 'data-test-subj': 'mlNotificationsSearchBarInput', }} filters={filters} onChange={(e) => { diff --git a/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts b/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts index a82691ee52813..153ffd0aeea87 100644 --- a/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts @@ -8,26 +8,10 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const getNotificationsQuerySchema = schema.object({ - /** - * Message level, e.g. info, error - */ - level: schema.maybe(schema.string()), - /** - * Message type, e.g. anomaly_detector - */ - type: schema.maybe(schema.string()), /** * Search string for the message content */ queryString: schema.maybe(schema.string()), - /** - * Page numer, zero-indexed - */ - from: schema.number({ defaultValue: 0 }), - /** - * Number of messages to return - */ - size: schema.number({ defaultValue: 10 }), /** * Sort field */ diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 915d755ca97c0..e76eef8cb82bf 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -67,5 +67,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./saved_objects')); loadTestFile(require.resolve('./system')); loadTestFile(require.resolve('./trained_models')); + loadTestFile(require.resolve('./notifications')); }); } diff --git a/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts b/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts new file mode 100644 index 0000000000000..58932ea199b5d --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/notifications/count_notifications.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 expect from '@kbn/expect'; +import moment from 'moment'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET notifications count', () => { + before(async () => { + await ml.api.initSavedObjects(); + await ml.testResources.setKibanaTimeZoneToUTC(); + + const adJobConfig = ml.commonConfig.getADFqSingleMetricJobConfig('fq_job'); + await ml.api.createAnomalyDetectionJob(adJobConfig); + + await ml.api.waitForJobNotificationsToIndex('fq_job'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.cleanMLSavedObjects(); + }); + + it('return notifications count by level', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications/count`) + .query({ lastCheckedAt: moment().subtract(7, 'd').valueOf() }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.info).to.eql(1); + expect(body.warning).to.eql(0); + expect(body.error).to.eql(0); + }); + + it('returns an error for unauthorized user', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications/count`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts b/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts new file mode 100644 index 0000000000000..992065cdae67d --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts @@ -0,0 +1,102 @@ +/* + * 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 expect from '@kbn/expect'; +import type { + NotificationItem, + NotificationsSearchResponse, +} from '@kbn/ml-plugin/common/types/notifications'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('GET notifications', () => { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification'); + await ml.api.initSavedObjects(); + await ml.testResources.setKibanaTimeZoneToUTC(); + + const adJobConfig = ml.commonConfig.getADFqSingleMetricJobConfig('fq_job'); + await ml.api.createAnomalyDetectionJob(adJobConfig); + + const dfaJobConfig = ml.commonConfig.getDFABmClassificationJobConfig('df_job'); + await ml.api.createDataFrameAnalyticsJob(dfaJobConfig); + + // wait for notification to index + + await ml.api.waitForJobNotificationsToIndex('fq_job'); + await ml.api.waitForJobNotificationsToIndex('df_job'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.cleanMLSavedObjects(); + }); + + it('return all notifications ', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications`) + .query({ earliest: 'now-1d', latest: 'now' }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + + expect((body as NotificationsSearchResponse).total).to.eql(2); + }); + + it('return notifications based on the query string', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications`) + .query({ earliest: 'now-1d', latest: 'now', queryString: 'job_type:anomaly_detector' }) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + + expect((body as NotificationsSearchResponse).total).to.eql(1); + expect( + (body as NotificationsSearchResponse).results.filter( + (result: NotificationItem) => result.job_type === 'anomaly_detector' + ) + ).to.length(body.total); + }); + + it('supports sorting asc sorting by field', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications`) + .query({ earliest: 'now-1d', latest: 'now', sortField: 'job_id', sortDirection: 'asc' }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.results[0].job_id).to.eql('df_job'); + }); + + it('supports sorting desc sorting by field', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications`) + .query({ earliest: 'now-1h', latest: 'now', sortField: 'job_id', sortDirection: 'desc' }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.results[0].job_id).to.eql('fq_job'); + }); + + it('returns an error for unauthorized user', async () => { + const { body, status } = await supertest + .get(`/api/ml/notifications`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/notifications/index.ts b/x-pack/test/api_integration/apis/ml/notifications/index.ts new file mode 100644 index 0000000000000..4a09fce5ee51e --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/notifications/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. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Notifications', function () { + loadTestFile(require.resolve('./get_notifications')); + loadTestFile(require.resolve('./count_notifications')); + }); +} diff --git a/x-pack/test/functional/apps/ml/short_tests/index.ts b/x-pack/test/functional/apps/ml/short_tests/index.ts index f96d2b91ee0ef..d446a35933474 100644 --- a/x-pack/test/functional/apps/ml/short_tests/index.ts +++ b/x-pack/test/functional/apps/ml/short_tests/index.ts @@ -33,5 +33,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./model_management')); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./settings')); + loadTestFile(require.resolve('./notifications')); }); } diff --git a/x-pack/test/functional/apps/ml/short_tests/notifications/index.ts b/x-pack/test/functional/apps/ml/short_tests/notifications/index.ts new file mode 100644 index 0000000000000..e026d44a67af2 --- /dev/null +++ b/x-pack/test/functional/apps/ml/short_tests/notifications/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. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Notifcations', function () { + this.tags(['ml', 'skipFirefox']); + + loadTestFile(require.resolve('./notification_list')); + }); +} diff --git a/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts new file mode 100644 index 0000000000000..c9ad8d2423ef8 --- /dev/null +++ b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts @@ -0,0 +1,96 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common']); + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + const browser = getService('browser'); + + describe('Notifications list', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + + // Prepare jobs to generate notifications + await Promise.all( + [ + { jobId: 'fq_001', spaceId: undefined }, + { jobId: 'fq_002', spaceId: 'space1' }, + ].map(async (v) => { + const datafeedConfig = ml.commonConfig.getADFqDatafeedConfig(v.jobId); + + await ml.api.createAnomalyDetectionJob( + ml.commonConfig.getADFqSingleMetricJobConfig(v.jobId), + v.spaceId + ); + await ml.api.openAnomalyDetectionJob(v.jobId); + await ml.api.createDatafeed(datafeedConfig, v.spaceId); + await ml.api.startDatafeed(datafeedConfig.datafeed_id); + }) + ); + + await ml.securityUI.loginAsMlPowerUser(); + await PageObjects.common.navigateToApp('ml', { + basePath: '', + }); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.cleanMLSavedObjects(); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + }); + + it('displays a generic notification indicator', async () => { + await ml.notifications.assertNotificationIndicatorExist(); + }); + + it('opens the Notifications page', async () => { + await ml.navigation.navigateToNotifications(); + + await ml.notifications.table.waitForTableToLoad(); + await ml.notifications.table.assertRowsNumberPerPage(25); + }); + + it('does not show notifications from another space', async () => { + await ml.notifications.table.filterWithSearchString('Job created', 1); + }); + + it('display a number of errors in the notification indicator', async () => { + await ml.navigation.navigateToOverview(); + + const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig('fq_fail'); + jobConfig.analysis_config = { + bucket_span: '15m', + influencers: ['airline'], + detectors: [ + { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, + ], + }; + // Set extremely low memory limit to trigger an error + jobConfig.analysis_limits!.model_memory_limit = '1024kb'; + + const datafeedConfig = ml.commonConfig.getADFqDatafeedConfig(jobConfig.job_id); + + await ml.api.createAnomalyDetectionJob(jobConfig); + await ml.api.openAnomalyDetectionJob(jobConfig.job_id); + await ml.api.createDatafeed(datafeedConfig); + await ml.api.startDatafeed(datafeedConfig.datafeed_id); + await ml.api.waitForJobMemoryStatus(jobConfig.job_id, 'hard_limit'); + + // refresh the page to avoid 1m wait + await browser.refresh(); + await ml.notifications.assertNotificationErrorsCount(0); + }); + }); +} diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 07bd1f346cf25..62d46e644f173 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -273,6 +273,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return state; }, + async getJobMemoryStatus(jobId: string): Promise<'hard_limit' | 'soft_limit' | 'ok'> { + const jobStats = await this.getADJobStats(jobId); + + expect(jobStats.jobs).to.have.length( + 1, + `Expected job stats to have exactly one job (got '${jobStats.length}')` + ); + return jobStats.jobs[0].model_size_stats.memory_status; + }, + async getADJobStats(jobId: string): Promise { log.debug(`Fetching anomaly detection job stats for job ${jobId}...`); const { body: jobStats, status } = await esSupertest.get( @@ -299,6 +309,27 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); }, + async waitForJobMemoryStatus( + jobId: string, + expectedMemoryStatus: 'hard_limit' | 'soft_limit' | 'ok', + timeout: number = 2 * 60 * 1000 + ) { + await retry.waitForWithTimeout( + `job memory status to be ${expectedMemoryStatus}`, + timeout, + async () => { + const memoryStatus = await this.getJobMemoryStatus(jobId); + if (memoryStatus === expectedMemoryStatus) { + return true; + } else { + throw new Error( + `expected job memory status to be ${expectedMemoryStatus} but got ${memoryStatus}` + ); + } + } + ); + }, + async getDatafeedState(datafeedId: string): Promise { log.debug(`Fetching datafeed state for datafeed ${datafeedId}`); const { body: datafeedStats, status } = await esSupertest.get( @@ -576,6 +607,18 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return response; }, + async hasNotifications(query: object) { + const body = await es.search({ + index: '.ml-notifications*', + body: { + size: 10000, + query, + }, + }); + + return body.hits.hits.length > 0; + }, + async adJobExist(jobId: string) { this.validateJobId(jobId); try { @@ -608,6 +651,24 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); }, + async waitForJobNotificationsToIndex(jobId: string, timeout: number = 60 * 1000) { + await retry.waitForWithTimeout(`Notifications for '${jobId}' to exist`, timeout, async () => { + if ( + await this.hasNotifications({ + term: { + job_id: { + value: jobId, + }, + }, + }) + ) { + return true; + } else { + throw new Error(`expected '${jobId}' notifications to exist`); + } + }); + }, + async createAnomalyDetectionJob(jobConfig: Job, space?: string) { const jobId = jobConfig.job_id; log.debug( diff --git a/x-pack/test/functional/services/ml/common_table_service.ts b/x-pack/test/functional/services/ml/common_table_service.ts new file mode 100644 index 0000000000000..50a40ab43f35a --- /dev/null +++ b/x-pack/test/functional/services/ml/common_table_service.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export type MlTableService = ReturnType; + +export function MlTableServiceProvider({ getPageObject, getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const commonPage = getPageObject('common'); + + const TableService = class { + constructor( + public readonly tableTestSubj: string, + public readonly tableRowSubj: string, + public readonly columns: Array<{ id: string; testSubj: string }>, + public readonly searchInputSubj: string + ) {} + + public async assertTableLoaded() { + await testSubjects.existOrFail(`~${this.tableTestSubj} loaded`); + } + + public async assertTableLoading() { + await testSubjects.existOrFail(`~${this.tableTestSubj} loading`); + } + + public async parseTable() { + const table = await testSubjects.find(`~${this.tableTestSubj}`); + const $ = await table.parseDomContent(); + const rows = []; + + for (const tr of $.findTestSubjects(`~${this.tableRowSubj}`).toArray()) { + const $tr = $(tr); + + const rowObject = this.columns.reduce((acc, curr) => { + acc[curr.id] = $tr + .findTestSubject(curr.testSubj) + .find('.euiTableCellContent') + .text() + .trim(); + return acc; + }, {} as Record); + + rows.push(rowObject); + } + + return rows; + } + + public async assertRowsNumberPerPage(rowsNumber: 10 | 25 | 50 | 100) { + const textContent = await testSubjects.getVisibleText( + `~${this.tableTestSubj} > tablePaginationPopoverButton` + ); + expect(textContent).to.be(`Rows per page: ${rowsNumber}`); + } + + public async waitForTableToStartLoading() { + await testSubjects.existOrFail(`~${this.tableTestSubj}`, { timeout: 60 * 1000 }); + await testSubjects.existOrFail(`${this.tableTestSubj} loading`, { timeout: 30 * 1000 }); + } + + public async waitForTableToLoad() { + await testSubjects.existOrFail(`~${this.tableTestSubj}`, { timeout: 60 * 1000 }); + await testSubjects.existOrFail(`${this.tableTestSubj} loaded`, { timeout: 30 * 1000 }); + } + + async getSearchInput(): Promise { + return await testSubjects.find(this.searchInputSubj); + } + + public async assertSearchInputValue(expectedSearchValue: string) { + const searchBarInput = await this.getSearchInput(); + const actualSearchValue = await searchBarInput.getAttribute('value'); + expect(actualSearchValue).to.eql( + expectedSearchValue, + `Search input value should be '${expectedSearchValue}' (got '${actualSearchValue}')` + ); + } + + public async filterWithSearchString(queryString: string, expectedRowCount: number = 1) { + await this.waitForTableToLoad(); + const searchBarInput = await this.getSearchInput(); + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.type(queryString); + await commonPage.pressEnterKey(); + await this.assertSearchInputValue(queryString); + await this.waitForTableToStartLoading(); + await this.waitForTableToLoad(); + + const rows = await this.parseTable(); + + expect(rows).to.have.length( + expectedRowCount, + `Filtered table should have ${expectedRowCount} row(s) for filter '${queryString}' (got ${rows.length} matching items)` + ); + } + }; + + return { + getServiceInstance( + name: string, + tableTestSubj: string, + tableRowSubj: string, + columns: Array<{ id: string; testSubj: string }>, + searchInputSubj: string + ) { + Object.defineProperty(TableService, 'name', { value: name }); + return new TableService(tableTestSubj, tableRowSubj, columns, searchInputSubj); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index d8c6924ec4cfd..9452baa324898 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -59,6 +59,8 @@ import { MachineLearningJobAnnotationsProvider } from './job_annotations_table'; import { MlNodesPanelProvider } from './ml_nodes_list'; import { MachineLearningCasesProvider } from './cases'; import { AnomalyChartsProvider } from './anomaly_charts'; +import { NotificationsProvider } from './notifications'; +import { MlTableServiceProvider } from './common_table_service'; export function MachineLearningProvider(context: FtrProviderContext) { const commonAPI = MachineLearningCommonAPIProvider(context); @@ -123,6 +125,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const settingsFilterList = MachineLearningSettingsFilterListProvider(context, commonUI); const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context, commonUI); const stackManagementJobs = MachineLearningStackManagementJobsProvider(context); + const tableService = MlTableServiceProvider(context); const testExecution = MachineLearningTestExecutionProvider(context); const testResources = MachineLearningTestResourcesProvider(context, api); const alerting = MachineLearningAlertingProvider(context, api, commonUI); @@ -130,6 +133,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const trainedModels = TrainedModelsProvider(context, commonUI); const trainedModelsTable = TrainedModelsTableProvider(context, commonUI); const mlNodesPanel = MlNodesPanelProvider(context); + const notifications = NotificationsProvider(context, commonUI, tableService); const cases = MachineLearningCasesProvider(context, swimLane, anomalyCharts); @@ -171,7 +175,9 @@ export function MachineLearningProvider(context: FtrProviderContext) { jobWizardMultiMetric, jobWizardPopulation, lensVisualizations, + mlNodesPanel, navigation, + notifications, overviewPage, securityCommon, securityUI, @@ -181,10 +187,10 @@ export function MachineLearningProvider(context: FtrProviderContext) { singleMetricViewer, stackManagementJobs, swimLane, + tableService, testExecution, testResources, trainedModels, trainedModelsTable, - mlNodesPanel, }; } diff --git a/x-pack/test/functional/services/ml/notifications.ts b/x-pack/test/functional/services/ml/notifications.ts new file mode 100644 index 0000000000000..2365717d7226b --- /dev/null +++ b/x-pack/test/functional/services/ml/notifications.ts @@ -0,0 +1,48 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { MlCommonUI } from './common_ui'; +import { MlTableService } from './common_table_service'; + +export function NotificationsProvider( + { getService }: FtrProviderContext, + mlCommonUI: MlCommonUI, + tableService: MlTableService +) { + const testSubjects = getService('testSubjects'); + + return { + async assertNotificationIndicatorExist(expectExist = true) { + if (expectExist) { + await testSubjects.existOrFail('mlNotificationsIndicator'); + } else { + await testSubjects.missingOrFail('mlNotificationsIndicator'); + } + }, + + async assertNotificationErrorsCount(expectedCount: number) { + const actualCount = await testSubjects.getVisibleText('mlNotificationErrorsIndicator'); + expect(actualCount).to.greaterThan(expectedCount); + }, + + table: tableService.getServiceInstance( + 'NotificationsTable', + 'mlNotificationsTable', + 'mlNotificationsTableRow', + [ + { id: 'timestamp', testSubj: 'mlNotificationTime' }, + { id: 'level', testSubj: 'mlNotificationLevel' }, + { id: 'job_type', testSubj: 'mlNotificationType' }, + { id: 'job_id', testSubj: 'mlNotificationEntity' }, + { id: 'message', testSubj: 'mlNotificationMessage' }, + ], + 'mlNotificationsSearchBarInput' + ), + }; +} From 0471095d7d01634ffae4ef5017797e28ff5b13a8 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 28 Sep 2022 13:02:51 +0200 Subject: [PATCH 116/172] Run ILM jest tests as integration tests allowing them to run beyond 5s timeout (#141750) --- .../index_lifecycle_management/README.md | 6 +++++- .../__jest__/extend_index_management.test.tsx | 2 +- .../integration_tests/README.md | 14 ++++++++++++++ .../app/app.helpers.ts | 2 +- .../app/app.test.ts | 0 .../edit_policy/constants.ts | 4 ++-- .../features/delete_phase.helpers.ts | 0 .../edit_policy/features/delete_phase.test.ts | 2 +- .../features/downsample.helpers.ts | 2 +- .../edit_policy/features/downsample.test.ts | 0 .../edit_policy/features/edit_warning.test.ts | 0 .../edit_policy/features/frozen_phase.test.ts | 0 .../cloud_aware_behavior.helpers.ts | 2 +- .../cloud_aware_behavior.test.ts | 0 .../node_allocation/cold_phase.helpers.ts | 0 .../node_allocation/cold_phase.test.ts | 0 .../general_behavior.helpers.ts | 0 .../node_allocation/general_behavior.test.ts | 2 +- .../node_allocation/warm_phase.helpers.ts | 0 .../node_allocation/warm_phase.test.ts | 0 .../features/request_flyout.helpers.ts | 0 .../features/request_flyout.test.ts | 0 .../edit_policy/features/rollover.helpers.ts | 0 .../edit_policy/features/rollover.test.ts | 0 .../features/searchable_snapshots.helpers.ts | 2 +- .../features/searchable_snapshots.test.ts | 2 +- .../edit_policy/features/timeline.helpers.ts | 2 +- .../edit_policy/features/timeline.test.ts | 0 .../edit_policy/features/timing.helpers.ts | 0 .../edit_policy/features/timing.test.ts | 2 +- .../cold_phase_validation.test.ts | 5 ++--- .../downsample_interval.test.ts | 7 +++---- .../form_validation/error_indicators.test.ts | 3 +-- .../hot_phase_validation.test.ts | 5 ++--- .../policy_name_validation.test.ts | 5 ++--- .../form_validation/timing.test.ts | 7 +++---- .../form_validation/validation.helpers.ts | 0 .../warm_phase_validation.test.ts | 5 ++--- .../edit_policy/init_test_bed.ts | 6 +++--- .../policy_serialization.helpers.ts | 2 +- .../policy_serialization.test.ts | 2 +- .../helpers/actions/downsample_actions.ts | 4 ++-- .../helpers/actions/errors_actions.ts | 2 +- .../helpers/actions/forcemerge_actions.ts | 2 +- .../helpers/actions/form_set_value_action.ts | 0 .../helpers/actions/form_toggle_action.ts | 0 .../form_toggle_and_set_value_action.ts | 0 .../helpers/actions/index.ts | 0 .../helpers/actions/index_priority_actions.ts | 2 +- .../helpers/actions/min_age_actions.ts | 2 +- .../actions/node_allocation_actions.ts | 4 ++-- .../helpers/actions/phases.ts | 0 .../helpers/actions/readonly_actions.ts | 2 +- .../helpers/actions/replicas_action.ts | 2 +- .../helpers/actions/request_flyout_actions.ts | 0 .../helpers/actions/rollover_actions.ts | 0 .../helpers/actions/save_policy_action.ts | 0 .../actions/searchable_snapshot_actions.ts | 2 +- .../helpers/actions/shrink_actions.ts | 2 +- .../actions/snapshot_policy_actions.ts | 0 .../helpers/actions/toggle_phase_action.ts | 2 +- .../helpers/global_mocks.tsx | 0 .../helpers/http_requests.ts | 4 ++-- .../helpers/index.ts | 0 .../helpers/setup_environment.tsx | 10 +++++----- .../jest.integration.config.js | 19 +++++++++++++++++++ .../index_lifecycle_management/tsconfig.json | 1 + 67 files changed, 91 insertions(+), 60 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/integration_tests/README.md rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/app/app.helpers.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/app/app.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/constants.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/delete_phase.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/delete_phase.test.ts (98%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/downsample.helpers.ts (95%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/downsample.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/edit_warning.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/frozen_phase.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts (94%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/cold_phase.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/cold_phase.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/general_behavior.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/general_behavior.test.ts (98%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/warm_phase.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/node_allocation/warm_phase.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/request_flyout.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/request_flyout.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/rollover.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/rollover.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/searchable_snapshots.helpers.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/searchable_snapshots.test.ts (99%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/timeline.helpers.ts (94%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/timeline.test.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/timing.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/features/timing.test.ts (95%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/cold_phase_validation.test.ts (91%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/downsample_interval.test.ts (93%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/error_indicators.test.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/hot_phase_validation.test.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/policy_name_validation.test.ts (93%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/timing.test.ts (93%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/validation.helpers.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/form_validation/warm_phase_validation.test.ts (95%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/init_test_bed.ts (89%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/serialization/policy_serialization.helpers.ts (95%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/edit_policy/serialization/policy_serialization.test.ts (99%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/downsample_actions.ts (96%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/errors_actions.ts (96%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/forcemerge_actions.ts (96%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/form_set_value_action.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/form_toggle_action.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/form_toggle_and_set_value_action.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/index_priority_actions.ts (94%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/min_age_actions.ts (94%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/node_allocation_actions.ts (95%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/phases.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/readonly_actions.ts (92%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/replicas_action.ts (92%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/request_flyout_actions.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/rollover_actions.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/save_policy_action.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/searchable_snapshot_actions.ts (96%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/shrink_actions.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/snapshot_policy_actions.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/actions/toggle_phase_action.ts (96%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/global_mocks.tsx (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/http_requests.ts (97%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/{__jest__/client_integration => integration_tests}/helpers/setup_environment.tsx (80%) create mode 100644 x-pack/plugins/index_lifecycle_management/jest.integration.config.js diff --git a/x-pack/plugins/index_lifecycle_management/README.md b/x-pack/plugins/index_lifecycle_management/README.md index 35c2aa063ec23..912b19295790f 100644 --- a/x-pack/plugins/index_lifecycle_management/README.md +++ b/x-pack/plugins/index_lifecycle_management/README.md @@ -115,4 +115,8 @@ this by running: ```bash yarn es snapshot --license=trial -``` \ No newline at end of file +``` + +## Integration tests + +See `./integration_tests/README.md` \ No newline at end of file diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx index ecefe8132c499..246de6e8ed25b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx @@ -7,7 +7,7 @@ import moment from 'moment-timezone'; -import { init } from './client_integration/helpers/http_requests'; +import { init } from '../integration_tests/helpers/http_requests'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks'; import { Index } from '../common/types'; diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/README.md b/x-pack/plugins/index_lifecycle_management/integration_tests/README.md new file mode 100644 index 0000000000000..5e4bf4360cb72 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/README.md @@ -0,0 +1,14 @@ +Most plugins of the deployment management team follow +the similar testing infrastructure where integration tests are located in `__jest__` and run as unit tests. + +The `index_lifecycle_management` tests [were refactored](https://github.com/elastic/kibana/pull/141750) to be run as integration tests because they [became flaky hitting the 5 seconds timeout](https://github.com/elastic/kibana/issues/115307#issuecomment-1238417474) for a jest unit test. + +Jest integration tests are just sit in a different directory and have two main differences: +- They never use parallelism, this allows them to access file system resources, launch services, etc. without needing to worry about conflicts with other tests +- They are allowed to take their sweet time, the default timeout is currently 10 minutes. + +To run these tests use: + +``` +node scripts/jest_integration x-pack/plugins/index_lifecycle_management/ +``` \ No newline at end of file diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts index df64fbce3fa1d..66810ebb1e546 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import { HttpSetup } from '@kbn/core/public'; import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test-jest-helpers'; -import { App } from '../../../public/application/app'; +import { App } from '../../public/application/app'; import { WithAppDependencies } from '../helpers'; const getTestBedConfig = (initialEntries: string[]): TestBedConfig => ({ diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/constants.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/constants.ts index 620cb9d6f8dde..a41bea1cb8415 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/constants.ts @@ -7,9 +7,9 @@ import moment from 'moment-timezone'; -import { PolicyFromES } from '../../../common/types'; +import { PolicyFromES } from '../../common/types'; -import { defaultRolloverAction } from '../../../public/application/constants'; +import { defaultRolloverAction } from '../../public/application/constants'; export const POLICY_NAME = 'my_policy'; export const SNAPSHOT_POLICY_NAME = 'my_snapshot_policy'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/delete_phase.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/delete_phase.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/delete_phase.test.ts similarity index 98% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/delete_phase.test.ts index d877f05d06ae1..df6dd516c8a58 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/delete_phase.test.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { API_BASE_PATH } from '../../../../common/constants'; +import { API_BASE_PATH } from '../../../common/constants'; import { setupEnvironment } from '../../helpers'; import { DELETE_PHASE_POLICY, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/downsample.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/downsample.helpers.ts similarity index 95% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/downsample.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/downsample.helpers.ts index be96aaabe4a71..1e6ed336a5f03 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/downsample.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/downsample.helpers.ts @@ -14,7 +14,7 @@ import { createTogglePhaseAction, } from '../../helpers'; import { initTestBed } from '../init_test_bed'; -import { AppServicesContext } from '../../../../public/types'; +import { AppServicesContext } from '../../../public/types'; type SetupReturn = ReturnType; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/downsample.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/downsample.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/downsample.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/downsample.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts similarity index 94% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts index 7092d52289de9..b41075c6b6b68 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts @@ -8,7 +8,7 @@ import { HttpSetup } from '@kbn/core/public'; import { TestBedConfig } from '@kbn/test-jest-helpers'; -import { AppServicesContext } from '../../../../../public/types'; +import { AppServicesContext } from '../../../../public/types'; import { createTogglePhaseAction, createNodeAllocationActions } from '../../../helpers'; import { initTestBed } from '../../init_test_bed'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cold_phase.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cold_phase.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cold_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cold_phase.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/general_behavior.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/general_behavior.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/general_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts similarity index 98% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/general_behavior.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts index 1eecd5207664f..4830cee8ee237 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/general_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts @@ -17,7 +17,7 @@ import { POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION, POLICY_WITH_NODE_ROLE_ALLOCATION, } from '../../constants'; -import { API_BASE_PATH } from '../../../../../common/constants'; +import { API_BASE_PATH } from '../../../../common/constants'; describe(' node allocation general behavior', () => { let testBed: GeneralNodeAllocationTestBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/warm_phase.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/warm_phase.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/warm_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/warm_phase.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/rollover.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/rollover.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/rollover.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/rollover.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.helpers.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.helpers.ts index e70f721a48075..d3b68ffc6a13e 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.helpers.ts @@ -18,7 +18,7 @@ import { createTogglePhaseAction, } from '../../helpers'; import { initTestBed } from '../init_test_bed'; -import { AppServicesContext } from '../../../../public/types'; +import { AppServicesContext } from '../../../public/types'; type SetupReturn = ReturnType; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts similarity index 99% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts index 03b7670c1eac5..68e74e23a781c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts @@ -10,7 +10,7 @@ import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { HttpFetchOptionsWithPath } from '@kbn/core/public'; import { setupEnvironment } from '../../helpers'; import { getDefaultHotPhasePolicy } from '../constants'; -import { API_BASE_PATH } from '../../../../common/constants'; +import { API_BASE_PATH } from '../../../common/constants'; import { SearchableSnapshotsTestBed, setupSearchableSnapshotsTestBed, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timeline.helpers.ts similarity index 94% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timeline.helpers.ts index 496b27330c931..202388a844464 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timeline.helpers.ts @@ -8,7 +8,7 @@ import { HttpSetup } from '@kbn/core/public'; import { createTogglePhaseAction } from '../../helpers'; import { initTestBed } from '../init_test_bed'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; type SetupReturn = ReturnType; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timeline.test.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timeline.test.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timing.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timing.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timing.test.ts similarity index 95% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timing.test.ts index 0aee8eb5f0be1..72c6a2a4e789c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/timing.test.ts @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import { setupEnvironment } from '../../helpers'; import { setupTimingTestBed, TimingTestBed } from './timing.helpers'; -import { PhaseWithTiming } from '../../../../common/types'; +import { PhaseWithTiming } from '../../../common/types'; describe(' timing', () => { let testBed: TimingTestBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts similarity index 91% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts index ef2fc67002c14..e75d2cb72ab28 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts @@ -6,12 +6,11 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141645 -describe.skip(' cold phase validation', () => { +describe(' cold phase validation', () => { let testBed: ValidationTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/downsample_interval.test.ts similarity index 93% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/downsample_interval.test.ts index 79f5fdc6e2840..d2f9943eba68a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/downsample_interval.test.ts @@ -6,14 +6,13 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; -import { PhaseWithDownsample } from '../../../../common/types'; +import { PhaseWithDownsample } from '../../../common/types'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141160 -describe.skip(' downsample interval validation', () => { +describe(' downsample interval validation', () => { let testBed: ValidationTestBed; let actions: ValidationTestBed['actions']; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts index 349f98766620d..bd4a2caec0be5 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts @@ -9,8 +9,7 @@ import { act } from 'react-dom/test-utils'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141645 -describe.skip(' error indicators', () => { +describe(' error indicators', () => { let testBed: ValidationTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts index 82b3568d39ce7..71f83a59360d6 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts @@ -6,12 +6,11 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141645 -describe.skip(' hot phase validation', () => { +describe(' hot phase validation', () => { let testBed: ValidationTestBed; let actions: ValidationTestBed['actions']; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts similarity index 93% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts index 928380e3d1eac..c530f73a66c11 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts @@ -6,13 +6,12 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; import { setupEnvironment } from '../../helpers'; import { getGeneratedPolicies } from '../constants'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141645 -describe.skip(' policy name validation', () => { +describe(' policy name validation', () => { let testBed: ValidationTestBed; let actions: ValidationTestBed['actions']; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts similarity index 93% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts index 7db483d6d0ef2..5838f04ba70e9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts @@ -6,14 +6,13 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; -import { PhaseWithTiming } from '../../../../common/types'; +import { PhaseWithTiming } from '../../../common/types'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/115307 -describe.skip(' timing validation', () => { +describe(' timing validation', () => { let testBed: ValidationTestBed; let actions: ValidationTestBed['actions']; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/validation.helpers.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/validation.helpers.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts similarity index 95% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts index df0607a0a0e65..47917b1f8e3d7 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts @@ -6,12 +6,11 @@ */ import { act } from 'react-dom/test-utils'; -import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { i18nTexts } from '../../../public/application/sections/edit_policy/i18n_texts'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/141645 -describe.skip(' warm phase validation', () => { +describe(' warm phase validation', () => { let testBed: ValidationTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/init_test_bed.ts similarity index 89% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/init_test_bed.ts index 875bf9db36264..56bed7a6e50aa 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/init_test_bed.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { HttpSetup } from '@kbn/core/public'; import { registerTestBed, TestBedConfig } from '@kbn/test-jest-helpers'; -import { HttpSetup } from '@kbn/core/public'; import { WithAppDependencies } from '../helpers'; -import { EditPolicy } from '../../../public/application/sections/edit_policy'; -import { AppServicesContext } from '../../../public/types'; +import { EditPolicy } from '../../public/application/sections/edit_policy'; +import { AppServicesContext } from '../../public/types'; import { POLICY_NAME } from './constants'; const getTestBedConfig = (testBedConfig?: Partial): TestBedConfig => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.helpers.ts similarity index 95% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.helpers.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.helpers.ts index 417f53c63a20c..e439fca0de512 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.helpers.ts @@ -6,7 +6,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { AppServicesContext } from '../../../../public/types'; +import { AppServicesContext } from '../../../public/types'; import { createColdPhaseActions, createDeletePhaseActions, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.test.ts similarity index 99% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.test.ts index 983cfbadfa017..05aa66fcc5f25 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/serialization/policy_serialization.test.ts @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { HttpFetchOptionsWithPath } from '@kbn/core/public'; import { setupEnvironment } from '../../helpers'; -import { API_BASE_PATH } from '../../../../common/constants'; +import { API_BASE_PATH } from '../../../common/constants'; import { getDefaultHotPhasePolicy, POLICY_WITH_INCLUDE_EXCLUDE, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/downsample_actions.ts similarity index 96% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/downsample_actions.ts index 315ed3d58520a..a389a9deebe32 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/downsample_actions.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; -import { Phase } from '../../../../common/types'; +import { TestBed } from '@kbn/test-jest-helpers'; +import { Phase } from '../../../common/types'; import { createFormToggleAction } from '..'; const createSetDownsampleIntervalAction = diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/errors_actions.ts similarity index 96% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/errors_actions.ts index 4b863071e191c..18f07734fadee 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/errors_actions.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; const createWaitForValidationAction = (testBed: TestBed) => () => { const { component } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/forcemerge_actions.ts similarity index 96% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/forcemerge_actions.ts index b6ed40de36ca9..619d6bd8be85f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/forcemerge_actions.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_set_value_action.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_set_value_action.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_toggle_action.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_toggle_action.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_toggle_and_set_value_action.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/form_toggle_and_set_value_action.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/index_priority_actions.ts similarity index 94% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/index_priority_actions.ts index 79fb77e53cc59..4c6e4604be7d4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/index_priority_actions.ts @@ -6,7 +6,7 @@ */ import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; import { createFormToggleAndSetValueAction } from './form_toggle_and_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/min_age_actions.ts similarity index 94% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/min_age_actions.ts index ef00fdc8d7757..8b4fa2e5f6179 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/min_age_actions.ts @@ -6,7 +6,7 @@ */ import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormSetValueAction } from './form_set_value_action'; export const createMinAgeActions = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/node_allocation_actions.ts similarity index 95% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/node_allocation_actions.ts index 2a680590654a8..a3cb607b60f99 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/node_allocation_actions.ts @@ -8,8 +8,8 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; -import { DataTierAllocationType } from '../../../../public/application/sections/edit_policy/types'; -import { Phase } from '../../../../common/types'; +import { DataTierAllocationType } from '../../../public/application/sections/edit_policy/types'; +import { Phase } from '../../../common/types'; import { createFormSetValueAction } from './form_set_value_action'; export const createNodeAllocationActions = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/phases.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/phases.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/readonly_actions.ts similarity index 92% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/readonly_actions.ts index 1a7ec56815b00..fe4c71d76265a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/readonly_actions.ts @@ -6,7 +6,7 @@ */ import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; export const createReadonlyActions = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/replicas_action.ts similarity index 92% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/replicas_action.ts index 43eca26a4e1ce..35c3afc607513 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/replicas_action.ts @@ -6,7 +6,7 @@ */ import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormToggleAndSetValueAction } from './form_toggle_and_set_value_action'; export const createReplicasAction = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/request_flyout_actions.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/request_flyout_actions.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/rollover_actions.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/rollover_actions.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/save_policy_action.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/save_policy_action.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/searchable_snapshot_actions.ts similarity index 96% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/searchable_snapshot_actions.ts index 3efffcddbece6..c9a019c2bc842 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/searchable_snapshot_actions.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; export const createSearchableSnapshotActions = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/shrink_actions.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/shrink_actions.ts index def20f73b82fe..4bf39185b8c03 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/shrink_actions.ts @@ -7,7 +7,7 @@ import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; import { createFormSetValueAction } from './form_set_value_action'; export const createShrinkActions = (testBed: TestBed, phase: Phase) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/snapshot_policy_actions.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/snapshot_policy_actions.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/toggle_phase_action.ts similarity index 96% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/toggle_phase_action.ts index c22efae87d5ac..fc89332e47a67 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/actions/toggle_phase_action.ts @@ -8,7 +8,7 @@ import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; -import { Phase } from '../../../../common/types'; +import { Phase } from '../../../common/types'; const toggleDeletePhase = async (testBed: TestBed) => { const { find, component } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/global_mocks.tsx b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/global_mocks.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/global_mocks.tsx rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/global_mocks.tsx diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/http_requests.ts similarity index 97% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/http_requests.ts index 7ddcd5358404f..70e85c8bc5df4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/http_requests.ts @@ -6,12 +6,12 @@ */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { API_BASE_PATH } from '../../../common/constants'; +import { API_BASE_PATH } from '../../common/constants'; import { ListNodesRouteResponse, ListSnapshotReposResponse, NodesDetailsResponse, -} from '../../../common/types'; +} from '../../common/types'; import { getDefaultHotPhasePolicy } from '../edit_policy/constants'; type HttpMethod = 'GET' | 'PUT' | 'DELETE' | 'POST'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx similarity index 80% rename from x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.tsx rename to x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx index 055097c883e94..91aebb485ea7f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx @@ -19,12 +19,12 @@ import { executionContextServiceMock, } from '@kbn/core/public/mocks'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { init as initHttp } from '../../../public/application/services/http'; +import { init as initHttp } from '../../public/application/services/http'; import { init as initHttpRequests } from './http_requests'; -import { init as initUiMetric } from '../../../public/application/services/ui_metric'; -import { init as initNotification } from '../../../public/application/services/notification'; -import { KibanaContextProvider } from '../../../public/shared_imports'; -import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock'; +import { init as initUiMetric } from '../../public/application/services/ui_metric'; +import { init as initNotification } from '../../public/application/services/notification'; +import { KibanaContextProvider } from '../../public/shared_imports'; +import { createBreadcrumbsMock } from '../../public/application/services/breadcrumbs.mock'; const breadcrumbService = createBreadcrumbsMock(); const appContextMock = { diff --git a/x-pack/plugins/index_lifecycle_management/jest.integration.config.js b/x-pack/plugins/index_lifecycle_management/jest.integration.config.js new file mode 100644 index 0000000000000..6d1ac5ec2fd8a --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/jest.integration.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test/jest_integration', + rootDir: '../../..', + roots: ['/x-pack/plugins/index_lifecycle_management'], + testMatch: ['/**/integration_tests/**/*.test.{js,mjs,ts,tsx}'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/index_lifecycle_management', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/index_lifecycle_management/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/index_lifecycle_management/tsconfig.json b/x-pack/plugins/index_lifecycle_management/tsconfig.json index d3a342e110211..4b5d7657ed9f6 100644 --- a/x-pack/plugins/index_lifecycle_management/tsconfig.json +++ b/x-pack/plugins/index_lifecycle_management/tsconfig.json @@ -8,6 +8,7 @@ }, "include": [ "__jest__/**/*", + "integration_tests/**/*", "common/**/*", "public/**/*", "server/**/*", From 0fbbd4f18ae78e4346366152192c4b2e0455ba24 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 28 Sep 2022 07:35:51 -0400 Subject: [PATCH 117/172] [Alerting][Docs] Adding link to ES docs for CCS setup (#141995) * Adding link to ES docs * Adding link to ES docs * Apply suggestions from code review Co-authored-by: Lisa Cawley Co-authored-by: Lisa Cawley --- docs/user/alerting/alerting-setup.asciidoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/user/alerting/alerting-setup.asciidoc b/docs/user/alerting/alerting-setup.asciidoc index 819f20005d7a4..36c8b46e7b801 100644 --- a/docs/user/alerting/alerting-setup.asciidoc +++ b/docs/user/alerting/alerting-setup.asciidoc @@ -98,3 +98,10 @@ user without those privileges updates the rule, the rule will no longer function. Conversely, if a user with greater or administrator privileges modifies the rule, it will begin running with increased privileges. ============================================== + +[float] +[[alerting-ccs-setup]] +=== {ccs-cap} + +If you want to use alerting rules with {ccs}, you must configure +{ref}/remote-clusters-privileges.html#clusters-privileges-ccs-kibana[privileges for {ccs-init} and {kib}]. \ No newline at end of file From 82492c791a3e1c27a88b8ceeecbe7a1d35c20ca5 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Wed, 28 Sep 2022 13:45:14 +0200 Subject: [PATCH 118/172] [Infra Monitoring UI] Change infrastructure pages titles to use breadcrumbs #135874 (#140824) * Use breadcrumbs to set page title intead of DocumentTitle * Remove document title component and use breadcrumbs - Logs Page * Remove no longer needed document title translations * Use docTitle.change in error page and remove document title component * Add document title check to the functional tests * Move test to check title after load * test: Title change * Remove unnecessary docTitle type * Add error page tests * Update snapshot * Functional tests: Wait for loading the header before checking the title * Change test to use react testing library Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/infra/public/apps/metrics_app.tsx | 1 + .../public/components/document_title.tsx | 72 ------------------- .../infra/public/hooks/use_breadcrumbs.ts | 9 ++- .../infra/public/hooks/use_document_title.tsx | 25 +++++++ .../infra/public/pages/logs/page_content.tsx | 3 - .../infra/public/pages/logs/stream/page.tsx | 2 - .../public/pages/logs/stream/page_header.tsx | 28 -------- .../public/pages/metrics/hosts/index.tsx | 13 ---- .../infra/public/pages/metrics/index.tsx | 7 -- .../pages/metrics/inventory_view/index.tsx | 14 ---- .../components/page_error.test.tsx | 47 ++++++++++++ .../metric_detail/components/page_error.tsx | 16 ++--- .../pages/metrics/metric_detail/index.tsx | 9 --- .../pages/metrics/metrics_explorer/index.tsx | 11 --- x-pack/plugins/infra/public/translations.ts | 4 ++ .../translations/translations/fr-FR.json | 7 +- .../translations/translations/ja-JP.json | 7 +- .../translations/translations/zh-CN.json | 7 +- .../test/functional/apps/infra/home_page.ts | 26 ++++++- x-pack/test/functional/apps/infra/link_to.ts | 2 + .../apps/infra/logs_source_configuration.ts | 10 +++ .../functional/apps/infra/metrics_explorer.ts | 8 +++ 22 files changed, 136 insertions(+), 192 deletions(-) delete mode 100644 x-pack/plugins/infra/public/components/document_title.tsx create mode 100644 x-pack/plugins/infra/public/hooks/use_document_title.tsx delete mode 100644 x-pack/plugins/infra/public/pages/logs/stream/page_header.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.test.tsx diff --git a/x-pack/plugins/infra/public/apps/metrics_app.tsx b/x-pack/plugins/infra/public/apps/metrics_app.tsx index fdeb7173d5cee..ce8123b5f2223 100644 --- a/x-pack/plugins/infra/public/apps/metrics_app.tsx +++ b/x-pack/plugins/infra/public/apps/metrics_app.tsx @@ -45,6 +45,7 @@ export const renderApp = ( ); return () => { + core.chrome.docTitle.reset(); ReactDOM.unmountComponentAtNode(element); plugins.data.search.session.clear(); }; diff --git a/x-pack/plugins/infra/public/components/document_title.tsx b/x-pack/plugins/infra/public/components/document_title.tsx deleted file mode 100644 index 20e482d9df5b5..0000000000000 --- a/x-pack/plugins/infra/public/components/document_title.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -type TitleProp = string | ((previousTitle: string) => string); - -interface DocumentTitleProps { - title: TitleProp; -} - -interface DocumentTitleState { - index: number; -} - -const wrapWithSharedState = () => { - const titles: string[] = []; - const TITLE_SUFFIX = ' - Kibana'; - - return class extends React.Component { - public componentDidMount() { - this.setState( - () => { - return { index: titles.push('') - 1 }; - }, - () => { - this.pushTitle(this.getTitle(this.props.title)); - this.updateDocumentTitle(); - } - ); - } - - public componentDidUpdate() { - this.pushTitle(this.getTitle(this.props.title)); - this.updateDocumentTitle(); - } - - public componentWillUnmount() { - this.removeTitle(); - this.updateDocumentTitle(); - } - - public render() { - return null; - } - - public getTitle(title: TitleProp) { - return typeof title === 'function' ? title(titles[this.state.index - 1]) : title; - } - - public pushTitle(title: string) { - titles[this.state.index] = title; - } - - public removeTitle() { - titles.pop(); - } - - public updateDocumentTitle() { - const title = (titles[titles.length - 1] || '') + TITLE_SUFFIX; - if (title !== document.title) { - document.title = title; - } - } - }; -}; - -export const DocumentTitle = wrapWithSharedState(); diff --git a/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts index 37801c16bcf1f..97f737380022d 100644 --- a/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts @@ -22,7 +22,7 @@ export const useBreadcrumbs = (app: AppId, appTitle: string, extraCrumbs: Chrome const appLinkProps = useLinkProps({ app }); useEffect(() => { - chrome?.setBreadcrumbs?.([ + const breadcrumbs = [ { ...observabilityLinkProps, text: observabilityTitle, @@ -32,6 +32,11 @@ export const useBreadcrumbs = (app: AppId, appTitle: string, extraCrumbs: Chrome text: appTitle, }, ...extraCrumbs, - ]); + ]; + + const docTitle = [...breadcrumbs].reverse().map((breadcrumb) => breadcrumb.text as string); + + chrome.docTitle.change(docTitle); + chrome.setBreadcrumbs(breadcrumbs); }, [appLinkProps, appTitle, chrome, extraCrumbs, observabilityLinkProps]); }; diff --git a/x-pack/plugins/infra/public/hooks/use_document_title.tsx b/x-pack/plugins/infra/public/hooks/use_document_title.tsx new file mode 100644 index 0000000000000..82fb5a669eb91 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_document_title.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 { ChromeBreadcrumb } from '@kbn/core/public'; +import { useEffect } from 'react'; +import { observabilityTitle } from '../translations'; +import { useKibanaContextForPlugin } from './use_kibana'; + +export const useDocumentTitle = (extraTitles: ChromeBreadcrumb[]) => { + const { + services: { chrome }, + } = useKibanaContextForPlugin(); + + useEffect(() => { + const docTitle = [{ text: observabilityTitle }, ...extraTitles] + .reverse() + .map((breadcrumb) => breadcrumb.text as string); + + chrome.docTitle.change(docTitle); + }, [chrome, extraTitles]); +}; diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index e42cfc2d7c54a..8acd4004603e9 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -12,7 +12,6 @@ import { Route, Switch } from 'react-router-dom'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-plugin/public'; import { AlertDropdown } from '../../alerting/log_threshold'; -import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider'; @@ -62,8 +61,6 @@ export const LogsPageContent: React.FunctionComponent = () => { return ( <> - - {setHeaderActionMenu && theme$ && ( diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx index bb8e4307fe3b9..19f098a6721bc 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { StreamPageContent } from './page_content'; -import { StreamPageHeader } from './page_header'; import { LogsPageProviders } from './page_providers'; import { streamTitle } from '../../../translations'; @@ -26,7 +25,6 @@ export const StreamPage = () => { return ( - diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_header.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_header.tsx deleted file mode 100644 index f6c4ad8c8c139..0000000000000 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_header.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -import { DocumentTitle } from '../../../components/document_title'; - -export const StreamPageHeader = () => { - return ( - <> - - i18n.translate('xpack.infra.logs.streamPage.documentTitle', { - defaultMessage: '{previousTitle} | Stream', - values: { - previousTitle, - }, - }) - } - /> - - ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx index 5c2fed9753b80..a5dfd7f2ddd0f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx @@ -6,13 +6,10 @@ */ import { EuiErrorBoundary } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; -import { DocumentTitle } from '../../../components/document_title'; - import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useSourceContext } from '../../../containers/metrics_source'; @@ -42,16 +39,6 @@ export const HostsPage = () => { ]); return ( - - i18n.translate('xpack.infra.infrastructureHostsPage.documentTitle', { - defaultMessage: '{previousTitle} | Hosts', - values: { - previousTitle, - }, - }) - } - /> {isLoading && !source ? ( ) : metricIndicesExist && source ? ( diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 9c02424aac949..691069a978e83 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -15,7 +15,6 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { HeaderMenuPortal } from '@kbn/observability-plugin/public'; import { useLinkProps } from '@kbn/observability-plugin/public'; import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources'; -import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; import { @@ -73,12 +72,6 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - - { return ( - - i18n.translate('xpack.infra.infrastructureSnapshotPage.documentTitle', { - defaultMessage: '{previousTitle} | Inventory', - values: { - previousTitle, - }, - }) - } - /> {isLoading && !source ? ( ) : metricIndicesExist ? ( diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.test.tsx new file mode 100644 index 0000000000000..25ae3b3717bd6 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { PageError } from './page_error'; +import { errorTitle } from '../../../../translations'; +import { InfraHttpError } from '../../../../types'; +import { useDocumentTitle } from '../../../../hooks/use_document_title'; +import { I18nProvider } from '@kbn/i18n-react'; + +jest.mock('../../../../hooks/use_document_title', () => ({ + useDocumentTitle: jest.fn(), +})); + +const renderErrorPage = () => + render( + + + + ); + +describe('PageError component', () => { + it('renders correctly and set title', () => { + const { getByText } = renderErrorPage(); + expect(useDocumentTitle).toHaveBeenCalledWith([{ text: `${errorTitle}` }]); + + expect(getByText('Error Message')).toBeInTheDocument(); + expect(getByText('Please click the back button and try again.')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx index a6665e0d244f2..b4cdb47399e98 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; +import { useDocumentTitle } from '../../../../hooks/use_document_title'; import { InvalidNodeError } from './invalid_node'; -import { DocumentTitle } from '../../../../components/document_title'; import { ErrorPageBody } from '../../../error'; import { InfraHttpError } from '../../../../types'; +import { errorTitle } from '../../../../translations'; interface Props { name: string; @@ -18,18 +18,10 @@ interface Props { } export const PageError = ({ error, name }: Props) => { + useDocumentTitle([{ text: errorTitle }]); + return ( <> - - i18n.translate('xpack.infra.metricDetailPage.documentTitleError', { - defaultMessage: '{previousTitle} | Uh oh', - values: { - previousTitle, - }, - }) - } - /> {error.body?.statusCode === 404 ? ( ) : ( diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx index 823b9d703f502..9b92901976bff 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { EuiTheme, withTheme } from '@kbn/kibana-react-plugin/common'; import { useLinkProps } from '@kbn/observability-plugin/public'; -import { DocumentTitle } from '../../../components/document_title'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from './hooks/use_metadata'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; @@ -100,14 +99,6 @@ export const MetricDetail = withMetricPageProviders( return ( <> - {metadata ? ( - - i18n.translate('xpack.infra.infrastructureMetricsExplorerPage.documentTitle', { - defaultMessage: '{previousTitle} | Metrics Explorer', - values: { - previousTitle, - }, - }) - } - /> { const esArchiver = getService('esArchiver'); + const browser = getService('browser'); const retry = getService('retry'); - const pageObjects = getPageObjects(['common', 'infraHome', 'infraSavedViews']); + const pageObjects = getPageObjects(['common', 'header', 'infraHome', 'infraSavedViews']); const kibanaServer = getService('kibanaServer'); describe('Home page', function () { @@ -34,6 +35,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp('infraOps'); await pageObjects.infraHome.getNoMetricsIndicesPrompt(); }); + + it('renders the correct error page title', async () => { + await pageObjects.common.navigateToUrlWithBrowserHistory( + 'infraOps', + '/detail/host/test', + '', + { + ensureCurrentUrl: false, + } + ); + await pageObjects.infraHome.waitForLoading(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain('Uh oh - Observability - Elastic'); + }); }); describe('with metrics present', () => { @@ -47,6 +64,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs') ); + it('renders the correct page title', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain('Inventory - Infrastructure - Observability - Elastic'); + }); + it('renders an empty data prompt for dates with no data', async () => { await pageObjects.infraHome.goToTime(DATE_WITHOUT_DATA); await pageObjects.infraHome.getNoMetricsDataPrompt(); diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index ebfcb740961b1..05eccc8e57ebc 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -42,6 +42,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); + const documentTitle = await browser.getTitle(); expect(parsedUrl.pathname).to.be('/app/logs/stream'); expect(parsedUrl.searchParams.get('logFilter')).to.be( @@ -51,6 +52,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { `(end:'${endDate}',position:(tiebreaker:0,time:${timestamp}),start:'${startDate}',streamLive:!f)` ); expect(parsedUrl.searchParams.get('sourceId')).to.be('default'); + expect(documentTitle).to.contain('Stream - Logs - Observability - Elastic'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index 56eed5ec4b635..38cc795034a22 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -16,6 +16,7 @@ const COMMON_REQUEST_HEADERS = { export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); + const browser = getService('browser'); const logsUi = getService('logsUi'); const infraSourceConfigurationForm = getService('infraSourceConfigurationForm'); const pageObjects = getPageObjects(['common', 'header', 'infraLogs']); @@ -49,6 +50,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); }); + it('renders the correct page title', async () => { + await pageObjects.infraLogs.navigateToTab('settings'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + const documentTitle = await browser.getTitle(); + + expect(documentTitle).to.contain('Settings - Logs - Observability - Elastic'); + }); + it('can change the log indices to a pattern that matches nothing', async () => { await pageObjects.infraLogs.navigateToTab('settings'); diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts index fc620d9ba5665..4d6859a4e99e7 100644 --- a/x-pack/test/functional/apps/infra/metrics_explorer.ts +++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts @@ -16,6 +16,7 @@ const timepickerFormat = 'MMM D, YYYY @ HH:mm:ss.SSS'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const browser = getService('browser'); const pageObjects = getPageObjects([ 'common', 'infraHome', @@ -42,6 +43,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); + it('should render the correct page title', async () => { + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain( + 'Metrics Explorer - Infrastructure - Observability - Elastic' + ); + }); + it('should have three metrics by default', async () => { const metrics = await pageObjects.infraMetricsExplorer.getMetrics(); expect(metrics.length).to.equal(3); From 5f057ff610139a88d660e48e41bd5c2648140800 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Wed, 28 Sep 2022 13:49:07 +0200 Subject: [PATCH 119/172] Add enrichment event log time (#141433) * Add enrichment event log time * fix types * Fix test * Add avg field * Fix enrichments event log * Add telemetry * Update schema Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/event_log/README.md | 1 + .../plugins/event_log/generated/mappings.json | 3 + x-pack/plugins/event_log/generated/schemas.ts | 1 + x-pack/plugins/event_log/scripts/mappings.js | 3 + .../model/execution_metrics.ts | 9 + .../client_for_executors/client.ts | 1 + .../client_for_executors/client_interface.ts | 1 + .../saved_objects_type.ts | 3 + .../create_security_rule_type_wrapper.ts | 5 + .../factories/bulk_create_factory.ts | 24 +- .../lib/detection_engine/rule_types/types.ts | 1 + .../rule_types/utils/index.ts | 1 + .../threat_mapping/create_threat_signals.ts | 1 + .../signals/threat_mapping/utils.test.ts | 38 ++ .../signals/threat_mapping/utils.ts | 4 + .../lib/detection_engine/signals/types.ts | 1 + .../detection_engine/signals/utils.test.ts | 13 + .../lib/detection_engine/signals/utils.ts | 7 + .../server/usage/collector.ts | 252 ++++++++++ .../detections/rules/get_initial_usage.ts | 1 + .../detections/rules/get_metrics.mocks.ts | 414 ++++++++++++++++ .../server/usage/detections/rules/types.ts | 1 + ...event_log_agg_by_rule_type_metrics.test.ts | 15 + .../get_event_log_agg_by_rule_type_metrics.ts | 15 + ...vent_log_agg_by_rule_types_metrics.test.ts | 60 +++ .../get_event_log_agg_by_statuses.test.ts | 60 +++ .../transform_single_rule_metric.test.ts | 14 + .../utils/transform_single_rule_metric.ts | 5 + .../security_solution/server/usage/types.ts | 9 + .../schema/xpack_plugins.json | 468 ++++++++++++++++-- 30 files changed, 1394 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md index c1d5869e7ed48..05b05946b652d 100644 --- a/x-pack/plugins/event_log/README.md +++ b/x-pack/plugins/event_log/README.md @@ -159,6 +159,7 @@ Below is a document in the expected structure, with descriptions of the fields: es_search_duration_ms: "total time spent performing ES searches as measured by Elasticsearch", total_search_duration_ms: "total time spent performing ES searches as measured by Kibana; includes network latency and time spent serializing/deserializing request/response", total_indexing_duration_ms: "total time spent indexing documents during current rule execution cycle", + total_enrichment_duration_ms: "total time spent enriching documents during current rule execution cycle", execution_gap_duration_s: "duration in seconds of execution gap" } } diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index db6719fed996a..4756b4f2e5534 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -347,6 +347,9 @@ }, "total_run_duration_ms": { "type": "long" + }, + "total_enrichment_duration_ms": { + "type": "long" } } } diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index b5557f64e9ac7..523ad683eabf2 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -150,6 +150,7 @@ export const EventSchema = schema.maybe( claim_to_start_duration_ms: ecsStringOrNumber(), prepare_rule_duration_ms: ecsStringOrNumber(), total_run_duration_ms: ecsStringOrNumber(), + total_enrichment_duration_ms: ecsStringOrNumber(), }) ), }) diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 98050b6557210..65c9220fb5355 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -130,6 +130,9 @@ exports.EcsCustomPropertyMappings = { total_run_duration_ms: { type: 'long', }, + total_enrichment_duration_ms: { + type: 'long', + }, }, }, }, diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/model/execution_metrics.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/model/execution_metrics.ts index c6bb71970e599..b15c76119e441 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/model/execution_metrics.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/model/execution_metrics.ts @@ -12,8 +12,17 @@ export type DurationMetric = t.TypeOf; export const DurationMetric = PositiveInteger; export type RuleExecutionMetrics = t.TypeOf; + +/** + @property total_search_duration_ms - "total time spent performing ES searches as measured by Kibana; + includes network latency and time spent serializing/deserializing request/response", + @property total_indexing_duration_ms - "total time spent indexing documents during current rule execution cycle", + @property total_enrichment_duration_ms - total time spent enriching documents during current rule execution cycle + @property execution_gap_duration_s - "duration in seconds of execution gap" +*/ export const RuleExecutionMetrics = t.partial({ total_search_duration_ms: DurationMetric, total_indexing_duration_ms: DurationMetric, + total_enrichment_duration_ms: DurationMetric, execution_gap_duration_s: DurationMetric, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts index 4116848b1ffcf..3c712847851fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts @@ -222,6 +222,7 @@ const normalizeStatusChangeArgs = (args: StatusChangeArgs): NormalizedStatusChan ? { total_search_duration_ms: normalizeDurations(metrics.searchDurations), total_indexing_duration_ms: normalizeDurations(metrics.indexingDurations), + total_enrichment_duration_ms: normalizeDurations(metrics.enrichmentDurations), execution_gap_duration_s: normalizeGap(metrics.executionGap), } : undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client_interface.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client_interface.ts index 22392e699fcea..5e48a43e949c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client_interface.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client_interface.ts @@ -115,5 +115,6 @@ export interface StatusChangeArgs { export interface MetricsArgs { searchDurations?: string[]; indexingDurations?: string[]; + enrichmentDurations?: string[]; executionGap?: Duration; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/execution_saved_object/saved_objects_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/execution_saved_object/saved_objects_type.ts index ac3b28e87e0d9..1e0a2e74cadcd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/execution_saved_object/saved_objects_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/execution_saved_object/saved_objects_type.ts @@ -58,6 +58,9 @@ const ruleExecutionMappings: SavedObjectsType['mappings'] = { total_indexing_duration_ms: { type: 'long', }, + total_enrichment_duration_ms: { + type: 'long', + }, execution_gap_duration_s: { type: 'long', }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index ae3f310d841c3..05813ed1ee29c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -343,6 +343,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const warningMessages = result.warningMessages.concat(runResult.warningMessages); result = { bulkCreateTimes: result.bulkCreateTimes.concat(runResult.bulkCreateTimes), + enrichmentTimes: result.enrichmentTimes.concat(runResult.enrichmentTimes), createdSignals, createdSignalsCount: createdSignals.length, errors: result.errors.concat(runResult.errors), @@ -358,6 +359,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = } else { result = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignals: [], createdSignalsCount: 0, errors: [], @@ -434,6 +436,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, + enrichmentDurations: result.enrichmentTimes, }, }); } @@ -452,6 +455,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, + enrichmentDurations: result.enrichmentTimes, }, }); } @@ -464,6 +468,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, + enrichmentDurations: result.enrichmentTimes, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts index 81f15364ff128..95bc32571f6ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts @@ -21,6 +21,7 @@ import type { export interface GenericBulkCreateResponse { success: boolean; bulkCreateDuration: string; + enrichmentDuration: string; createdItemsCount: number; createdItems: Array & { _id: string; _index: string }>; errors: string[]; @@ -45,6 +46,7 @@ export const bulkCreateFactory = return { errors: [], success: true, + enrichmentDuration: '0', bulkCreateDuration: '0', createdItemsCount: 0, createdItems: [], @@ -54,6 +56,24 @@ export const bulkCreateFactory = const start = performance.now(); + let enrichmentsTimeStart = 0; + let enrichmentsTimeFinish = 0; + let enrichAlertsWrapper: typeof enrichAlerts; + if (enrichAlerts) { + enrichAlertsWrapper = async (alerts, params) => { + enrichmentsTimeStart = performance.now(); + try { + const enrichedAlerts = await enrichAlerts(alerts, params); + return enrichedAlerts; + } catch (error) { + ruleExecutionLogger.error(`Enrichments failed ${error}`); + throw error; + } finally { + enrichmentsTimeFinish = performance.now(); + } + }; + } + const { createdAlerts, errors, alertsWereTruncated } = await alertWithPersistence( wrappedDocs.map((doc) => ({ _id: doc._id, @@ -62,7 +82,7 @@ export const bulkCreateFactory = })), refreshForBulkCreate, maxAlerts, - enrichAlerts + enrichAlertsWrapper ); const end = performance.now(); @@ -78,6 +98,7 @@ export const bulkCreateFactory = return { errors: Object.keys(errors), success: false, + enrichmentDuration: makeFloatString(enrichmentsTimeFinish - enrichmentsTimeStart), bulkCreateDuration: makeFloatString(end - start), createdItemsCount: createdAlerts.length, createdItems: createdAlerts, @@ -88,6 +109,7 @@ export const bulkCreateFactory = errors: [], success: true, bulkCreateDuration: makeFloatString(end - start), + enrichmentDuration: makeFloatString(enrichmentsTimeFinish - enrichmentsTimeStart), createdItemsCount: createdAlerts.length, createdItems: createdAlerts, alertsWereTruncated, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 403d6542f4f0c..e1af3077ec3e3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -42,6 +42,7 @@ import type { IRuleExecutionLogForExecutors, IRuleExecutionLogService } from '.. export interface SecurityAlertTypeReturnValue { bulkCreateTimes: string[]; + enrichmentTimes: string[]; createdSignalsCount: number; createdSignals: unknown[]; errors: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts index 5d2dcd4a4b3d2..8f23da386f5c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts @@ -10,6 +10,7 @@ import type { SecurityAlertTypeReturnValue } from '../types'; export const createResultObject = (state: TState) => { const result: SecurityAlertTypeReturnValue = { + enrichmentTimes: [], bulkCreateTimes: [], createdSignalsCount: 0, createdSignals: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index d80e5cba026b0..0ab6ec2a01dda 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -65,6 +65,7 @@ export const createThreatSignals = async ({ let results: SearchAfterAndBulkCreateReturnType = { success: true, warning: false, + enrichmentTimes: [], bulkCreateTimes: [], searchAfterTimes: [], lastLookBackDate: null, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index 981868589e4a1..0bcdc8450a830 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -55,6 +55,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -67,6 +68,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -83,6 +85,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -95,6 +98,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -111,6 +115,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -123,6 +128,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -139,6 +145,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -151,6 +158,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -162,6 +170,7 @@ describe('utils', () => { expect.objectContaining({ searchAfterTimes: ['60'], bulkCreateTimes: ['50'], + enrichmentTimes: ['6'], }) ); }); @@ -172,6 +181,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -184,6 +194,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -296,6 +307,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -307,6 +319,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + enrichmentTimes: ['3'], // max value from existingResult.enrichmentTimes lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -323,6 +336,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -334,6 +348,7 @@ describe('utils', () => { warning: false, searchAfterTimes: [], bulkCreateTimes: [], + enrichmentTimes: [], lastLookBackDate: undefined, createdSignalsCount: 0, createdSignals: [], @@ -345,6 +360,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + enrichmentTimes: ['3'], // max value from existingResult.enrichmentTimes lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -362,6 +378,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], // max is 30 bulkCreateTimes: ['5', '15', '25'], // max is 25 + enrichmentTimes: ['1', '2', '3'], // max is 3 lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -373,6 +390,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), @@ -384,6 +402,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['40', '5', '15'], bulkCreateTimes: ['50', '5', '15'], + enrichmentTimes: ['4', '2', '3'], lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), createdSignalsCount: 8, createdSignals: Array(8).fill(sampleSignalHit()), @@ -396,6 +415,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + enrichmentTimes: ['7'], // max value between newResult1 and newResult2 + max array value of existingResult (4 + 3 = 7) lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) createdSignals: Array(16).fill(sampleSignalHit()), @@ -413,6 +433,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], // max is 30 bulkCreateTimes: ['5', '15', '25'], // max is 25 + enrichmentTimes: ['1', '2', '3'], // max is 3 lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -424,6 +445,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), @@ -435,6 +457,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['40', '5', '15'], bulkCreateTimes: ['50', '5', '15'], + enrichmentTimes: ['5', '2', '3'], lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), createdSignalsCount: 8, createdSignals: Array(8).fill(sampleSignalHit()), @@ -447,6 +470,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + enrichmentTimes: ['8'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 3 = 8) lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) createdSignals: Array(16).fill(sampleSignalHit()), @@ -464,6 +488,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], // max is 30 bulkCreateTimes: ['5', '15', '25'], // max is 25 + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -475,6 +500,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), @@ -486,6 +512,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['40', '5', '15'], bulkCreateTimes: ['50', '5', '15'], + enrichmentTimes: ['5', '2', '3'], lastLookBackDate: null, createdSignalsCount: 8, createdSignals: Array(8).fill(sampleSignalHit()), @@ -498,6 +525,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + enrichmentTimes: ['8'], // max value between newResult1 and newResult2 + max array value of existingResult (5 + 3 = 8) lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), // max lastLookBackDate createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) createdSignals: Array(16).fill(sampleSignalHit()), @@ -515,6 +543,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -527,6 +556,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['5', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -543,6 +573,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -555,6 +586,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -571,6 +603,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -583,6 +616,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -599,6 +633,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -611,6 +646,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -632,6 +668,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: undefined, createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), @@ -644,6 +681,7 @@ describe('utils', () => { warning: false, searchAfterTimes: ['10', '20', '30'], bulkCreateTimes: ['5', '15', '25'], + enrichmentTimes: ['1', '2', '3'], lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts index bfba9a6fd22a1..a73ead0bf946d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts @@ -70,6 +70,7 @@ export const combineResults = ( ): SearchAfterAndBulkCreateReturnType => ({ success: currentResult.success === false ? false : newResult.success, warning: currentResult.warning || newResult.warning, + enrichmentTimes: calculateAdditiveMax(currentResult.enrichmentTimes, newResult.enrichmentTimes), bulkCreateTimes: calculateAdditiveMax(currentResult.bulkCreateTimes, newResult.bulkCreateTimes), searchAfterTimes: calculateAdditiveMax( currentResult.searchAfterTimes, @@ -94,6 +95,7 @@ export const combineConcurrentResults = ( const maxedNewResult = newResult.reduce( (accum, item) => { const maxSearchAfterTime = calculateMax(accum.searchAfterTimes, item.searchAfterTimes); + const maxEnrichmentTimes = calculateMax(accum.enrichmentTimes, item.enrichmentTimes); const maxBulkCreateTimes = calculateMax(accum.bulkCreateTimes, item.bulkCreateTimes); const lastLookBackDate = calculateMaxLookBack(accum.lastLookBackDate, item.lastLookBackDate); return { @@ -101,6 +103,7 @@ export const combineConcurrentResults = ( warning: accum.warning || item.warning, searchAfterTimes: [maxSearchAfterTime], bulkCreateTimes: [maxBulkCreateTimes], + enrichmentTimes: [maxEnrichmentTimes], lastLookBackDate, createdSignalsCount: accum.createdSignalsCount + item.createdSignalsCount, createdSignals: [...accum.createdSignals, ...item.createdSignals], @@ -113,6 +116,7 @@ export const combineConcurrentResults = ( warning: false, searchAfterTimes: [], bulkCreateTimes: [], + enrichmentTimes: [], lastLookBackDate: undefined, createdSignalsCount: 0, createdSignals: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 93ddadf826d73..f786a28b50044 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -286,6 +286,7 @@ export interface SearchAfterAndBulkCreateReturnType { success: boolean; warning: boolean; searchAfterTimes: string[]; + enrichmentTimes: string[]; bulkCreateTimes: string[]; lastLookBackDate: Date | null | undefined; createdSignalsCount: number; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index d5603130400c7..d80ed256a0de0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -953,6 +953,7 @@ describe('utils', () => { }); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignalsCount: 0, createdSignals: [], errors: [], @@ -973,6 +974,7 @@ describe('utils', () => { }); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignalsCount: 0, createdSignals: [], errors: [], @@ -1291,6 +1293,7 @@ describe('utils', () => { const searchAfterReturnType = createSearchAfterReturnType(); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignalsCount: 0, createdSignals: [], errors: [], @@ -1306,6 +1309,7 @@ describe('utils', () => { test('createSearchAfterReturnType can override all values', () => { const searchAfterReturnType = createSearchAfterReturnType({ bulkCreateTimes: ['123'], + enrichmentTimes: [], createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), errors: ['error 1'], @@ -1317,6 +1321,7 @@ describe('utils', () => { }); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: ['123'], + enrichmentTimes: [], createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), errors: ['error 1'], @@ -1337,6 +1342,7 @@ describe('utils', () => { }); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignalsCount: 5, createdSignals: Array(5).fill(sampleSignalHit()), errors: ['error 1'], @@ -1355,6 +1361,7 @@ describe('utils', () => { const merged = mergeReturns([createSearchAfterReturnType(), createSearchAfterReturnType()]); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: [], + enrichmentTimes: [], createdSignalsCount: 0, createdSignals: [], errors: [], @@ -1411,6 +1418,7 @@ describe('utils', () => { const merged = mergeReturns([ createSearchAfterReturnType({ bulkCreateTimes: ['123'], + enrichmentTimes: [], createdSignalsCount: 3, createdSignals: Array(3).fill(sampleSignalHit()), errors: ['error 1', 'error 2'], @@ -1421,6 +1429,7 @@ describe('utils', () => { }), createSearchAfterReturnType({ bulkCreateTimes: ['456'], + enrichmentTimes: [], createdSignalsCount: 2, createdSignals: Array(2).fill(sampleSignalHit()), errors: ['error 3'], @@ -1433,6 +1442,7 @@ describe('utils', () => { ]); const expected: SearchAfterAndBulkCreateReturnType = { bulkCreateTimes: ['123', '456'], // concatenates the prev and next together + enrichmentTimes: [], createdSignalsCount: 5, // Adds the 3 and 2 together createdSignals: Array(5).fill(sampleSignalHit()), errors: ['error 1', 'error 2', 'error 3'], // concatenates the prev and next together @@ -1452,6 +1462,7 @@ describe('utils', () => { const next: GenericBulkCreateResponse = { success: false, bulkCreateDuration: '100', + enrichmentDuration: '0', createdItemsCount: 1, createdItems: [], errors: ['new error'], @@ -1469,6 +1480,7 @@ describe('utils', () => { const next: GenericBulkCreateResponse = { success: true, bulkCreateDuration: '0', + enrichmentDuration: '0', createdItemsCount: 0, createdItems: [], errors: ['error 1'], @@ -1484,6 +1496,7 @@ describe('utils', () => { const next: GenericBulkCreateResponse = { success: true, bulkCreateDuration: '0', + enrichmentDuration: '0', createdItemsCount: 0, createdItems: [], errors: ['error 2'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 6030400da2b75..24b5b068d5689 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -649,6 +649,7 @@ export const createSearchAfterReturnType = ({ success, warning, searchAfterTimes, + enrichmentTimes, bulkCreateTimes, lastLookBackDate, createdSignalsCount, @@ -659,6 +660,7 @@ export const createSearchAfterReturnType = ({ success?: boolean | undefined; warning?: boolean; searchAfterTimes?: string[] | undefined; + enrichmentTimes?: string[] | undefined; bulkCreateTimes?: string[] | undefined; lastLookBackDate?: Date | undefined; createdSignalsCount?: number | undefined; @@ -670,6 +672,7 @@ export const createSearchAfterReturnType = ({ success: success ?? true, warning: warning ?? false, searchAfterTimes: searchAfterTimes ?? [], + enrichmentTimes: enrichmentTimes ?? [], bulkCreateTimes: bulkCreateTimes ?? [], lastLookBackDate: lastLookBackDate ?? null, createdSignalsCount: createdSignalsCount ?? 0, @@ -715,6 +718,7 @@ export const addToSearchAfterReturn = ({ current.createdSignalsCount += next.createdItemsCount; current.createdSignals.push(...next.createdItems); current.bulkCreateTimes.push(next.bulkCreateDuration); + current.enrichmentTimes.push(next.enrichmentDuration); current.errors = [...new Set([...current.errors, ...next.errors])]; }; @@ -727,6 +731,7 @@ export const mergeReturns = ( warning: existingWarning, searchAfterTimes: existingSearchAfterTimes, bulkCreateTimes: existingBulkCreateTimes, + enrichmentTimes: existingEnrichmentTimes, lastLookBackDate: existingLastLookBackDate, createdSignalsCount: existingCreatedSignalsCount, createdSignals: existingCreatedSignals, @@ -738,6 +743,7 @@ export const mergeReturns = ( success: newSuccess, warning: newWarning, searchAfterTimes: newSearchAfterTimes, + enrichmentTimes: newEnrichmentTimes, bulkCreateTimes: newBulkCreateTimes, lastLookBackDate: newLastLookBackDate, createdSignalsCount: newCreatedSignalsCount, @@ -750,6 +756,7 @@ export const mergeReturns = ( success: existingSuccess && newSuccess, warning: existingWarning || newWarning, searchAfterTimes: [...existingSearchAfterTimes, ...newSearchAfterTimes], + enrichmentTimes: [...existingEnrichmentTimes, ...newEnrichmentTimes], bulkCreateTimes: [...existingBulkCreateTimes, ...newBulkCreateTimes], lastLookBackDate: newLastLookBackDate ?? existingLastLookBackDate, createdSignalsCount: existingCreatedSignalsCount + newCreatedSignalsCount, diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index e14af8c553736..6f526051f17d1 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -414,6 +414,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -500,6 +514,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -586,6 +614,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -672,6 +714,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -758,6 +814,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -844,6 +914,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -946,6 +1030,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1032,6 +1130,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1118,6 +1230,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1204,6 +1330,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1290,6 +1430,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1376,6 +1530,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1478,6 +1646,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1564,6 +1746,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1650,6 +1846,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1736,6 +1946,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1822,6 +2046,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', @@ -1908,6 +2146,20 @@ export const registerCollector: RegisterCollector = ({ _meta: { description: 'The min duration' }, }, }, + enrichment_duration: { + max: { + type: 'float', + _meta: { description: 'The max duration' }, + }, + avg: { + type: 'float', + _meta: { description: 'The avg duration' }, + }, + min: { + type: 'float', + _meta: { description: 'The min duration' }, + }, + }, gap_duration: { max: { type: 'float', diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts index eb32c58bda6cf..3ad6b5740b53e 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts @@ -144,6 +144,7 @@ export const getInitialSingleEventMetric = (): SingleEventMetric => ({ succeeded: 0, index_duration: getInitialMaxAvgMin(), search_duration: getInitialMaxAvgMin(), + enrichment_duration: getInitialMaxAvgMin(), gap_duration: getInitialMaxAvgMin(), gap_count: 0, }); diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/get_metrics.mocks.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/get_metrics.mocks.ts index 129d2b32d0b3d..83eb7df28a026 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/get_metrics.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/get_metrics.mocks.ts @@ -155,6 +155,15 @@ export const getEventLogAllRules = (): SearchResponse ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threat_match: { @@ -2506,6 +2835,11 @@ export const getEventLogAllRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, machine_learning: { @@ -2529,6 +2863,11 @@ export const getEventLogAllRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, query: { @@ -2594,6 +2933,11 @@ export const getEventLogAllRulesResult = (): SingleEventLogStatusMetric => ({ avg: 4246.375, min: 2811, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 6, }, saved_query: { @@ -2617,6 +2961,11 @@ export const getEventLogAllRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threshold: { @@ -2640,6 +2989,11 @@ export const getEventLogAllRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, total: { @@ -2676,6 +3030,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threat_match: { @@ -2699,6 +3058,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, machine_learning: { @@ -2722,6 +3086,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, query: { @@ -2772,6 +3141,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 4141.75, min: 2811, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 4, }, saved_query: { @@ -2795,6 +3169,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threshold: { @@ -2818,6 +3197,11 @@ export const getEventLogElasticRulesResult = (): SingleEventLogStatusMetric => ( avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, total: { @@ -2854,6 +3238,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threat_match: { @@ -2877,6 +3266,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, machine_learning: { @@ -2900,6 +3294,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, query: { @@ -2940,6 +3339,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 4351, min: 3051, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 2, }, saved_query: { @@ -2963,6 +3367,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, threshold: { @@ -2986,6 +3395,11 @@ export const getEventLogCustomRulesResult = (): SingleEventLogStatusMetric => ({ avg: 0, min: 0, }, + enrichment_duration: { + max: 0, + avg: 0, + min: 0, + }, gap_count: 0, }, total: { diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts index 499c79b11fcfa..84fb656f793d4 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts @@ -85,6 +85,7 @@ export interface SingleEventMetric { succeeded: number; index_duration: MaxAvgMin; search_duration: MaxAvgMin; + enrichment_duration: MaxAvgMin; gap_duration: MaxAvgMin; gap_count: number; } diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.test.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.test.ts index 09a988fbf02ef..693e3579d7c3a 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.test.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.test.ts @@ -68,6 +68,21 @@ describe('get_event_log_agg_by_rule_type_metrics', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }); }); diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.ts index 6fe8103e29a0d..957f56809d64f 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_type_metrics.ts @@ -74,6 +74,21 @@ export const getEventLogAggByRuleTypeMetrics = ( field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }; }; diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_types_metrics.test.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_types_metrics.test.ts index 22261ac48812c..4673ca1b6bcb8 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_types_metrics.test.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_rule_types_metrics.test.ts @@ -74,6 +74,21 @@ describe('get_event_log_agg_by_rule_types_metrics', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }); @@ -139,6 +154,21 @@ describe('get_event_log_agg_by_rule_types_metrics', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }); @@ -204,6 +234,21 @@ describe('get_event_log_agg_by_rule_types_metrics', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, 'siem.indicatorRule': { @@ -263,6 +308,21 @@ describe('get_event_log_agg_by_rule_types_metrics', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }); diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_statuses.test.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_statuses.test.ts index 7d474769bd79f..a87046660fe16 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_statuses.test.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/get_event_log_agg_by_statuses.test.ts @@ -137,6 +137,21 @@ describe('get_event_log_agg_by_statuses', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }, @@ -246,6 +261,21 @@ describe('get_event_log_agg_by_statuses', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }, @@ -418,6 +448,21 @@ describe('get_event_log_agg_by_statuses', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, 'siem.thresholdRule': { @@ -477,6 +522,21 @@ describe('get_event_log_agg_by_statuses', () => { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms', }, }, + maxTotalEnrichmentDuration: { + max: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + minTotalEnrichmentDuration: { + min: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, + avgTotalEnrichmentDuration: { + avg: { + field: 'kibana.alert.rule.execution.metrics.total_enrichment_duration_ms', + }, + }, }, }, }, diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.test.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.test.ts index c64f0833fe851..9c5810011a975 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.test.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.test.ts @@ -85,6 +85,15 @@ describe('transform_single_rule_metric', () => { minTotalSearchDuration: { value: 12, }, + minTotalEnrichmentDuration: { + value: 4, + }, + maxTotalEnrichmentDuration: { + value: 2, + }, + avgTotalEnrichmentDuration: { + value: 12, + }, }, }); @@ -131,6 +140,11 @@ describe('transform_single_rule_metric', () => { avg: 2, min: 9, }, + enrichment_duration: { + max: 2, + avg: 12, + min: 4, + }, gap_count: 4, }); }); diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.ts index bebd867fb195f..5b3b6f8b7ebb2 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/utils/transform_single_rule_metric.ts @@ -52,6 +52,11 @@ export const transformSingleRuleMetric = ({ avg: singleMetric.avgTotalSearchDuration.value ?? 0.0, min: singleMetric.minTotalSearchDuration.value ?? 0.0, }, + enrichment_duration: { + max: singleMetric?.maxTotalEnrichmentDuration?.value ?? 0.0, + avg: singleMetric?.avgTotalEnrichmentDuration?.value ?? 0.0, + min: singleMetric?.minTotalEnrichmentDuration?.value ?? 0.0, + }, gap_duration: { max: singleMetric.maxGapDuration.value ?? 0.0, avg: singleMetric.avgGapDuration.value ?? 0.0, diff --git a/x-pack/plugins/security_solution/server/usage/types.ts b/x-pack/plugins/security_solution/server/usage/types.ts index fe7711196303c..a6db2e3d71e91 100644 --- a/x-pack/plugins/security_solution/server/usage/types.ts +++ b/x-pack/plugins/security_solution/server/usage/types.ts @@ -121,6 +121,15 @@ export interface SingleExecutionMetricAgg { minTotalSearchDuration: { value: number | null; }; + maxTotalEnrichmentDuration: { + value: number | null; + }; + avgTotalEnrichmentDuration: { + value: number | null; + }; + minTotalEnrichmentDuration: { + value: number | null; + }; } export interface EventLogTypeStatusAggs { diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 0c389000f6c80..9a47ad9de093e 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4644,6 +4644,18 @@ "properties": { "all": { "properties": { + "total": { + "type": "long" + }, + "monthly": { + "type": "long" + }, + "weekly": { + "type": "long" + }, + "daily": { + "type": "long" + }, "assignees": { "properties": { "total": { @@ -4657,18 +4669,6 @@ } } }, - "total": { - "type": "long" - }, - "monthly": { - "type": "long" - }, - "weekly": { - "type": "long" - }, - "daily": { - "type": "long" - }, "status": { "properties": { "open": { @@ -4720,6 +4720,18 @@ }, "sec": { "properties": { + "total": { + "type": "long" + }, + "monthly": { + "type": "long" + }, + "weekly": { + "type": "long" + }, + "daily": { + "type": "long" + }, "assignees": { "properties": { "total": { @@ -4732,7 +4744,11 @@ "type": "long" } } - }, + } + } + }, + "obs": { + "properties": { "total": { "type": "long" }, @@ -4744,11 +4760,7 @@ }, "daily": { "type": "long" - } - } - }, - "obs": { - "properties": { + }, "assignees": { "properties": { "total": { @@ -4761,7 +4773,11 @@ "type": "long" } } - }, + } + } + }, + "main": { + "properties": { "total": { "type": "long" }, @@ -4773,11 +4789,7 @@ }, "daily": { "type": "long" - } - } - }, - "main": { - "properties": { + }, "assignees": { "properties": { "total": { @@ -4790,18 +4802,6 @@ "type": "long" } } - }, - "total": { - "type": "long" - }, - "monthly": { - "type": "long" - }, - "weekly": { - "type": "long" - }, - "daily": { - "type": "long" } } } @@ -10029,6 +10029,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10161,6 +10183,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10293,6 +10337,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10425,6 +10491,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10557,6 +10645,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10689,6 +10799,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10847,6 +10979,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -10979,6 +11133,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11111,6 +11287,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11243,6 +11441,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11375,6 +11595,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11507,6 +11749,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11665,6 +11929,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11797,6 +12083,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -11929,6 +12237,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -12061,6 +12391,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -12193,6 +12545,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { @@ -12325,6 +12699,28 @@ } } }, + "enrichment_duration": { + "properties": { + "max": { + "type": "float", + "_meta": { + "description": "The max duration" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "The avg duration" + } + }, + "min": { + "type": "float", + "_meta": { + "description": "The min duration" + } + } + } + }, "gap_duration": { "properties": { "max": { From 4c12ff3fddf6edab74147ce9a714bc1e5e91c65e Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 28 Sep 2022 08:11:57 -0400 Subject: [PATCH 120/172] chore(slo): remove space slo resources (#141662) --- .../observability/server/assets/constants.ts | 8 +--- x-pack/plugins/observability/server/plugin.ts | 2 - .../server/routes/register_routes.ts | 4 -- .../observability/server/routes/slo/route.ts | 22 ++------- .../server/services/slo/delete_slo.test.ts | 1 + .../server/services/slo/delete_slo.ts | 3 ++ .../services/slo/resource_installer.test.ts | 19 ++------ .../server/services/slo/resource_installer.ts | 23 ++++------ .../apm_transaction_duration.test.ts.snap | 4 +- .../apm_transaction_error_rate.test.ts.snap | 4 +- .../apm_transaction_duration.test.ts | 4 +- .../apm_transaction_duration.ts | 14 +++--- .../apm_transaction_error_rate.test.ts | 6 +-- .../apm_transaction_error_rate.ts | 14 +++--- .../transform_generator.ts | 2 +- .../services/slo/transform_manager.test.ts | 46 +++---------------- .../server/services/slo/transform_manager.ts | 5 +- 17 files changed, 58 insertions(+), 123 deletions(-) diff --git a/x-pack/plugins/observability/server/assets/constants.ts b/x-pack/plugins/observability/server/assets/constants.ts index 4c0cc0e2e6f83..182ca89712dcc 100644 --- a/x-pack/plugins/observability/server/assets/constants.ts +++ b/x-pack/plugins/observability/server/assets/constants.ts @@ -9,11 +9,7 @@ export const SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME = 'slo-observability.sli-mappi export const SLO_COMPONENT_TEMPLATE_SETTINGS_NAME = 'slo-observability.sli-settings'; export const SLO_INDEX_TEMPLATE_NAME = 'slo-observability.sli'; export const SLO_RESOURCES_VERSION = 1; - -export const getSLOIngestPipelineName = (spaceId: string) => - `${SLO_INDEX_TEMPLATE_NAME}.monthly-${spaceId}`; - -export const getSLODestinationIndexName = (spaceId: string) => - `${SLO_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}-${spaceId}`; +export const SLO_INGEST_PIPELINE_NAME = `${SLO_INDEX_TEMPLATE_NAME}.monthly`; +export const SLO_DESTINATION_INDEX_NAME = `${SLO_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}`; export const getSLOTransformId = (sloId: string) => `slo-${sloId}`; diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 1d9d3cbf455f1..ff5fd246bea1b 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -144,7 +144,6 @@ export class ObservabilityPlugin implements Plugin { const start = () => core.getStartServices().then(([coreStart]) => coreStart); - const { spacesService } = plugins.spaces; const { ruleDataService } = plugins.ruleRegistry; registerRoutes({ @@ -155,7 +154,6 @@ export class ObservabilityPlugin implements Plugin { logger: this.initContext.logger.get(), repository: getGlobalObservabilityServerRouteRepository(config), ruleDataService, - spacesService, }); return { diff --git a/x-pack/plugins/observability/server/routes/register_routes.ts b/x-pack/plugins/observability/server/routes/register_routes.ts index e0e9e94f5cb21..1c45eb6001479 100644 --- a/x-pack/plugins/observability/server/routes/register_routes.ts +++ b/x-pack/plugins/observability/server/routes/register_routes.ts @@ -14,7 +14,6 @@ import { CoreSetup, CoreStart, Logger, RouteRegistrar } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import { RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; -import { SpacesServiceStart } from '@kbn/spaces-plugin/server'; import { ObservabilityRequestHandlerContext } from '../types'; import { AbstractObservabilityServerRouteRepository } from './types'; import { getHTTPResponseCode, ObservabilityError } from '../errors'; @@ -24,7 +23,6 @@ export function registerRoutes({ core, logger, ruleDataService, - spacesService, }: { core: { setup: CoreSetup; @@ -33,7 +31,6 @@ export function registerRoutes({ repository: AbstractObservabilityServerRouteRepository; logger: Logger; ruleDataService: RuleDataPluginService; - spacesService: SpacesServiceStart; }) { const routes = Object.values(repository); @@ -67,7 +64,6 @@ export function registerRoutes({ logger, params: decodedParams, ruleDataService, - spacesService, })) as any; if (data === undefined) { diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 3f04d5e0b13c6..63ef4e1e07e64 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -38,19 +38,13 @@ const createSLORoute = createObservabilityServerRoute({ tags: [], }, params: createSLOParamsSchema, - handler: async ({ context, request, params, logger, spacesService }) => { + handler: async ({ context, params, logger }) => { const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; - const spaceId = spacesService.getSpaceId(request); - const resourceInstaller = new DefaultResourceInstaller(esClient, logger, spaceId); + const resourceInstaller = new DefaultResourceInstaller(esClient, logger); const repository = new KibanaSavedObjectsSLORepository(soClient); - const transformManager = new DefaultTransformManager( - transformGenerators, - esClient, - logger, - spaceId - ); + const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); const createSLO = new CreateSLO(resourceInstaller, repository, transformManager); const response = await createSLO.execute(params.body); @@ -65,18 +59,12 @@ const deleteSLORoute = createObservabilityServerRoute({ tags: [], }, params: deleteSLOParamsSchema, - handler: async ({ context, request, params, logger, spacesService }) => { + handler: async ({ context, params, logger }) => { const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; - const spaceId = spacesService.getSpaceId(request); const repository = new KibanaSavedObjectsSLORepository(soClient); - const transformManager = new DefaultTransformManager( - transformGenerators, - esClient, - logger, - spaceId - ); + const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); const deleteSLO = new DeleteSLO(repository, transformManager, esClient); diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts index b1a68b9c04aee..2e43c81f6d382 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts @@ -33,6 +33,7 @@ describe('DeleteSLO', () => { await deleteSLO.execute(slo.id); + expect(mockRepository.findById).toHaveBeenCalledWith(slo.id); expect(mockTransformManager.stop).toHaveBeenCalledWith(getSLOTransformId(slo.id)); expect(mockTransformManager.uninstall).toHaveBeenCalledWith(getSLOTransformId(slo.id)); expect(mockEsClient.deleteByQuery).toHaveBeenCalledWith( diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.ts index 59e5df8a5975a..a7d931174a59a 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.ts @@ -19,6 +19,9 @@ export class DeleteSLO { ) {} public async execute(sloId: string): Promise { + // ensure the slo exists on the request's space. + await this.repository.findById(sloId); + const sloTransformId = getSLOTransformId(sloId); await this.transformManager.stop(sloTransformId); await this.transformManager.uninstall(sloTransformId); diff --git a/x-pack/plugins/observability/server/services/slo/resource_installer.test.ts b/x-pack/plugins/observability/server/services/slo/resource_installer.test.ts index f8746f38cd246..90749176513da 100644 --- a/x-pack/plugins/observability/server/services/slo/resource_installer.test.ts +++ b/x-pack/plugins/observability/server/services/slo/resource_installer.test.ts @@ -9,7 +9,7 @@ import { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/typesW import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { - getSLOIngestPipelineName, + SLO_INGEST_PIPELINE_NAME, SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME, SLO_COMPONENT_TEMPLATE_SETTINGS_NAME, SLO_INDEX_TEMPLATE_NAME, @@ -17,17 +17,12 @@ import { } from '../../assets/constants'; import { DefaultResourceInstaller } from './resource_installer'; -const SPACE_ID = 'space-id'; describe('resourceInstaller', () => { describe("when the common resources don't exist", () => { it('installs the common resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); mockClusterClient.indices.existsIndexTemplate.mockResponseOnce(false); - const installer = new DefaultResourceInstaller( - mockClusterClient, - loggerMock.create(), - SPACE_ID - ); + const installer = new DefaultResourceInstaller(mockClusterClient, loggerMock.create()); await installer.ensureCommonResourcesInstalled(); @@ -44,7 +39,7 @@ describe('resourceInstaller', () => { expect.objectContaining({ name: SLO_INDEX_TEMPLATE_NAME }) ); expect(mockClusterClient.ingest.putPipeline).toHaveBeenCalledWith( - expect.objectContaining({ id: getSLOIngestPipelineName(SPACE_ID) }) + expect.objectContaining({ id: SLO_INGEST_PIPELINE_NAME }) ); }); }); @@ -55,13 +50,9 @@ describe('resourceInstaller', () => { mockClusterClient.indices.existsIndexTemplate.mockResponseOnce(true); mockClusterClient.ingest.getPipeline.mockResponseOnce({ // @ts-ignore _meta not typed properly - [getSLOIngestPipelineName(SPACE_ID)]: { _meta: { version: SLO_RESOURCES_VERSION } }, + [SLO_INGEST_PIPELINE_NAME]: { _meta: { version: SLO_RESOURCES_VERSION } }, } as IngestGetPipelineResponse); - const installer = new DefaultResourceInstaller( - mockClusterClient, - loggerMock.create(), - SPACE_ID - ); + const installer = new DefaultResourceInstaller(mockClusterClient, loggerMock.create()); await installer.ensureCommonResourcesInstalled(); diff --git a/x-pack/plugins/observability/server/services/slo/resource_installer.ts b/x-pack/plugins/observability/server/services/slo/resource_installer.ts index 8ed8108b3c4b7..abc02052097b5 100644 --- a/x-pack/plugins/observability/server/services/slo/resource_installer.ts +++ b/x-pack/plugins/observability/server/services/slo/resource_installer.ts @@ -13,7 +13,7 @@ import type { import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { - getSLOIngestPipelineName, + SLO_INGEST_PIPELINE_NAME, SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME, SLO_COMPONENT_TEMPLATE_SETTINGS_NAME, SLO_INDEX_TEMPLATE_NAME, @@ -29,11 +29,7 @@ export interface ResourceInstaller { } export class DefaultResourceInstaller implements ResourceInstaller { - constructor( - private esClient: ElasticsearchClient, - private logger: Logger, - private spaceId: string - ) {} + constructor(private esClient: ElasticsearchClient, private logger: Logger) {} public async ensureCommonResourcesInstalled(): Promise { const alreadyInstalled = await this.areResourcesAlreadyInstalled(); @@ -64,8 +60,8 @@ export class DefaultResourceInstaller implements ResourceInstaller { await this.createOrUpdateIngestPipelineTemplate( getSLOPipelineTemplate( - getSLOIngestPipelineName(this.spaceId), - this.getPipelinePrefix(SLO_RESOURCES_VERSION, this.spaceId) + SLO_INGEST_PIPELINE_NAME, + this.getPipelinePrefix(SLO_RESOURCES_VERSION) ) ); } catch (err) { @@ -74,10 +70,10 @@ export class DefaultResourceInstaller implements ResourceInstaller { } } - private getPipelinePrefix(version: number, spaceId: string): string { + private getPipelinePrefix(version: number): string { // Following https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme - // slo-observability.sli--. - return `${SLO_INDEX_TEMPLATE_NAME}-v${version}-${spaceId}.`; + // slo-observability.sli-. + return `${SLO_INDEX_TEMPLATE_NAME}-v${version}.`; } private async areResourcesAlreadyInstalled(): Promise { @@ -87,12 +83,11 @@ export class DefaultResourceInstaller implements ResourceInstaller { let ingestPipelineExists = false; try { - const pipelineName = getSLOIngestPipelineName(this.spaceId); - const pipeline = await this.esClient.ingest.getPipeline({ id: pipelineName }); + const pipeline = await this.esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME }); ingestPipelineExists = // @ts-ignore _meta is not defined on the type - pipeline && pipeline[pipelineName]._meta.version === SLO_RESOURCES_VERSION; + pipeline && pipeline[SLO_INGEST_PIPELINE_NAME]._meta.version === SLO_RESOURCES_VERSION; } catch (err) { return false; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index 239c3c93503b9..7b7f49061fd57 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -20,8 +20,8 @@ Object { "version": 1, }, "dest": Object { - "index": "slo-observability.sli-v1-my-namespace", - "pipeline": "slo-observability.sli.monthly-my-namespace", + "index": "slo-observability.sli-v1", + "pipeline": "slo-observability.sli.monthly", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index 1fdaec28f8977..8b73f76a8082d 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -20,8 +20,8 @@ Object { "version": 1, }, "dest": Object { - "index": "slo-observability.sli-v1-my-namespace", - "pipeline": "slo-observability.sli.monthly-my-namespace", + "index": "slo-observability.sli-v1", + "pipeline": "slo-observability.sli.monthly", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts index 1e8fadf365d72..ba984e542619b 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts @@ -13,7 +13,7 @@ const generator = new ApmTransactionDurationTransformGenerator(); describe('APM Transaction Duration Transform Generator', () => { it('returns the correct transform params with every specified indicator params', async () => { const anSLO = createSLO(createAPMTransactionDurationIndicator()); - const transform = generator.getTransformParams(anSLO, 'my-namespace'); + const transform = generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot({ transform_id: expect.any(String), @@ -34,7 +34,7 @@ describe('APM Transaction Duration Transform Generator', () => { transaction_type: '*', }) ); - const transform = generator.getTransformParams(anSLO, 'my-namespace'); + const transform = generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts index 4c46421737630..bc45e12abbb30 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts @@ -12,8 +12,8 @@ import { } from '@elastic/elasticsearch/lib/api/types'; import { ALL_VALUE } from '../../../types/schema'; import { - getSLODestinationIndexName, - getSLOIngestPipelineName, + SLO_DESTINATION_INDEX_NAME, + SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; @@ -27,7 +27,7 @@ import { TransformGenerator } from '.'; const APM_SOURCE_INDEX = 'metrics-apm*'; export class ApmTransactionDurationTransformGenerator implements TransformGenerator { - public getTransformParams(slo: SLO, spaceId: string): TransformPutTransformRequest { + public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionDurationSLOSchema.is(slo)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -35,7 +35,7 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera return getSLOTransformTemplate( this.buildTransformId(slo), this.buildSource(slo), - this.buildDestination(slo, spaceId), + this.buildDestination(), this.buildGroupBy(), this.buildAggregations(slo) ); @@ -104,10 +104,10 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera }; } - private buildDestination(slo: APMTransactionDurationSLO, spaceId: string) { + private buildDestination() { return { - pipeline: getSLOIngestPipelineName(spaceId), - index: getSLODestinationIndexName(spaceId), + pipeline: SLO_INGEST_PIPELINE_NAME, + index: SLO_DESTINATION_INDEX_NAME, }; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts index afa904fa1f8cb..2bc88c576f8c4 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts @@ -13,7 +13,7 @@ const generator = new ApmTransactionErrorRateTransformGenerator(); describe('APM Transaction Error Rate Transform Generator', () => { it('returns the correct transform params with every specified indicator params', async () => { const anSLO = createSLO(createAPMTransactionErrorRateIndicator()); - const transform = generator.getTransformParams(anSLO, 'my-namespace'); + const transform = generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot({ transform_id: expect.any(String), @@ -27,7 +27,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { it("uses default values when 'good_status_codes' is not specified", async () => { const anSLO = createSLO(createAPMTransactionErrorRateIndicator({ good_status_codes: [] })); - const transform = generator.getTransformParams(anSLO, 'my-namespace'); + const transform = generator.getTransformParams(anSLO); expect(transform.pivot?.aggregations).toMatchSnapshot(); }); @@ -41,7 +41,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { transaction_type: '*', }) ); - const transform = generator.getTransformParams(anSLO, 'my-namespace'); + const transform = generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts index 6740bee2b707f..23a9a03f6e14c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts @@ -14,8 +14,8 @@ import { ALL_VALUE } from '../../../types/schema'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; import { TransformGenerator } from '.'; import { - getSLODestinationIndexName, - getSLOIngestPipelineName, + SLO_DESTINATION_INDEX_NAME, + SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; import { @@ -29,7 +29,7 @@ const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx']; const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx']; export class ApmTransactionErrorRateTransformGenerator implements TransformGenerator { - public getTransformParams(slo: SLO, spaceId: string): TransformPutTransformRequest { + public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionErrorRateSLOSchema.is(slo)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -37,7 +37,7 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener return getSLOTransformTemplate( this.buildTransformId(slo), this.buildSource(slo), - this.buildDestination(slo, spaceId), + this.buildDestination(), this.buildGroupBy(), this.buildAggregations(slo) ); @@ -106,10 +106,10 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener }; } - private buildDestination(slo: APMTransactionErrorRateSLO, spaceId: string) { + private buildDestination() { return { - pipeline: getSLOIngestPipelineName(spaceId), - index: getSLODestinationIndexName(spaceId), + pipeline: SLO_INGEST_PIPELINE_NAME, + index: SLO_DESTINATION_INDEX_NAME, }; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts index 21a917ea1af6d..3965e809373c8 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts @@ -9,5 +9,5 @@ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typ import { SLO } from '../../../types/models'; export interface TransformGenerator { - getTransformParams(slo: SLO, spaceId: string): TransformPutTransformRequest; + getTransformParams(slo: SLO): TransformPutTransformRequest; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts index 1badb6b08e49f..434e6841ff0e9 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts @@ -23,8 +23,6 @@ import { import { SLO, SLITypes } from '../../types/models'; import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; -const SPACE_ID = 'space-id'; - describe('TransformManager', () => { let esClientMock: ElasticsearchClientMock; let loggerMock: jest.Mocked; @@ -41,7 +39,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_duration': new DummyTransformGenerator(), }; - const service = new DefaultTransformManager(generators, esClientMock, loggerMock, SPACE_ID); + const service = new DefaultTransformManager(generators, esClientMock, loggerMock); await expect( service.install( @@ -63,12 +61,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_duration': new FailTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); await expect( transformManager.install( @@ -92,12 +85,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); const slo = createSLO(createAPMTransactionErrorRateIndicator()); const transformId = await transformManager.install(slo); @@ -113,12 +101,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); await transformManager.start('slo-transform-id'); @@ -132,12 +115,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); await transformManager.stop('slo-transform-id'); @@ -151,12 +129,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); await transformManager.uninstall('slo-transform-id'); @@ -171,12 +144,7 @@ describe('TransformManager', () => { const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; - const transformManager = new DefaultTransformManager( - generators, - esClientMock, - loggerMock, - SPACE_ID - ); + const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); await transformManager.uninstall('slo-transform-id'); diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.ts index ab7799a4a00c6..154660fccaf9f 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.ts @@ -24,8 +24,7 @@ export class DefaultTransformManager implements TransformManager { constructor( private generators: Record, private esClient: ElasticsearchClient, - private logger: Logger, - private spaceId: string + private logger: Logger ) {} async install(slo: SLO): Promise { @@ -35,7 +34,7 @@ export class DefaultTransformManager implements TransformManager { throw new Error(`Unsupported SLO type: ${slo.indicator.type}`); } - const transformParams = generator.getTransformParams(slo, this.spaceId); + const transformParams = generator.getTransformParams(slo); try { await retryTransientEsErrors(() => this.esClient.transform.putTransform(transformParams), { logger: this.logger, From 2d6a45b5ef95502e503f17600076680fac5693a0 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Wed, 28 Sep 2022 08:50:25 -0400 Subject: [PATCH 121/172] [RAM] Add Stats on top of execution logs (#140883) * WIP * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * add new route for global execution KPI + add filter * revert changes * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * update query kpi * add new route for global KPI * Add KPI to log tab and add filtering * Add types * Fix calling API multiple times when refresh, add unit tests * Fix lint and type checks * clean up * remove sorting * add tests * fix global kpi log * fix 141833 * fix query to match with filter * allow access to getRuleExecutionKPI * fix unit test * fix jest tests Co-authored-by: Jiawei Wu Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../alerting/common/execution_log_types.ts | 13 + .../authorization/alerting_authorization.ts | 1 + .../lib/get_execution_log_aggregation.test.ts | 583 ++++++++++++++++++ .../lib/get_execution_log_aggregation.ts | 199 +++++- .../routes/get_global_execution_kpi.test.ts | 69 +++ .../server/routes/get_global_execution_kpi.ts | 50 ++ .../routes/get_rule_execution_kpi.test.ts | 102 +++ .../server/routes/get_rule_execution_kpi.ts | 56 ++ .../plugins/alerting/server/routes/index.ts | 4 + .../alerting/server/rules_client.mock.ts | 2 + .../server/rules_client/audit_events.ts | 14 + .../server/rules_client/rules_client.ts | 134 ++++ .../server/task_runner/task_runner.ts | 1 - .../alerting.test.ts | 8 + .../feature_privilege_builder/alerting.ts | 9 +- .../application/lib/rule_api/get_filter.ts | 34 + .../public/application/lib/rule_api/index.ts | 4 + .../rule_api/load_action_error_log.test.ts | 2 +- .../lib/rule_api/load_action_error_log.ts | 17 +- .../load_execution_kpi_aggregations.ts | 41 ++ .../load_execution_log_aggregations.ts | 17 +- .../load_global_execution_kpi_aggregations.ts | 38 ++ .../with_bulk_rule_api_operations.tsx | 32 +- .../rule_details/components/rule.test.tsx | 4 +- .../sections/rule_details/components/rule.tsx | 16 +- .../rule_event_log_list_kpi.test.tsx | 183 ++++++ .../components/rule_event_log_list_kpi.tsx | 251 ++++++++ .../components/rule_event_log_list_table.tsx | 19 +- .../alerting/get_global_execution_kpi.ts | 162 +++++ .../tests/alerting/get_rule_execution_kpi.ts | 133 ++++ 30 files changed, 2143 insertions(+), 55 deletions(-) create mode 100644 x-pack/plugins/alerting/server/routes/get_global_execution_kpi.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_global_execution_kpi.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_kpi_aggregations.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_global_execution_kpi_aggregations.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/get_global_execution_kpi.ts create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/get_rule_execution_kpi.ts diff --git a/x-pack/plugins/alerting/common/execution_log_types.ts b/x-pack/plugins/alerting/common/execution_log_types.ts index 1938d8be4acd3..b08a0bf76d69e 100644 --- a/x-pack/plugins/alerting/common/execution_log_types.ts +++ b/x-pack/plugins/alerting/common/execution_log_types.ts @@ -26,6 +26,17 @@ export const actionErrorLogSortableColumns = [ 'event.action', ]; +export const EMPTY_EXECUTION_KPI_RESULT = { + success: 0, + unknown: 0, + failure: 0, + activeAlerts: 0, + newAlerts: 0, + recoveredAlerts: 0, + erroredActions: 0, + triggeredActions: 0, +}; + export type ExecutionLogSortFields = typeof executionLogSortableColumns[number]; export type ActionErrorLogSortFields = typeof actionErrorLogSortableColumns[number]; @@ -68,3 +79,5 @@ export interface IExecutionLogResult { total: number; data: IExecutionLog[]; } + +export type IExecutionKPIResult = typeof EMPTY_EXECUTION_KPI_RESULT; diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 0c501f25a857d..7138aabe9d263 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -35,6 +35,7 @@ export enum ReadOperations { Find = 'find', GetAuthorizedAlertsIndices = 'getAuthorizedAlertsIndices', RunSoon = 'runSoon', + GetRuleExecutionKPI = 'getRuleExecutionKPI', } export enum WriteOperations { diff --git a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts index e48483785490a..eb46ae67e2ef1 100644 --- a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts @@ -13,6 +13,8 @@ import { formatSortForBucketSort, formatSortForTermSort, ExecutionUuidAggResult, + getExecutionKPIAggregation, + formatExecutionKPIResult, } from './get_execution_log_aggregation'; describe('formatSortForBucketSort', () => { @@ -1689,3 +1691,584 @@ describe('formatExecutionLogResult', () => { }); }); }); + +describe('getExecutionKPIAggregation', () => { + test('should correctly generate aggregation', () => { + expect(getExecutionKPIAggregation()).toEqual({ + excludeExecuteStart: { + filter: { + bool: { + must_not: [ + { + term: { + 'event.action': 'execute-start', + }, + }, + ], + }, + }, + aggs: { + executionUuid: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 1000, + }, + aggs: { + executionUuidSorted: { + bucket_sort: { + from: 0, + size: 1000, + gap_policy: 'insert_zeros', + }, + }, + actionExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'actions', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + actionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + ruleExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + numTriggeredActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', + missing: 0, + }, + }, + numGeneratedActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_generated_actions', + missing: 0, + }, + }, + numActiveAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.active', + missing: 0, + }, + }, + numRecoveredAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.recovered', + missing: 0, + }, + }, + numNewAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.new', + missing: 0, + }, + }, + ruleExecutionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + minExecutionUuidBucket: { + bucket_selector: { + buckets_path: { + count: 'ruleExecution._count', + }, + script: { + source: 'params.count > 0', + }, + }, + }, + }, + }, + }, + }, + }); + }); + + test('should correctly generate aggregation with a defined filter in the form of a string', () => { + expect(getExecutionKPIAggregation('test:test')).toEqual({ + excludeExecuteStart: { + filter: { + bool: { + must_not: [ + { + term: { + 'event.action': 'execute-start', + }, + }, + ], + }, + }, + aggs: { + executionUuid: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 1000, + }, + aggs: { + executionUuidSorted: { + bucket_sort: { + from: 0, + size: 1000, + gap_policy: 'insert_zeros', + }, + }, + actionExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'actions', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + actionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + ruleExecution: { + filter: { + bool: { + filter: { + bool: { + should: [ + { + match: { + test: 'test', + }, + }, + ], + minimum_should_match: 1, + }, + }, + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + numTriggeredActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', + missing: 0, + }, + }, + numGeneratedActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_generated_actions', + missing: 0, + }, + }, + numActiveAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.active', + missing: 0, + }, + }, + numRecoveredAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.recovered', + missing: 0, + }, + }, + numNewAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.new', + missing: 0, + }, + }, + ruleExecutionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + minExecutionUuidBucket: { + bucket_selector: { + buckets_path: { + count: 'ruleExecution._count', + }, + script: { + source: 'params.count > 0', + }, + }, + }, + }, + }, + }, + }, + }); + }); + + test('should correctly generate aggregation with a defined filter in the form of a KueryNode', () => { + expect(getExecutionKPIAggregation(fromKueryExpression('test:test'))).toEqual({ + excludeExecuteStart: { + filter: { + bool: { + must_not: [ + { + term: { + 'event.action': 'execute-start', + }, + }, + ], + }, + }, + aggs: { + executionUuid: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 1000, + }, + aggs: { + executionUuidSorted: { + bucket_sort: { + from: 0, + size: 1000, + gap_policy: 'insert_zeros', + }, + }, + actionExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'actions', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + actionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + ruleExecution: { + filter: { + bool: { + filter: { + bool: { + should: [ + { + match: { + test: 'test', + }, + }, + ], + minimum_should_match: 1, + }, + }, + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + numTriggeredActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', + missing: 0, + }, + }, + numGeneratedActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_generated_actions', + missing: 0, + }, + }, + numActiveAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.active', + missing: 0, + }, + }, + numRecoveredAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.recovered', + missing: 0, + }, + }, + numNewAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.new', + missing: 0, + }, + }, + ruleExecutionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + minExecutionUuidBucket: { + bucket_selector: { + buckets_path: { + count: 'ruleExecution._count', + }, + script: { + source: 'params.count > 0', + }, + }, + }, + }, + }, + }, + }, + }); + }); +}); + +describe('formatExecutionKPIAggBuckets', () => { + test('should return empty results if aggregations are undefined', () => { + expect( + formatExecutionKPIResult({ + aggregations: undefined, + }) + ).toEqual({ + activeAlerts: 0, + erroredActions: 0, + failure: 0, + newAlerts: 0, + recoveredAlerts: 0, + success: 0, + triggeredActions: 0, + unknown: 0, + }); + }); + + test('should format results correctly', () => { + const results = { + aggregations: { + excludeExecuteStart: { + meta: {}, + doc_count: 875, + executionUuid: { + meta: {}, + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + ruleExecution: { + meta: {}, + doc_count: 3, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 0.0, + }, + ruleExecutionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'success', + doc_count: 3, + }, + ], + }, + }, + actionExecution: { + meta: {}, + doc_count: 5, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'success', + doc_count: 5, + }, + ], + }, + }, + }, + { + ruleExecution: { + meta: {}, + doc_count: 2, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 0.0, + }, + ruleExecutionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'success', + doc_count: 2, + }, + ], + }, + }, + actionExecution: { + meta: {}, + doc_count: 3, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'failure', + doc_count: 3, + }, + ], + }, + }, + }, + ], + }, + }, + }, + }; + + expect(formatExecutionKPIResult(results)).toEqual({ + success: 5, + unknown: 0, + failure: 0, + activeAlerts: 10, + newAlerts: 10, + recoveredAlerts: 0, + erroredActions: 3, + triggeredActions: 10, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts index 0854488d5f29e..fedc827a46c73 100644 --- a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts +++ b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts @@ -12,7 +12,7 @@ import { flatMap, get } from 'lodash'; import { AggregateEventsBySavedObjectResult } from '@kbn/event-log-plugin/server'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { parseDuration } from '.'; -import { IExecutionLog, IExecutionLogResult } from '../../common'; +import { IExecutionLog, IExecutionLogResult, EMPTY_EXECUTION_KPI_RESULT } from '../../common'; const DEFAULT_MAX_BUCKETS_LIMIT = 1000; // do not retrieve more than this number of executions @@ -51,6 +51,21 @@ interface IActionExecution buckets: Array<{ key: string; doc_count: number }>; } +interface IExecutionUuidKpiAggBucket extends estypes.AggregationsStringTermsBucketKeys { + actionExecution: { + doc_count: number; + actionOutcomes: IActionExecution; + }; + ruleExecution: { + doc_count: number; + numTriggeredActions: estypes.AggregationsSumAggregate; + numGeneratedActions: estypes.AggregationsSumAggregate; + numActiveAlerts: estypes.AggregationsSumAggregate; + numRecoveredAlerts: estypes.AggregationsSumAggregate; + numNewAlerts: estypes.AggregationsSumAggregate; + ruleExecutionOutcomes: IActionExecution; + }; +} interface IExecutionUuidAggBucket extends estypes.AggregationsStringTermsBucketKeys { timeoutMessage: estypes.AggregationsMultiBucketBase; ruleExecution: { @@ -76,12 +91,22 @@ export interface ExecutionUuidAggResult buckets: TBucket[]; } +export interface ExecutionUuidKPIAggResult + extends estypes.AggregationsAggregateBase { + buckets: TBucket[]; +} + interface ExcludeExecuteStartAggResult extends estypes.AggregationsAggregateBase { executionUuid: ExecutionUuidAggResult; executionUuidCardinality: { executionUuidCardinality: estypes.AggregationsCardinalityAggregate; }; } + +interface ExcludeExecuteStartKpiAggResult extends estypes.AggregationsAggregateBase { + executionUuid: ExecutionUuidKPIAggResult; +} + export interface IExecutionLogAggOptions { filter?: string | KueryNode; page: number; @@ -102,6 +127,115 @@ const ExecutionLogSortFields: Record = { num_new_alerts: 'ruleExecution>numNewAlerts', }; +export const getExecutionKPIAggregation = (filter?: IExecutionLogAggOptions['filter']) => { + const dslFilterQuery: estypes.QueryDslBoolQuery['filter'] = buildDslFilterQuery(filter); + + return { + excludeExecuteStart: { + filter: { + bool: { + must_not: [ + { + term: { + 'event.action': 'execute-start', + }, + }, + ], + }, + }, + aggs: { + executionUuid: { + // Bucket by execution UUID + terms: { + field: EXECUTION_UUID_FIELD, + size: DEFAULT_MAX_BUCKETS_LIMIT, + }, + aggs: { + executionUuidSorted: { + bucket_sort: { + from: 0, + size: 1000, + gap_policy: 'insert_zeros' as estypes.AggregationsGapPolicy, + }, + }, + actionExecution: { + filter: { + bool: { + must: [getProviderAndActionFilter('actions', 'execute')], + }, + }, + aggs: { + actionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + ruleExecution: { + filter: { + bool: { + ...(dslFilterQuery ? { filter: dslFilterQuery } : {}), + must: [getProviderAndActionFilter('alerting', 'execute')], + }, + }, + aggs: { + numTriggeredActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', + missing: 0, + }, + }, + numGeneratedActions: { + sum: { + field: 'kibana.alert.rule.execution.metrics.number_of_generated_actions', + missing: 0, + }, + }, + numActiveAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.active', + missing: 0, + }, + }, + numRecoveredAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.recovered', + missing: 0, + }, + }, + numNewAlerts: { + sum: { + field: 'kibana.alert.rule.execution.metrics.alert_counts.new', + missing: 0, + }, + }, + ruleExecutionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + minExecutionUuidBucket: { + bucket_selector: { + buckets_path: { + count: 'ruleExecution._count', + }, + script: { + source: 'params.count > 0', + }, + }, + }, + }, + }, + }, + }, + }; +}; + export function getExecutionLogAggregation({ filter, page, @@ -130,13 +264,7 @@ export function getExecutionLogAggregation({ throw Boom.badRequest(`Invalid perPage field "${perPage}" - must be greater than 0`); } - let dslFilterQuery: estypes.QueryDslBoolQuery['filter']; - try { - const filterKueryNode = typeof filter === 'string' ? fromKueryExpression(filter) : filter; - dslFilterQuery = filter ? toElasticsearchQuery(filterKueryNode) : undefined; - } catch (err) { - throw Boom.badRequest(`Invalid kuery syntax for filter ${filter}`); - } + const dslFilterQuery: estypes.QueryDslBoolQuery['filter'] = buildDslFilterQuery(filter); return { excludeExecuteStart: { @@ -295,6 +423,15 @@ export function getExecutionLogAggregation({ }; } +function buildDslFilterQuery(filter: IExecutionLogAggOptions['filter']) { + try { + const filterKueryNode = typeof filter === 'string' ? fromKueryExpression(filter) : filter; + return filter ? toElasticsearchQuery(filterKueryNode) : undefined; + } catch (err) { + throw Boom.badRequest(`Invalid kuery syntax for filter ${filter}`); + } +} + function getProviderAndActionFilter(provider: string, action: string) { return { bool: { @@ -362,6 +499,52 @@ function formatExecutionLogAggBucket(bucket: IExecutionUuidAggBucket): IExecutio }; } +function formatExecutionKPIAggBuckets(buckets: IExecutionUuidKpiAggBucket[]) { + const objToReturn = { + success: 0, + unknown: 0, + failure: 0, + activeAlerts: 0, + newAlerts: 0, + recoveredAlerts: 0, + erroredActions: 0, + triggeredActions: 0, + }; + + buckets.forEach((bucket) => { + const ruleExecutionOutcomes = bucket?.ruleExecution?.ruleExecutionOutcomes?.buckets ?? []; + const actionExecutionOutcomes = bucket?.actionExecution?.actionOutcomes?.buckets ?? []; + + const ruleExecutionCount = bucket?.ruleExecution?.doc_count ?? 0; + const successRuleExecution = + ruleExecutionOutcomes.find((subBucket) => subBucket?.key === 'success')?.doc_count ?? 0; + const failureRuleExecution = + ruleExecutionOutcomes.find((subBucket) => subBucket?.key === 'failure')?.doc_count ?? 0; + + objToReturn.success += successRuleExecution; + objToReturn.unknown += ruleExecutionCount - (successRuleExecution + failureRuleExecution); + objToReturn.failure += failureRuleExecution; + objToReturn.activeAlerts += bucket?.ruleExecution?.numActiveAlerts.value ?? 0; + objToReturn.newAlerts += bucket?.ruleExecution?.numNewAlerts.value ?? 0; + objToReturn.recoveredAlerts += bucket?.ruleExecution?.numRecoveredAlerts.value ?? 0; + objToReturn.erroredActions += + actionExecutionOutcomes.find((subBucket) => subBucket?.key === 'failure')?.doc_count ?? 0; + objToReturn.triggeredActions += bucket?.ruleExecution?.numTriggeredActions.value ?? 0; + }); + + return objToReturn; +} + +export function formatExecutionKPIResult(results: AggregateEventsBySavedObjectResult) { + const { aggregations } = results; + if (!aggregations || !aggregations.excludeExecuteStart) { + return EMPTY_EXECUTION_KPI_RESULT; + } + const aggs = aggregations.excludeExecuteStart as ExcludeExecuteStartKpiAggResult; + const buckets = aggs.executionUuid.buckets; + return formatExecutionKPIAggBuckets(buckets); +} + export function formatExecutionLogResult( results: AggregateEventsBySavedObjectResult ): IExecutionLogResult { diff --git a/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.test.ts b/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.test.ts new file mode 100644 index 0000000000000..89b21547c892a --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.test.ts @@ -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 { httpServiceMock } from '@kbn/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { rulesClientMock } from '../rules_client.mock'; +import { getGlobalExecutionKPIRoute } from './get_global_execution_kpi'; + +const rulesClient = rulesClientMock.create(); +jest.mock('../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getGlobalExecutionKPIRoute', () => { + const dateString = new Date().toISOString(); + const mockedExecutionLog = { + success: 3, + unknown: 0, + failure: 0, + activeAlerts: 5, + newAlerts: 5, + recoveredAlerts: 0, + erroredActions: 0, + triggeredActions: 5, + }; + + it('gets global execution KPI', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getGlobalExecutionKPIRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/_global_execution_kpi"`); + + rulesClient.getGlobalExecutionKpiWithAuth.mockResolvedValue(mockedExecutionLog); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + query: { + date_start: dateString, + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(rulesClient.getGlobalExecutionKpiWithAuth).toHaveBeenCalledTimes(1); + expect(rulesClient.getGlobalExecutionKpiWithAuth.mock.calls[0]).toEqual([ + { + dateStart: dateString, + }, + ]); + + expect(res.ok).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.ts b/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.ts new file mode 100644 index 0000000000000..29937cc3d8c98 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_global_execution_kpi.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; +import { RewriteRequestCase, verifyAccessAndContext } from './lib'; +import { GetGlobalExecutionKPIParams } from '../rules_client'; +import { ILicenseState } from '../lib'; + +const querySchema = schema.object({ + date_start: schema.string(), + date_end: schema.maybe(schema.string()), + filter: schema.maybe(schema.string()), +}); + +const rewriteReq: RewriteRequestCase = ({ + date_start: dateStart, + date_end: dateEnd, + ...rest +}) => ({ + ...rest, + dateStart, + dateEnd, +}); + +export const getGlobalExecutionKPIRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/_global_execution_kpi`, + validate: { + query: querySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + return res.ok({ + body: await rulesClient.getGlobalExecutionKpiWithAuth(rewriteReq(req.query)), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts new file mode 100644 index 0000000000000..db5033404788c --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts @@ -0,0 +1,102 @@ +/* + * 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 { httpServiceMock } from '@kbn/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { rulesClientMock } from '../rules_client.mock'; +import { getRuleExecutionKPIRoute } from './get_rule_execution_kpi'; + +const rulesClient = rulesClientMock.create(); +jest.mock('../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getRuleExecutionKPIRoute', () => { + const dateString = new Date().toISOString(); + const mockedExecutionLog = { + success: 3, + unknown: 0, + failure: 0, + activeAlerts: 5, + newAlerts: 5, + recoveredAlerts: 0, + erroredActions: 0, + triggeredActions: 5, + }; + + it('gets rule execution KPI', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleExecutionKPIRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_execution_kpi"`); + + rulesClient.getRuleExecutionKPI.mockResolvedValue(mockedExecutionLog); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { + id: '1', + }, + query: { + date_start: dateString, + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(rulesClient.getRuleExecutionKPI).toHaveBeenCalledTimes(1); + expect(rulesClient.getRuleExecutionKPI.mock.calls[0]).toEqual([ + { + dateStart: dateString, + id: '1', + }, + ]); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('returns NOT-FOUND when rule is not found', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleExecutionKPIRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + rulesClient.getRuleExecutionKPI = jest + .fn() + .mockRejectedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1')); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { + id: '1', + }, + query: {}, + }, + ['notFound'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( + `[Error: Saved object [alert/1] not found]` + ); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.ts b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.ts new file mode 100644 index 0000000000000..11f7085c53290 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; +import { RewriteRequestCase, verifyAccessAndContext } from './lib'; +import { GetRuleExecutionKPIParams } from '../rules_client'; +import { ILicenseState } from '../lib'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const querySchema = schema.object({ + date_start: schema.string(), + date_end: schema.maybe(schema.string()), + filter: schema.maybe(schema.string()), +}); + +const rewriteReq: RewriteRequestCase = ({ + date_start: dateStart, + date_end: dateEnd, + ...rest +}) => ({ + ...rest, + dateStart, + dateEnd, +}); + +export const getRuleExecutionKPIRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_execution_kpi`, + validate: { + params: paramSchema, + query: querySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + const { id } = req.params; + return res.ok({ + body: await rulesClient.getRuleExecutionKPI(rewriteReq({ id, ...req.query })), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index dca2e214d0786..de9e2f112d9e9 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -22,7 +22,9 @@ import { findRulesRoute, findInternalRulesRoute } from './find_rules'; import { getRuleAlertSummaryRoute } from './get_rule_alert_summary'; import { getRuleExecutionLogRoute } from './get_rule_execution_log'; import { getGlobalExecutionLogRoute } from './get_global_execution_logs'; +import { getGlobalExecutionKPIRoute } from './get_global_execution_kpi'; import { getActionErrorLogRoute } from './get_action_error_log'; +import { getRuleExecutionKPIRoute } from './get_rule_execution_kpi'; import { getRuleStateRoute } from './get_rule_state'; import { healthRoute } from './health'; import { resolveRuleRoute } from './resolve_rule'; @@ -63,6 +65,8 @@ export function defineRoutes(opts: RouteOptions) { getRuleExecutionLogRoute(router, licenseState); getGlobalExecutionLogRoute(router, licenseState); getActionErrorLogRoute(router, licenseState); + getRuleExecutionKPIRoute(router, licenseState); + getGlobalExecutionKPIRoute(router, licenseState); getRuleStateRoute(router, licenseState); healthRoute(router, licenseState, encryptedSavedObjects); ruleTypesRoute(router, licenseState); diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 7333ad59bbb71..2092b98e48c0d 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -30,6 +30,8 @@ const createRulesClientMock = () => { listAlertTypes: jest.fn(), getAlertSummary: jest.fn(), getExecutionLogForRule: jest.fn(), + getRuleExecutionKPI: jest.fn(), + getGlobalExecutionKpiWithAuth: jest.fn(), getGlobalExecutionLogWithAuth: jest.fn(), getActionErrorLog: jest.fn(), getSpaceId: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/audit_events.ts b/x-pack/plugins/alerting/server/rules_client/audit_events.ts index c29bc99aa6ed7..30b759c895c46 100644 --- a/x-pack/plugins/alerting/server/rules_client/audit_events.ts +++ b/x-pack/plugins/alerting/server/rules_client/audit_events.ts @@ -26,7 +26,9 @@ export enum RuleAuditAction { BULK_EDIT = 'rule_bulk_edit', GET_EXECUTION_LOG = 'rule_get_execution_log', GET_GLOBAL_EXECUTION_LOG = 'rule_get_global_execution_log', + GET_GLOBAL_EXECUTION_KPI = 'rule_get_global_execution_kpi', GET_ACTION_ERROR_LOG = 'rule_get_action_error_log', + GET_RULE_EXECUTION_KPI = 'rule_get_execution_kpi', SNOOZE = 'rule_snooze', UNSNOOZE = 'rule_unsnooze', RUN_SOON = 'rule_run_soon', @@ -68,6 +70,16 @@ const eventVerbs: Record = { rule_snooze: ['snooze', 'snoozing', 'snoozed'], rule_unsnooze: ['unsnooze', 'unsnoozing', 'unsnoozed'], rule_run_soon: ['run', 'running', 'ran'], + rule_get_execution_kpi: [ + 'access execution KPI for', + 'accessing execution KPI for', + 'accessed execution KPI for', + ], + rule_get_global_execution_kpi: [ + 'access global execution KPI for', + 'accessing global execution KPI for', + 'accessed global execution KPI for', + ], }; const eventTypes: Record = { @@ -92,6 +104,8 @@ const eventTypes: Record = { rule_snooze: 'change', rule_unsnooze: 'change', rule_run_soon: 'access', + rule_get_execution_kpi: 'access', + rule_get_global_execution_kpi: 'access', }; export interface RuleAuditEventParams { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index c2a643ce8c296..f865a0472465a 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -118,7 +118,9 @@ import { import { AlertingRulesConfig } from '../config'; import { formatExecutionLogResult, + formatExecutionKPIResult, getExecutionLogAggregation, + getExecutionKPIAggregation, } from '../lib/get_execution_log_aggregation'; import { IExecutionLogResult, IExecutionErrorsResult } from '../../common'; import { validateSnoozeStartDate } from '../lib/validate_snooze_date'; @@ -381,6 +383,19 @@ export interface GetExecutionLogByIdParams { sort: estypes.Sort; } +export interface GetRuleExecutionKPIParams { + id: string; + dateStart: string; + dateEnd?: string; + filter?: string; +} + +export interface GetGlobalExecutionKPIParams { + dateStart: string; + dateEnd?: string; + filter?: string; +} + export interface GetGlobalExecutionLogParams { dateStart: string; dateEnd?: string; @@ -1039,6 +1054,125 @@ export class RulesClient { } } + public async getGlobalExecutionKpiWithAuth({ + dateStart, + dateEnd, + filter, + }: GetGlobalExecutionKPIParams) { + this.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`); + + let authorizationTuple; + try { + authorizationTuple = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'kibana.alert.rule.rule_type_id', + consumer: 'kibana.alert.rule.consumer', + }, + } + ); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, + error, + }) + ); + throw error; + } + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, + }) + ); + + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await this.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsWithAuthFilter( + 'alert', + authorizationTuple.filter as KueryNode, + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionKPIAggregation(filter), + } + ); + + return formatExecutionKPIResult(aggResult); + } catch (err) { + this.logger.debug( + `rulesClient.getGlobalExecutionKpiWithAuth(): error searching global execution KPI: ${err.message}` + ); + throw err; + } + } + + public async getRuleExecutionKPI({ id, dateStart, dateEnd, filter }: GetRuleExecutionKPIParams) { + this.logger.debug(`getRuleExecutionKPI(): getting execution KPI for rule ${id}`); + const rule = (await this.get({ id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; + + try { + // Make sure user has access to this rule + await this.authorization.ensureAuthorized({ + ruleTypeId: rule.alertTypeId, + consumer: rule.consumer, + operation: ReadOperations.GetRuleExecutionKPI, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_RULE_EXECUTION_KPI, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_RULE_EXECUTION_KPI, + savedObject: { type: 'alert', id }, + }) + ); + + // default duration of instance summary is 60 * rule interval + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await this.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsBySavedObjectIds( + 'alert', + [id], + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionKPIAggregation(filter), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ); + + return formatExecutionKPIResult(aggResult); + } catch (err) { + this.logger.debug( + `rulesClient.getRuleExecutionKPI(): error searching execution KPI for rule ${id}: ${err.message}` + ); + throw err; + } + } + public async find({ options: { fields, ...options } = {}, excludeFromPublicApi = false, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 457b2872faa62..41029af567524 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -420,7 +420,6 @@ export class TaskRunner< checkHasReachedAlertLimit(); this.alertingEventLogger.setExecutionSucceeded(`rule executed: ${ruleLabel}`); - ruleRunMetricsStore.setSearchMetrics([ wrappedScopedClusterClient.getMetrics(), wrappedSearchSourceClient.getMetrics(), diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index 8272c9220e103..cd18a28e0d373 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -90,6 +90,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", ] `); }); @@ -174,6 +175,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices", @@ -218,6 +220,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", @@ -316,6 +319,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", @@ -374,6 +378,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", @@ -392,6 +397,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleExecutionKPI", ] `); }); @@ -480,6 +486,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", @@ -498,6 +505,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getAlertSummary", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getExecutionLog", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleExecutionKPI", "alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/get", "alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/find", "alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/getAuthorizedAlertsIndices", diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts index 542dfd1267d4c..a11a4fa77bcdd 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts @@ -17,7 +17,14 @@ enum AlertingEntity { } const readOperations: Record = { - rule: ['get', 'getRuleState', 'getAlertSummary', 'getExecutionLog', 'find'], + rule: [ + 'get', + 'getRuleState', + 'getAlertSummary', + 'getExecutionLog', + 'find', + 'getRuleExecutionKPI', + ], alert: ['get', 'find', 'getAuthorizedAlertsIndices'], }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts new file mode 100644 index 0000000000000..65ace4fa72c7b --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// TODO (Jiawei): Use node builder instead of strings +export const getFilter = ({ + message, + outcomeFilter, + runId, +}: { + message?: string; + outcomeFilter?: string[]; + runId?: string; +}) => { + const filter: string[] = []; + + if (message) { + const escapedMessage = message.replace(/([\)\(\<\>\}\{\"\:\\])/gm, '\\$&'); + filter.push(`message: "${escapedMessage}" OR error.message: "${escapedMessage}"`); + } + + if (outcomeFilter && outcomeFilter.length) { + filter.push(`event.outcome: ${outcomeFilter.join(' or ')}`); + } + + if (runId) { + filter.push(`kibana.alert.rule.execution.uuid: ${runId}`); + } + + return filter; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts index e841506595c04..e23fe787e87c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts @@ -28,6 +28,10 @@ export { loadExecutionLogAggregations, loadGlobalExecutionLogAggregations, } from './load_execution_log_aggregations'; +export type { LoadExecutionKPIAggregationsProps } from './load_execution_kpi_aggregations'; +export { loadExecutionKPIAggregations } from './load_execution_kpi_aggregations'; +export type { LoadGlobalExecutionKPIAggregationsProps } from './load_global_execution_kpi_aggregations'; +export { loadGlobalExecutionKPIAggregations } from './load_global_execution_kpi_aggregations'; export type { LoadActionErrorLogProps } from './load_action_error_log'; export { loadActionErrorLog } from './load_action_error_log'; export { unmuteAlertInstance } from './unmute_alert'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts index b4384713336f7..d06447be31fbc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts @@ -117,7 +117,7 @@ describe('loadActionErrorLog', () => { "query": Object { "date_end": "2022-03-23T16:17:53.482Z", "date_start": "2022-03-23T16:17:53.482Z", - "filter": "kibana.alert.rule.execution.uuid: 123 and message: \\"test\\" OR error.message: \\"test\\"", + "filter": "message: \\"test\\" OR error.message: \\"test\\" and kibana.alert.rule.execution.uuid: 123", "page": 1, "per_page": 10, "sort": "[{\\"@timestamp\\":{\\"order\\":\\"asc\\"}}]", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.ts index 4ec1a6949bfe5..10f2879085cd0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.ts @@ -9,6 +9,7 @@ import { HttpSetup } from '@kbn/core/public'; import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IExecutionErrorsResult, ActionErrorLogSortFields } from '@kbn/alerting-plugin/common'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { getFilter } from './get_filter'; export type SortField = Record< ActionErrorLogSortFields, @@ -49,22 +50,6 @@ const getRenamedSort = (sort?: SortField[]) => { }); }; -// TODO (Jiawei): Use node builder instead of strings -const getFilter = ({ runId, message }: { runId?: string; message?: string }) => { - const filter: string[] = []; - - if (runId) { - filter.push(`kibana.alert.rule.execution.uuid: ${runId}`); - } - - if (message) { - const escapedMessage = message.replace(/([\)\(\<\>\}\{\"\:\\])/gm, '\\$&'); - filter.push(`message: "${escapedMessage}" OR error.message: "${escapedMessage}"`); - } - - return filter; -}; - export const loadActionErrorLog = ({ id, http, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_kpi_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_kpi_aggregations.ts new file mode 100644 index 0000000000000..076e1167f444a --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_kpi_aggregations.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { IExecutionKPIResult } from '@kbn/alerting-plugin/common'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { getFilter } from './get_filter'; + +export interface LoadExecutionKPIAggregationsProps { + id: string; + outcomeFilter?: string[]; + message?: string; + dateStart: string; + dateEnd?: string; +} + +export const loadExecutionKPIAggregations = ({ + id, + http, + outcomeFilter, + message, + dateStart, + dateEnd, +}: LoadExecutionKPIAggregationsProps & { http: HttpSetup }) => { + const filter = getFilter({ outcomeFilter, message }); + + return http.get( + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${id}/_execution_kpi`, + { + query: { + filter: filter.length ? filter.join(' and ') : undefined, + date_start: dateStart, + date_end: dateEnd, + }, + } + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.ts index c1f8487d842c5..bf5e529499b42 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.ts @@ -16,6 +16,7 @@ import { } from '@kbn/alerting-plugin/common'; import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { getFilter } from './get_filter'; const getRenamedLog = (data: IExecutionLog) => { const { @@ -40,22 +41,6 @@ const rewriteBodyRes: RewriteRequestCase = ({ data, ...rest ...rest, }); -// TODO (Jiawei): Use node builder instead of strings -const getFilter = ({ outcomeFilter, message }: { outcomeFilter?: string[]; message?: string }) => { - const filter: string[] = []; - - if (outcomeFilter && outcomeFilter.length) { - filter.push(`event.outcome: ${outcomeFilter.join(' or ')}`); - } - - if (message) { - const escapedMessage = message.replace(/([\)\(\<\>\}\{\"\:\\])/gm, '\\$&'); - filter.push(`message: "${escapedMessage}" OR error.message: "${escapedMessage}"`); - } - - return filter; -}; - export type SortField = Record< ExecutionLogSortFields, { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_global_execution_kpi_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_global_execution_kpi_aggregations.ts new file mode 100644 index 0000000000000..332e14ad4383f --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_global_execution_kpi_aggregations.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 { HttpSetup } from '@kbn/core/public'; +import { IExecutionKPIResult } from '@kbn/alerting-plugin/common'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { getFilter } from './get_filter'; + +export interface LoadGlobalExecutionKPIAggregationsProps { + id: string; + outcomeFilter?: string[]; + message?: string; + dateStart: string; + dateEnd?: string; +} + +export const loadGlobalExecutionKPIAggregations = ({ + id, + http, + outcomeFilter, + message, + dateStart, + dateEnd, +}: LoadGlobalExecutionKPIAggregationsProps & { http: HttpSetup }) => { + const filter = getFilter({ outcomeFilter, message }); + + return http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/_global_execution_kpi`, { + query: { + filter: filter.length ? filter.join(' and ') : undefined, + date_start: dateStart, + date_end: dateEnd, + }, + }); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index 3150a4cdf407b..fa93ae18ec701 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -7,7 +7,11 @@ import React from 'react'; -import { IExecutionLogResult, IExecutionErrorsResult } from '@kbn/alerting-plugin/common'; +import { + IExecutionLogResult, + IExecutionErrorsResult, + IExecutionKPIResult, +} from '@kbn/alerting-plugin/common'; import { Rule, RuleType, @@ -46,6 +50,10 @@ import { bulkSnoozeRules, BulkSnoozeRulesProps, unsnoozeRule, + loadExecutionKPIAggregations, + LoadExecutionKPIAggregationsProps, + loadGlobalExecutionKPIAggregations, + LoadGlobalExecutionKPIAggregationsProps, bulkUnsnoozeRules, BulkUnsnoozeRulesProps, } from '../../../lib/rule_api'; @@ -74,12 +82,18 @@ export interface ComponentOpts { loadRuleState: (id: Rule['id']) => Promise; loadRuleSummary: (id: Rule['id'], numberOfExecutions?: number) => Promise; loadRuleTypes: () => Promise; + loadExecutionKPIAggregations: ( + props: LoadExecutionKPIAggregationsProps + ) => Promise; loadExecutionLogAggregations: ( props: LoadExecutionLogAggregationsProps ) => Promise; loadGlobalExecutionLogAggregations: ( props: LoadGlobalExecutionLogAggregationsProps ) => Promise; + loadGlobalExecutionKPIAggregations: ( + props: LoadGlobalExecutionKPIAggregationsProps + ) => Promise; loadActionErrorLog: (props: LoadActionErrorLogProps) => Promise; getHealth: () => Promise; resolveRule: (id: Rule['id']) => Promise; @@ -177,6 +191,22 @@ export function withBulkRuleOperations( http, }) } + loadExecutionKPIAggregations={async ( + loadExecutionKPIAggregationProps: LoadExecutionKPIAggregationsProps + ) => + loadExecutionKPIAggregations({ + ...loadExecutionKPIAggregationProps, + http, + }) + } + loadGlobalExecutionKPIAggregations={async ( + loadGlobalExecutionKPIAggregationsProps: LoadGlobalExecutionKPIAggregationsProps + ) => + loadGlobalExecutionKPIAggregations({ + ...loadGlobalExecutionKPIAggregationsProps, + http, + }) + } resolveRule={async (ruleId: Rule['id']) => resolveRule({ http, ruleId })} getHealth={async () => alertingFrameworkHealth({ http })} snoozeRule={async (rule: Rule, snoozeSchedule: SnoozeSchedule) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx index bfe337a1fb880..5eac73c4e87ac 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx @@ -419,8 +419,8 @@ describe('tabbed content', () => { tabbedContent.update(); }); - expect(tabbedContent.find('[aria-labelledby="rule_event_log_list"]').exists()).toBeTruthy(); - expect(tabbedContent.find('[aria-labelledby="rule_alert_list"]').exists()).toBeFalsy(); + expect(tabbedContent.find('[aria-labelledby="rule_event_log_list"]').exists()).toBeFalsy(); + expect(tabbedContent.find('[aria-labelledby="rule_alert_list"]').exists()).toBeTruthy(); tabbedContent.find('[data-test-subj="eventLogListTab"]').simulate('click'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index 5c44b9161b2c2..db82f36cbc90c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -97,6 +97,14 @@ export function RuleComponent({ }; const tabs = [ + { + id: ALERT_LIST_TAB, + name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText', { + defaultMessage: 'Alerts', + }), + 'data-test-subj': 'ruleAlertListTab', + content: renderRuleAlertList(), + }, { id: EVENT_LOG_LIST_TAB, name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText', { @@ -118,14 +126,6 @@ export function RuleComponent({ requestRefresh, }), }, - { - id: ALERT_LIST_TAB, - name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText', { - defaultMessage: 'Alerts', - }), - 'data-test-subj': 'ruleAlertListTab', - content: renderRuleAlertList(), - }, ]; const renderTabs = () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx new file mode 100644 index 0000000000000..f77494e4f3c12 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_execution_kpi_aggregations'; +import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; +import { RuleEventLogListKPI } from './rule_event_log_list_kpi'; + +jest.mock('../../../../common/lib/kibana', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + notifications: { toast: { addDanger: jest.fn() } }, + }, + }), +})); + +jest.mock('../../../lib/rule_api/load_execution_kpi_aggregations', () => ({ + loadExecutionKPIAggregations: jest.fn(), +})); + +jest.mock('../../../lib/rule_api/load_global_execution_kpi_aggregations', () => ({ + loadGlobalExecutionKPIAggregations: jest.fn(), +})); + +const mockKpiResponse = { + success: 4, + unknown: 10, + failure: 60, + activeAlerts: 100, + newAlerts: 40, + recoveredAlerts: 30, + erroredActions: 60, + triggeredActions: 140, +}; + +const loadExecutionKPIAggregationsMock = + loadExecutionKPIAggregations as unknown as jest.MockedFunction; +const loadGlobalExecutionKPIAggregationsMock = + loadGlobalExecutionKPIAggregations as unknown as jest.MockedFunction; + +describe('rule_event_log_list_kpi', () => { + beforeEach(() => { + jest.clearAllMocks(); + loadExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); + loadGlobalExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); + }); + + it('renders correctly', async () => { + const wrapper = mountWithIntl( + + ); + + expect(wrapper.find('[data-test-subj="centerJustifiedSpinner"]').exists()).toBeTruthy(); + + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="centerJustifiedSpinner"]').exists()).toBeFalsy(); + + expect(loadExecutionKPIAggregationsMock).toHaveBeenCalledWith( + expect.objectContaining({ + id: '123', + message: undefined, + outcomeFilter: undefined, + }) + ); + + expect(loadGlobalExecutionKPIAggregations).not.toHaveBeenCalled(); + + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-successOutcome"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.success}`); + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-unknownOutcome"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.unknown}`); + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-failureOutcome"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.failure}`); + expect( + wrapper.find('[data-test-subj="ruleEventLogKpi-activeAlerts"] .euiStat__title').first().text() + ).toEqual(`${mockKpiResponse.activeAlerts}`); + expect( + wrapper.find('[data-test-subj="ruleEventLogKpi-newAlerts"] .euiStat__title').first().text() + ).toEqual(`${mockKpiResponse.newAlerts}`); + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-recoveredAlerts"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.recoveredAlerts}`); + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-erroredActions"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.erroredActions}`); + expect( + wrapper + .find('[data-test-subj="ruleEventLogKpi-triggeredActions"] .euiStat__title') + .first() + .text() + ).toEqual(`${mockKpiResponse.triggeredActions}`); + }); + + it('calls global KPI API if provided global rule id', async () => { + const wrapper = mountWithIntl( + + ); + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadGlobalExecutionKPIAggregations).toHaveBeenCalledWith( + expect.objectContaining({ + id: '*', + message: undefined, + outcomeFilter: undefined, + }) + ); + + expect(loadExecutionKPIAggregationsMock).not.toHaveBeenCalled(); + }); + + it('calls KPI API with filters', async () => { + const wrapper = mountWithIntl( + + ); + + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadExecutionKPIAggregationsMock).toHaveBeenCalledWith( + expect.objectContaining({ + id: '123', + message: 'test', + outcomeFilter: ['status: 123', 'test:456'], + }) + ); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx new file mode 100644 index 0000000000000..7fe7dd8fdb029 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx @@ -0,0 +1,251 @@ +/* + * 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, useState, useMemo, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import datemath from '@kbn/datemath'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiIconTip, EuiStat, EuiSpacer } from '@elastic/eui'; +import { IExecutionKPIResult } from '@kbn/alerting-plugin/common'; +import { + ComponentOpts as RuleApis, + withBulkRuleOperations, +} from '../../common/components/with_bulk_rule_api_operations'; +import { useKibana } from '../../../../common/lib/kibana'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; +import { RuleEventLogListStatus } from './rule_event_log_list_status'; + +const getParsedDate = (date: string) => { + if (date.includes('now')) { + return datemath.parse(date)?.format() || date; + } + return date; +}; + +const API_FAILED_MESSAGE = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.ruleEventLogListKpi.apiError', + { + defaultMessage: 'Failed to fetch event log KPI.', + } +); + +const RESPONSE_TOOLTIP = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.ruleEventLogListKpi.responseTooltip', + { + defaultMessage: 'The responses for the latest rule runs.', + } +); + +const ALERTS_TOOLTIP = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.ruleEventLogListKpi.alertsTooltip', + { + defaultMessage: 'The alert statuses for the latest rule runs.', + } +); + +const ACTIONS_TOOLTIP = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.ruleEventLogListKpi.actionsTooltip', + { + defaultMessage: 'The action statuses for the latest rule runs.', + } +); + +const Stat = ({ + title, + tooltip, + children, +}: { + title: string; + tooltip: string; + children?: JSX.Element; +}) => { + return ( + + + + {title} + + + + + + + {children} + + ); +}; + +export type RuleEventLogListKPIProps = { + ruleId: string; + dateStart: string; + dateEnd: string; + outcomeFilter?: string[]; + message?: string; + refreshToken?: number; +} & Pick; + +export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { + const { + ruleId, + dateStart, + dateEnd, + outcomeFilter, + message, + refreshToken, + loadExecutionKPIAggregations, + loadGlobalExecutionKPIAggregations, + } = props; + const { + notifications: { toasts }, + } = useKibana().services; + + const isInitialized = useRef(false); + + const [isLoading, setIsLoading] = useState(false); + const [kpi, setKpi] = useState(); + + const loadKPIFn = useMemo(() => { + if (ruleId === '*') { + return loadGlobalExecutionKPIAggregations; + } + return loadExecutionKPIAggregations; + }, [ruleId, loadExecutionKPIAggregations, loadGlobalExecutionKPIAggregations]); + + const loadKPIs = async () => { + setIsLoading(true); + try { + const newKpi = await loadKPIFn({ + id: ruleId, + dateStart: getParsedDate(dateStart), + dateEnd: getParsedDate(dateEnd), + outcomeFilter, + message, + }); + setKpi(newKpi); + } catch (e) { + toasts.addDanger({ + title: API_FAILED_MESSAGE, + text: e.body?.message ?? e, + }); + } + setIsLoading(false); + }; + + useEffect(() => { + loadKPIs(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ruleId, dateStart, dateEnd, outcomeFilter, message]); + + useEffect(() => { + if (isInitialized.current) { + loadKPIs(); + } + isInitialized.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refreshToken]); + + if (isLoading || !kpi) { + return ; + } + + const getStatDescription = (element: React.ReactNode) => { + return ( + <> + {element} + + + ); + }; + + return ( + + + + + + )} + titleSize="s" + title={kpi.success} + /> + + + )} + titleSize="s" + title={kpi.unknown} + /> + + + )} + titleSize="s" + title={kpi.failure} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const RuleEventLogListKPIWithApi = withBulkRuleOperations(RuleEventLogListKPI); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx index b647442a8eaf0..2c1d6df71fc3d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx @@ -30,9 +30,9 @@ import { RuleEventLogListStatusFilter } from './rule_event_log_list_status_filte import { RuleEventLogDataGrid } from './rule_event_log_data_grid'; import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; import { RuleActionErrorLogFlyout } from './rule_action_error_log_flyout'; - import { RefineSearchPrompt } from '../refine_search_prompt'; import { LoadExecutionLogAggregationsProps } from '../../../lib/rule_api'; +import { RuleEventLogListKPIWithApi as RuleEventLogListKPI } from './rule_event_log_list_kpi'; import { ComponentOpts as RuleApis, withBulkRuleOperations, @@ -114,6 +114,9 @@ export const RuleEventLogListTable = ( const [search, setSearch] = useState(''); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedRunLog, setSelectedRunLog] = useState(); + const [internalRefreshToken, setInternalRefreshToken] = useState( + refreshToken + ); // Data grid states const [logs, setLogs] = useState(); @@ -243,6 +246,7 @@ export const RuleEventLogListTable = ( ); const onRefresh = () => { + setInternalRefreshToken(Date.now()); loadEventLogs(); }; @@ -339,6 +343,10 @@ export const RuleEventLogListTable = ( localStorage.setItem(localStorageKey, JSON.stringify(visibleColumns)); }, [localStorageKey, visibleColumns]); + useEffect(() => { + setInternalRefreshToken(refreshToken); + }, [refreshToken]); + return ( <> @@ -371,6 +379,15 @@ export const RuleEventLogListTable = ( + + {renderList()} {isOnLastPage && ( { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + it('should return KPI only from the current space', async () => { + const startTime = new Date().toISOString(); + + const spaceId = UserAtSpaceScenarios[1].space.id; + const user = UserAtSpaceScenarios[1].user; + const response = await supertest + .post(`${getUrlPrefix(spaceId)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + schedule: { interval: '1s' }, + throttle: null, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(spaceId, ruleId, 'rule', 'alerting'); + + const response2 = await supertest + .post(`${getUrlPrefix(spaceId)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + schedule: { interval: '1s' }, + throttle: null, + }) + ); + + expect(response2.status).to.eql(200); + const ruleId2 = response2.body.id; + objectRemover.add(spaceId, ruleId2, 'rule', 'alerting'); + + await retry.try(async () => { + // break AAD + await supertest + .put(`${getUrlPrefix(spaceId)}/api/alerts_fixture/saved_object/alert/${ruleId2}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + }); + + await retry.try(async () => { + // there can be a successful execute before the error one + const someEvents = await getEventLog({ + getService, + spaceId, + type: 'alert', + id: ruleId2, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), + }); + const errorEvents = someEvents.filter( + (event) => event?.kibana?.alerting?.status === 'error' + ); + expect(errorEvents.length).to.be.above(0); + }); + + await retry.try(async () => { + // there can be a successful execute before the error one + const logResponse = await supertestWithoutAuth + .get( + `${getUrlPrefix( + spaceId + )}/internal/alerting/_global_execution_logs?date_start=${startTime}&date_end=9999-12-31T23:59:59Z&per_page=50&page=1` + ) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password); + expect(logResponse.statusCode).to.be(200); + expect(logResponse.body.data.length).to.be.above(1); + }); + + await retry.try(async () => { + // break AAD + await supertest + .put(`${getUrlPrefix(spaceId)}/api/alerts_fixture/saved_object/alert/${ruleId}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + }); + + await retry.try(async () => { + // there can be a successful execute before the error one + const someEvents = await getEventLog({ + getService, + spaceId, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), + }); + const errorEvents = someEvents.filter( + (event) => event?.kibana?.alerting?.status === 'error' + ); + expect(errorEvents.length).to.be.above(0); + }); + + const kpiLogs = await retry.try(async () => { + // there can be a successful execute before the error one + const logResponse = await supertestWithoutAuth + .get( + `${getUrlPrefix( + spaceId + )}/internal/alerting/_global_execution_kpi?date_start=${startTime}&date_end=9999-12-31T23:59:59Z` + ) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password); + expect(logResponse.statusCode).to.be(200); + + return logResponse.body; + }); + + expect(Object.keys(kpiLogs)).to.eql([ + 'success', + 'unknown', + 'failure', + 'activeAlerts', + 'newAlerts', + 'recoveredAlerts', + 'erroredActions', + 'triggeredActions', + ]); + // it should be above 1 since we have two rule running + expect(kpiLogs.success).to.be.above(1); + expect(kpiLogs.failure).to.be.above(0); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/get_rule_execution_kpi.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/get_rule_execution_kpi.ts new file mode 100644 index 0000000000000..2303fc616d8dc --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/get_rule_execution_kpi.ts @@ -0,0 +1,133 @@ +/* + * 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 expect from '@kbn/expect'; +import { UserAtSpaceScenarios } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function getRuleExecutionKpiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const retry = getService('retry'); + + describe('getRuleExecutionKpi', () => { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + it('should return KPI only from the current space', async () => { + const startTime = new Date().toISOString(); + + const spaceId = UserAtSpaceScenarios[1].space.id; + const user = UserAtSpaceScenarios[1].user; + const response = await supertest + .post(`${getUrlPrefix(spaceId)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + schedule: { interval: '1s' }, + throttle: null, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(spaceId, ruleId, 'rule', 'alerting'); + + const spaceId2 = UserAtSpaceScenarios[4].space.id; + const response2 = await supertest + .post(`${getUrlPrefix(spaceId2)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + schedule: { interval: '1s' }, + throttle: null, + }) + ); + + expect(response2.status).to.eql(200); + const ruleId2 = response2.body.id; + objectRemover.add(spaceId2, ruleId2, 'rule', 'alerting'); + + await retry.try(async () => { + // there can be a successful execute before the error one + const someEvents = await getEventLog({ + getService, + spaceId, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), + }); + + expect(someEvents.length).to.be.above(0); + }); + + await retry.try(async () => { + // break AAD + await supertest + .put(`${getUrlPrefix(spaceId)}/api/alerts_fixture/saved_object/alert/${ruleId}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + }); + + await retry.try(async () => { + // there can be a successful execute before the error one + const someEvents = await getEventLog({ + getService, + spaceId, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), + }); + const errorEvents = someEvents.filter( + (event) => event?.kibana?.alerting?.status === 'error' + ); + expect(errorEvents.length).to.be.above(0); + }); + + const kpiLogs = await retry.try(async () => { + // there can be a successful execute before the error one + const logResponse = await supertestWithoutAuth + .get( + `${getUrlPrefix( + spaceId + )}/internal/alerting/rule/${ruleId}/_execution_kpi?date_start=${startTime}&date_end=9999-12-31T23:59:59Z` + ) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password); + expect(logResponse.statusCode).to.be(200); + + return logResponse.body; + }); + + expect(Object.keys(kpiLogs)).to.eql([ + 'success', + 'unknown', + 'failure', + 'activeAlerts', + 'newAlerts', + 'recoveredAlerts', + 'erroredActions', + 'triggeredActions', + ]); + expect(kpiLogs.success).to.be.above(0); + expect(kpiLogs.failure).to.be.above(0); + }); + }); +} From 254b9a525d5508614d035a22dc690f53ed837a2e Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 28 Sep 2022 15:52:01 +0300 Subject: [PATCH 122/172] [Lens] Stabilize the table functional test (#142038) --- .../test/functional/apps/lens/group1/table.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/test/functional/apps/lens/group1/table.ts b/x-pack/test/functional/apps/lens/group1/table.ts index 7bf9b49c53d8d..724efbe6c6c82 100644 --- a/x-pack/test/functional/apps/lens/group1/table.ts +++ b/x-pack/test/functional/apps/lens/group1/table.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const PageObjects = getPageObjects(['visualize', 'lens', 'common']); const listingTable = getService('listingTable'); const find = getService('find'); const retry = getService('retry'); @@ -91,14 +91,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow to sort by transposed columns', async () => { await PageObjects.lens.changeTableSortingBy(2, 'ascending'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); expect(await PageObjects.lens.getDatatableCellText(0, 2)).to.eql('17,246'); }); it('should show dynamic coloring feature for numeric columns', async () => { await PageObjects.lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); await PageObjects.lens.setTableDynamicColoring('text'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); const styleObj = await PageObjects.lens.getDatatableCellStyle(0, 2); expect(styleObj['background-color']).to.be(undefined); expect(styleObj.color).to.be('rgb(133, 189, 177)'); @@ -106,7 +106,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow to color cell background rather than text', async () => { await PageObjects.lens.setTableDynamicColoring('cell'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); const styleObj = await PageObjects.lens.getDatatableCellStyle(0, 2); expect(styleObj['background-color']).to.be('rgb(133, 189, 177)'); // should also set text color when in cell mode @@ -115,9 +115,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should open the palette panel to customize the palette look', async () => { await PageObjects.lens.openPalettePanel('lnsDatatable'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); await PageObjects.lens.changePaletteTo('temperature'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); const styleObj = await PageObjects.lens.getDatatableCellStyle(0, 2); expect(styleObj['background-color']).to.be('rgb(235, 239, 245)'); }); @@ -125,7 +125,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should keep the coloring consistent when changing mode', async () => { // Change mode from percent to number await testSubjects.click('lnsPalettePanel_dynamicColoring_rangeType_groups_number'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); // check that all remained the same const styleObj = await PageObjects.lens.getDatatableCellStyle(0, 2); expect(styleObj['background-color']).to.be('rgb(235, 239, 245)'); @@ -133,7 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should keep the coloring consistent when moving to custom palette from default', async () => { await PageObjects.lens.changePaletteTo('custom'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); // check that all remained the same const styleObj = await PageObjects.lens.getDatatableCellStyle(0, 2); expect(styleObj['background-color']).to.be('rgb(235, 239, 245)'); @@ -149,7 +149,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // when clicking on another row will trigger a sorting + update await testSubjects.click('lnsPalettePanel_dynamicColoring_range_value_1'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); // pick a cell without color as is below the range const styleObj = await PageObjects.lens.getDatatableCellStyle(3, 3); expect(styleObj['background-color']).to.be(undefined); @@ -159,7 +159,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow the user to reverse the palette', async () => { await testSubjects.click('lnsPalettePanel_dynamicColoring_reverseColors'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); const styleObj = await PageObjects.lens.getDatatableCellStyle(1, 1); expect(styleObj['background-color']).to.be('rgb(168, 191, 218)'); // should also set text color when in cell mode @@ -169,7 +169,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow to show a summary table for metric columns', async () => { await PageObjects.lens.setTableSummaryRowFunction('sum'); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization(); await PageObjects.lens.assertExactText( '[data-test-subj="lnsDataTable-footer-169.228.188.120-›-Average-of-bytes"]', 'Sum: 18,994' From c7301e51f172028bdce87ea626b25c15849fd7eb Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:58:57 +0100 Subject: [PATCH 123/172] [Security Solution][Detections] adds missing bulk edit data view tests (#141915) ## Summary - addresses https://github.com/elastic/kibana/issues/135201 - adds Data View cypress and integration tests according to [Data view Bulk Edit test plan](https://docs.google.com/document/d/116x7ITTTJQ6cTiwaGK831_f6Ox7XB3qyLiHxC3Cmf8w/edit#heading=h.j583i3o7bg2g) (internal document) - integration tests were added earlier in https://github.com/elastic/kibana/pull/138448 --- .../bulk_edit_rules_data_view.cy.ts | 177 ++++++++++++++ .../cypress/screens/rules_bulk_edit.ts | 6 + .../cypress/tasks/api_calls/rules.ts | 228 +++++++++--------- .../cypress/tasks/rule_details.ts | 3 + .../cypress/tasks/rules_bulk_edit.ts | 9 + .../group10/perform_bulk_action.ts | 36 +++ 6 files changed, 344 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts new file mode 100644 index 0000000000000..766c8d9483f0a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts @@ -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 { + RULES_BULK_EDIT_DATA_VIEWS_WARNING, + RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX, +} from '../../screens/rules_bulk_edit'; + +import { DATA_VIEW_DETAILS, INDEX_PATTERNS_DETAILS } from '../../screens/rule_details'; + +import { + waitForRulesTableToBeLoaded, + goToRuleDetails, + selectNumberOfRules, +} from '../../tasks/alerts_detection_rules'; + +import { + typeIndexPatterns, + waitForBulkEditActionToFinish, + submitBulkEditForm, + checkOverwriteDataViewCheckbox, + checkOverwriteIndexPatternsCheckbox, + openBulkEditAddIndexPatternsForm, + openBulkEditDeleteIndexPatternsForm, +} from '../../tasks/rules_bulk_edit'; + +import { hasIndexPatterns, getDetails, assertDetailsNotExist } from '../../tasks/rule_details'; +import { login, visitWithoutDateRange } from '../../tasks/login'; + +import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; +import { + createCustomRule, + createCustomIndicatorRule, + createEventCorrelationRule, + createThresholdRule, + createNewTermsRule, + createSavedQueryRule, +} from '../../tasks/api_calls/rules'; +import { cleanKibana, deleteAlertsAndRules, postDataView } from '../../tasks/common'; + +import { + getEqlRule, + getNewThreatIndicatorRule, + getNewRule, + getNewThresholdRule, + getNewTermsRule, +} from '../../objects/rule'; + +import { esArchiverResetKibana } from '../../tasks/es_archiver'; + +const DATA_VIEW_ID = 'auditbeat'; + +const expectedIndexPatterns = ['index-1-*', 'index-2-*']; + +const expectedNumberOfCustomRulesToBeEdited = 6; + +const indexDataSource = { dataView: DATA_VIEW_ID, type: 'dataView' } as const; + +const defaultRuleData = { + dataSource: indexDataSource, +}; + +describe('Detection rules, bulk edit, data view', () => { + before(() => { + cleanKibana(); + login(); + }); + beforeEach(() => { + deleteAlertsAndRules(); + esArchiverResetKibana(); + + postDataView(DATA_VIEW_ID); + + createCustomRule({ ...getNewRule(), ...defaultRuleData }, '1'); + createEventCorrelationRule({ ...getEqlRule(), ...defaultRuleData }, '2'); + createCustomIndicatorRule({ ...getNewThreatIndicatorRule(), ...defaultRuleData }, '3'); + createThresholdRule({ ...getNewThresholdRule(), ...defaultRuleData }, '4'); + createNewTermsRule({ ...getNewTermsRule(), ...defaultRuleData }, '5'); + createSavedQueryRule({ ...getNewRule(), ...defaultRuleData, savedId: 'mocked' }, '6'); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + waitForRulesTableToBeLoaded(); + }); + + it('Add index patterns to custom rules with configured data view', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(expectedIndexPatterns); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule still has data view and index patterns field does not exist + goToRuleDetails(); + getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); + assertDetailsNotExist(INDEX_PATTERNS_DETAILS); + }); + + it('Add index patterns to custom rules with configured data view when data view checkbox is checked', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(expectedIndexPatterns); + + // click on data view overwrite checkbox, ensure warning is displayed + cy.get(RULES_BULK_EDIT_DATA_VIEWS_WARNING).should('not.exist'); + checkOverwriteDataViewCheckbox(); + cy.get(RULES_BULK_EDIT_DATA_VIEWS_WARNING).should('be.visible'); + + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule has been updated with index patterns and data view does not exist + goToRuleDetails(); + hasIndexPatterns(expectedIndexPatterns.join('')); + assertDetailsNotExist(DATA_VIEW_DETAILS); + }); + + it('Overwrite index patterns in custom rules with configured data view', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(expectedIndexPatterns); + checkOverwriteIndexPatternsCheckbox(); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule still has data view and index patterns field does not exist + goToRuleDetails(); + getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); + assertDetailsNotExist(INDEX_PATTERNS_DETAILS); + }); + + it('Overwrite index patterns in custom rules with configured data view when data view checkbox is checked', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(expectedIndexPatterns); + checkOverwriteIndexPatternsCheckbox(); + checkOverwriteDataViewCheckbox(); + + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule has been overwritten with index patterns and data view does not exist + goToRuleDetails(); + hasIndexPatterns(expectedIndexPatterns.join('')); + assertDetailsNotExist(DATA_VIEW_DETAILS); + }); + + it('Delete index patterns in custom rules with configured data view', () => { + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + openBulkEditDeleteIndexPatternsForm(); + typeIndexPatterns(expectedIndexPatterns); + + // in delete form data view checkbox is absent + cy.get(RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX).should('not.exist'); + + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule still has data view and index patterns field does not exist + goToRuleDetails(); + getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts index 34e4f9515b27f..6f4056034a053 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts @@ -32,6 +32,9 @@ export const RULES_BULK_EDIT_INDEX_PATTERNS = '[data-test-subj="bulkEditRulesInd export const RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX = '[data-test-subj="bulkEditRulesOverwriteIndexPatterns"]'; +export const RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX = + '[data-test-subj="bulkEditRulesOverwriteRulesWithDataViews"]'; + export const RULES_BULK_EDIT_TAGS = '[data-test-subj="bulkEditRulesTags"]'; export const RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX = @@ -48,6 +51,9 @@ export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR = export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING = '[data-test-subj="bulkEditRulesTimelineTemplateWarning"]'; +export const RULES_BULK_EDIT_DATA_VIEWS_WARNING = + '[data-test-subj="bulkEditRulesDataViewsWarning"]'; + export const RULES_BULK_EDIT_SCHEDULES_WARNING = '[data-test-subj="bulkEditRulesSchedulesWarning"]'; export const UPDATE_SCHEDULE_INTERVAL_INPUT = diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 4fb076f11f445..80fb77013acbb 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -53,7 +53,8 @@ export const createCustomRule = ( severity: rule.severity.toLocaleLowerCase(), type: 'query', from: 'now-50000h', - index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, query: rule.customQuery, language: 'kuery', enabled: false, @@ -71,83 +72,80 @@ export const createCustomRule = ( }); export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => { - if (rule.dataSource.type === 'indexPatterns') { - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, - from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'eql', - index: rule.dataSource.index, - query: rule.customQuery, - language: 'eql', - enabled: true, - tags: rule.tags, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - }); - } + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, + from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'eql', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, + query: rule.customQuery, + language: 'eql', + enabled: true, + tags: rule.tags, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); }; export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing') => { - if (rule.dataSource.type === 'indexPatterns') { - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, - from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'threshold', - index: rule.dataSource.index, - query: rule.customQuery, - threshold: { - field: [rule.thresholdField], - value: parseInt(rule.threshold, 10), - cardinality: [], - }, - enabled: true, - tags: rule.tags, + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, + from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'threshold', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, + query: rule.customQuery, + threshold: { + field: [rule.thresholdField], + value: parseInt(rule.threshold, 10), + cardinality: [], }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - }); - } + enabled: true, + tags: rule.tags, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); }; export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') => { - if (rule.dataSource.type === 'indexPatterns') { - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, - from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'new_terms', - index: rule.dataSource.index, - query: rule.customQuery, - new_terms_fields: rule.newTermsFields, - history_window_start: `now-${rule.historyWindowSize.interval}${rule.historyWindowSize.type}`, - enabled: true, - tags: rule.tags, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - }); - } + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, + from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'new_terms', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, + query: rule.customQuery, + new_terms_fields: rule.newTermsFields, + history_window_start: `now-${rule.historyWindowSize.interval}${rule.historyWindowSize.type}`, + enabled: true, + tags: rule.tags, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); }; export const createSavedQueryRule = ( @@ -166,7 +164,8 @@ export const createSavedQueryRule = ( severity: rule.severity.toLocaleLowerCase(), type: 'saved_query', from: 'now-50000h', - index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, saved_id: rule.savedId, language: 'kuery', enabled: false, @@ -184,49 +183,48 @@ export const createSavedQueryRule = ( }); export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => { - if (rule.dataSource.type === 'indexPatterns') { - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - // Default interval is 1m, our tests config overwrite this to 1s - // See https://github.com/elastic/kibana/pull/125396 for details - interval: '10s', - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'threat_match', - timeline_id: rule.timeline.templateTimelineId, - timeline_title: rule.timeline.title, - threat_mapping: [ - { - entries: [ - { - field: rule.indicatorMappingField, - type: 'mapping', - value: rule.indicatorIndexField, - }, - ], - }, - ], - threat_query: '*:*', - threat_language: 'kuery', - threat_filters: [], - threat_index: rule.indicatorIndexPattern, - threat_indicator_path: rule.threatIndicatorPath, - from: 'now-50000h', - index: rule.dataSource.index, - query: rule.customQuery || '*:*', - language: 'kuery', - enabled: true, - tags: rule.tags, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - failOnStatusCode: false, - }); - } + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + // Default interval is 1m, our tests config overwrite this to 1s + // See https://github.com/elastic/kibana/pull/125396 for details + interval: '10s', + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'threat_match', + timeline_id: rule.timeline.templateTimelineId, + timeline_title: rule.timeline.title, + threat_mapping: [ + { + entries: [ + { + field: rule.indicatorMappingField, + type: 'mapping', + value: rule.indicatorIndexField, + }, + ], + }, + ], + threat_query: '*:*', + threat_language: 'kuery', + threat_filters: [], + threat_index: rule.indicatorIndexPattern, + threat_indicator_path: rule.threatIndicatorPath, + from: 'now-50000h', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined, + data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined, + query: rule.customQuery || '*:*', + language: 'kuery', + enabled: true, + tags: rule.tags, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, + }); }; export const createCustomRuleEnabled = ( diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 75668b49aa207..0bd06911acf7d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -158,6 +158,9 @@ export const goBackToAllRulesTable = () => { export const getDetails = (title: string | RegExp) => cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION); +export const assertDetailsNotExist = (title: string | RegExp) => + cy.get(DETAILS_TITLE).contains(title).should('not.exist'); + export const hasIndexPatterns = (indexPatterns: string) => { cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns); diff --git a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts index 5b3ae403f4a0b..0000a84f26683 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts @@ -30,6 +30,7 @@ import { APPLY_TIMELINE_RULE_BULK_MENU_ITEM, RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX, RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX, + RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX, RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR, UPDATE_SCHEDULE_MENU_ITEM, UPDATE_SCHEDULE_INTERVAL_INPUT, @@ -159,6 +160,14 @@ export const checkOverwriteIndexPatternsCheckbox = () => { .should('be.checked'); }; +export const checkOverwriteDataViewCheckbox = () => { + cy.get(RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX) + .should('have.text', 'Apply changes to rules configured with data views') + .click() + .get('input') + .should('be.checked'); +}; + export const selectTimelineTemplate = (timelineTitle: string) => { cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR).click(); cy.get(TIMELINE_SEARCHBOX).type(`${timelineTitle}{enter}`).should('not.exist'); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index c17e679b6be31..8f06bc93820a4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -1829,6 +1829,42 @@ export default ({ getService }: FtrProviderContext): void => { expect(setIndexRule.data_view_id).to.eql(undefined); }); + it('should return error when set an empty index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => { + const dataViewId = 'index1-*'; + const simpleRule = { + ...getSimpleRule(), + index: undefined, + data_view_id: dataViewId, + }; + const rule = await createRule(supertest, log, simpleRule); + + const { body } = await postBulkAction() + .send({ + query: '', + action: BulkAction.edit, + [BulkAction.edit]: [ + { + type: BulkActionEditType.set_index_patterns, + value: [], + overwrite_data_views: true, + }, + ], + }) + .expect(500); + + expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).to.eql({ + message: "Mutated params invalid: Index patterns can't be empty", + status_code: 500, + rules: [ + { + id: rule.id, + name: rule.name, + }, + ], + }); + }); + it('should NOT set an index pattern to a rule and overwrite the data view when overwrite_data_views is false', async () => { const ruleId = 'ruleId'; const dataViewId = 'index1-*'; From 7cae4515534d8387c67cc8f114573ed241095106 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 14:25:28 +0100 Subject: [PATCH 124/172] skip flaky suite (#133259) --- x-pack/test/api_integration/apis/osquery/packs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/osquery/packs.ts b/x-pack/test/api_integration/apis/osquery/packs.ts index 9d00249a4e1b0..de490a489cec7 100644 --- a/x-pack/test/api_integration/apis/osquery/packs.ts +++ b/x-pack/test/api_integration/apis/osquery/packs.ts @@ -45,7 +45,8 @@ limit 1000;`; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe('Packs', () => { + // FLAKY: https://github.com/elastic/kibana/issues/133259 + describe.skip('Packs', () => { let packId: string = ''; let hostedPolicy: Record; let packagePolicyId: string; From 421150a445e5811728620bd0c887188d830cc0be Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 14:27:39 +0100 Subject: [PATCH 125/172] skip flaky suite (#142083) --- .../apps/ml/data_frame_analytics/outlier_detection_creation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 947cd82cdd342..b3c471f7255c9 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -13,7 +13,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const editedDescription = 'Edited description'; - describe('outlier detection creation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/142083 + describe.skip('outlier detection creation', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ihp_outlier'); await ml.testResources.createIndexPatternIfNeeded('ft_ihp_outlier', '@timestamp'); From 6ae4764db6ed54ac8ffc690ac4f1c4c81ad176e1 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Wed, 28 Sep 2022 08:46:21 -0500 Subject: [PATCH 126/172] Updates warning for unsupported rolled up data function (#141524) * Updates warning for unsupported rolled up data function * Update the functional test * Fix text on functional test Co-authored-by: Stratoula Kalafateli --- x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx | 2 +- x-pack/test/functional/apps/lens/group2/tsdb.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx index 2cb1ba164d572..77a359729a5e0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx @@ -194,7 +194,7 @@ export function getTSDBRollupWarningMessages( ).map((label) => i18n.translate('xpack.lens.indexPattern.tsdbRollupWarning', { defaultMessage: - '"{label}" does not work for all indices in the selected data view because it\'s using a function which is not supported on rolled up data. Please edit the visualization to use another function or change the time range.', + '{label} uses a function that is unsupported by rolled up data. Select a different function or change the time range.', values: { label, }, diff --git a/x-pack/test/functional/apps/lens/group2/tsdb.ts b/x-pack/test/functional/apps/lens/group2/tsdb.ts index 7a43fc47471a5..d19ab9d19db7d 100644 --- a/x-pack/test/functional/apps/lens/group2/tsdb.ts +++ b/x-pack/test/functional/apps/lens/group2/tsdb.ts @@ -119,7 +119,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('lns-indexPatternDimension-median'); await PageObjects.lens.waitForVisualization('xyVisChart'); await PageObjects.lens.assertEditorWarning( - '"Median of kubernetes.container.memory.available.bytes" does not work for all indices in the selected data view because it\'s using a function which is not supported on rolled up data. Please edit the visualization to use another function or change the time range.' + 'Median of kubernetes.container.memory.available.bytes uses a function that is unsupported by rolled up data. Select a different function or change the time range.' ); }); it('shows warnings in dashboards as well', async () => { @@ -127,7 +127,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.lens.assertInlineWarning( - '"Median of kubernetes.container.memory.available.bytes" does not work for all indices in the selected data view because it\'s using a function which is not supported on rolled up data. Please edit the visualization to use another function or change the time range.' + 'Median of kubernetes.container.memory.available.bytes uses a function that is unsupported by rolled up data. Select a different function or change the time range.' ); }); it('still shows other warnings as toast', async () => { From a73c2d4a1f7e358bac729277c3257dcb499026fc Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 15:51:23 +0100 Subject: [PATCH 127/172] skip flaky suite (#142083) --- .../apps/ml/data_frame_analytics/outlier_detection_creation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index b3c471f7255c9..fb04cd793d9d8 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -109,7 +109,8 @@ export default function ({ getService }: FtrProviderContext) { ]; for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/142083 + describe.skip(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); From 04f570c1a577ab93644e5b802c52fbb4185d90a3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 16:20:06 +0100 Subject: [PATCH 128/172] skip flaky suite (#142095) --- .../apps/ml/data_frame_analytics/regression_creation.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 7a84c41aa4a66..744b61a2a06ca 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -13,7 +13,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const editedDescription = 'Edited description'; - describe('regression creation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/142095 + describe.skip('regression creation', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/egs_regression'); await ml.testResources.createIndexPatternIfNeeded('ft_egs_regression', '@timestamp'); @@ -86,7 +87,8 @@ export default function ({ getService }: FtrProviderContext) { ]; for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/142095 + describe.skip(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); From f72002e6851181870450f8d359986120286fe0d1 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 16:24:21 +0100 Subject: [PATCH 129/172] skip flaky suite (#142102) --- .../apps/ml/data_frame_analytics/classification_creation.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index 2ba4ac6f08350..ea95525062700 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -13,7 +13,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const editedDescription = 'Edited description'; - describe('classification creation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/142102 + describe.skip('classification creation', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification'); await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp'); @@ -92,7 +93,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/142102 + describe.skip(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); From 0b4a942a00bcceee19604bfd06c35663f87725cd Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 16:31:36 +0100 Subject: [PATCH 130/172] skip flaky suite (#142118) --- .../test/functional/apps/ml/data_frame_analytics/cloning.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts index 3a33c95edba42..7d6cc5ea9881a 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts @@ -15,7 +15,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('jobs cloning supported by UI form', function () { + // FLAKY: https://github.com/elastic/kibana/issues/142118 + describe.skip('jobs cloning supported by UI form', function () { const testDataList: Array<{ suiteTitle: string; archive: string; @@ -135,7 +136,8 @@ export default function ({ getService }: FtrProviderContext) { }); for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/142118 + describe.skip(`${testData.suiteTitle}`, function () { const cloneJobId = `${testData.job.id}_clone`; const cloneDestIndex = `${testData.job!.dest!.index}_clone`; From a47918dcd9ca542c5905ac1626e35e4e796743d1 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:17:50 -0400 Subject: [PATCH 131/172] [ResponseOps][Alerting] xpack.actions.proxyUrl is not validated as a URL at startup (#141970) * Adding validation * Removing error --- x-pack/plugins/actions/server/config.test.ts | 22 ++++++++++++++++++++ x-pack/plugins/actions/server/config.ts | 9 ++++++++ 2 files changed, 31 insertions(+) diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index e6e3a24db5214..475b88089581f 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -163,6 +163,28 @@ describe('config validation', () => { `); }); + test('validates proxyUrl', () => { + const proxyUrl = 'https://test.com'; + const badProxyUrl = 'bad url'; + let validated: ActionsConfig; + + validated = configSchema.validate({ proxyUrl }); + expect(validated.proxyUrl).toEqual(proxyUrl); + expect(getValidatedConfig(mockLogger, validated).proxyUrl).toEqual(proxyUrl); + expect(mockLogger.warn.mock.calls).toMatchInlineSnapshot(`Array []`); + + validated = configSchema.validate({ proxyUrl: badProxyUrl }); + expect(validated.proxyUrl).toEqual(badProxyUrl); + expect(getValidatedConfig(mockLogger, validated).proxyUrl).toEqual(badProxyUrl); + expect(mockLogger.warn.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "The confguration xpack.actions.proxyUrl: bad url is invalid.", + ], + ] + `); + }); + // Most of the customHostSettings tests are in ./lib/custom_host_settings.test.ts // but this one seemed more relevant for this test suite, since url is the one // required property. diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 4c8ca7ff9fff7..76270a466ee8f 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -127,6 +127,15 @@ export type ActionsConfig = TypeOf; export function getValidatedConfig(logger: Logger, originalConfig: ActionsConfig): ActionsConfig { const proxyBypassHosts = originalConfig.proxyBypassHosts; const proxyOnlyHosts = originalConfig.proxyOnlyHosts; + const proxyUrl = originalConfig.proxyUrl; + + if (proxyUrl) { + try { + new URL(proxyUrl); + } catch (err) { + logger.warn(`The confguration xpack.actions.proxyUrl: ${proxyUrl} is invalid.`); + } + } if (proxyBypassHosts && proxyOnlyHosts) { logger.warn( From f76918732e52cf5aa5a87d428ad4035b24e0fd33 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Wed, 28 Sep 2022 20:35:25 +0300 Subject: [PATCH 132/172] [Cases] Convert delete and update cases hook to react query (#141920) * Use react query for delete cases * Convert delete to react query * Convert update to react query * Convert use_get_cases_status to react query * Convert use_get_cases_metrics to react query * Refresh metrics and statuses * Show loading when updating cases * Create query key builder * Improve refreshing logic * Improve delete messages * Fix types and tests * Improvements * PR feedback * Fix bug * Fix i18n --- x-pack/plugins/cases/common/ui/types.ts | 12 +- .../cases/public/common/translations.ts | 8 +- .../public/common/use_cases_toast.test.tsx | 355 +++++++++++------- .../cases/public/common/use_cases_toast.tsx | 30 +- .../all_cases/all_cases_list.test.tsx | 204 +++------- .../components/all_cases/all_cases_list.tsx | 62 +-- .../all_cases/cases_metrics.test.tsx | 41 +- .../components/all_cases/cases_metrics.tsx | 25 +- .../public/components/all_cases/columns.tsx | 82 ++-- .../components/all_cases/index.test.tsx | 17 +- .../public/components/all_cases/table.tsx | 9 +- .../all_cases/table_filters.test.tsx | 9 - .../components/all_cases/table_filters.tsx | 15 +- .../components/all_cases/translations.ts | 41 +- .../all_cases/use_is_loading_cases.tsx | 20 + .../all_cases/use_on_refresh_cases.test.tsx | 36 ++ .../all_cases/use_on_refresh_cases.tsx | 28 ++ .../components/all_cases/utility_bar.tsx | 128 +++---- .../public/components/bulk_actions/index.tsx | 2 +- .../case_action_bar/actions.test.tsx | 46 +-- .../components/case_action_bar/actions.tsx | 43 ++- .../components/case_view/index.test.tsx | 8 +- .../use_on_refresh_case_view_page.tsx | 7 +- .../components/confirm_delete_case/index.tsx | 17 +- .../confirm_delete_case/translations.ts | 6 - .../cases/public/containers/__mocks__/api.ts | 7 +- .../cases/public/containers/api.test.tsx | 8 +- x-pack/plugins/cases/public/containers/api.ts | 6 +- .../containers/configure/use_action_types.tsx | 4 +- .../containers/configure/use_connectors.tsx | 4 +- .../cases/public/containers/constants.ts | 45 ++- .../cases/public/containers/translations.ts | 45 +-- .../containers/use_bulk_update_case.test.tsx | 151 +++----- .../containers/use_bulk_update_case.tsx | 171 ++------- .../containers/use_delete_cases.test.tsx | 155 +++----- .../public/containers/use_delete_cases.tsx | 156 ++------ .../containers/use_get_action_license.tsx | 4 +- .../cases/public/containers/use_get_case.tsx | 4 +- .../containers/use_get_case_metrics.tsx | 4 +- .../containers/use_get_case_user_actions.tsx | 4 +- .../cases/public/containers/use_get_cases.tsx | 4 +- .../containers/use_get_cases_metrics.test.tsx | 126 ++----- .../containers/use_get_cases_metrics.tsx | 93 ++--- .../containers/use_get_cases_status.test.tsx | 108 ++---- .../containers/use_get_cases_status.tsx | 99 ++--- .../cases/public/containers/use_get_tags.tsx | 7 +- .../use_bulk_get_user_profiles.ts | 4 +- .../use_get_current_user_profile.ts | 4 +- .../use_suggest_user_profiles.ts | 8 +- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 52 files changed, 977 insertions(+), 1504 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/all_cases/use_is_loading_cases.tsx create mode 100644 x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx create mode 100644 x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.tsx diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 9af253fc9559e..65a7fec902f7b 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -82,6 +82,7 @@ export type Case = Omit, 'comments'> & { comments export type Cases = Omit, 'cases'> & { cases: Case[] }; export type CasesStatus = SnakeToCamelCase; export type CasesMetrics = SnakeToCamelCase; +export type CaseUpdateRequest = SnakeToCamelCase; export interface ResolvedCase { case: Case; @@ -133,12 +134,6 @@ export interface ApiProps { signal: AbortSignal; } -export interface BulkUpdateStatus { - status: string; - id: string; - version: string; -} - export interface ActionLicense { id: string; name: string; @@ -147,11 +142,6 @@ export interface ActionLicense { enabledInLicense: boolean; } -export interface DeleteCase { - id: string; - title: string; -} - export interface FieldMappings { id: string; title?: string; diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts index 1dbc271e9be49..9748339878222 100644 --- a/x-pack/plugins/cases/public/common/translations.ts +++ b/x-pack/plugins/cases/public/common/translations.ts @@ -18,7 +18,7 @@ export const CANCEL = i18n.translate('xpack.cases.caseView.cancel', { export const DELETE_CASE = (quantity: number = 1) => i18n.translate('xpack.cases.confirmDeleteCase.deleteCase', { values: { quantity }, - defaultMessage: `Delete {quantity, plural, =1 {case} other {cases}}`, + defaultMessage: `Delete {quantity, plural, =1 {case} other {{quantity} cases}}`, }); export const NAME = i18n.translate('xpack.cases.caseView.name', { @@ -296,3 +296,9 @@ export const READ_ACTIONS_PERMISSIONS_ERROR_MSG = i18n.translate( 'You do not have permission to view connectors. If you would like to view connectors, contact your Kibana administrator.', } ); + +export const DELETED_CASES = (totalCases: number) => + i18n.translate('xpack.cases.containers.deletedCases', { + values: { totalCases }, + defaultMessage: 'Deleted {totalCases, plural, =1 {case} other {{totalCases} cases}}', + }); diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx index e8661387e1e64..cfa2d3a6e0052 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx @@ -23,6 +23,7 @@ const useKibanaMock = useKibana as jest.Mocked; describe('Use cases toast hook', () => { const successMock = jest.fn(); + const errorMock = jest.fn(); const getUrlForApp = jest.fn().mockReturnValue(`/app/cases/${mockCase.id}`); const navigateToUrl = jest.fn(); @@ -51,6 +52,7 @@ describe('Use cases toast hook', () => { useToastsMock.mockImplementation(() => { return { addSuccess: successMock, + addError: errorMock, }; }); @@ -63,159 +65,260 @@ describe('Use cases toast hook', () => { }; }); - describe('Toast hook', () => { - it('should create a success toast when invoked with a case', () => { - const { result } = renderHook( - () => { - return useCasesToast(); - }, - { wrapper: TestProviders } - ); - result.current.showSuccessAttach({ - theCase: mockCase, + describe('showSuccessAttach', () => { + describe('Toast hook', () => { + it('should create a success toast when invoked with a case', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ + theCase: mockCase, + }); + expect(successMock).toHaveBeenCalled(); }); - expect(successMock).toHaveBeenCalled(); }); - }); - describe('toast title', () => { - it('should create a success toast when invoked with a case and a custom title', () => { - const { result } = renderHook( - () => { - return useCasesToast(); - }, - { wrapper: TestProviders } - ); - result.current.showSuccessAttach({ theCase: mockCase, title: 'Custom title' }); - validateTitle('Custom title'); - }); + describe('toast title', () => { + it('should create a success toast when invoked with a case and a custom title', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ theCase: mockCase, title: 'Custom title' }); + validateTitle('Custom title'); + }); - it('should display the alert sync title when called with an alert attachment (1 alert)', () => { - const { result } = renderHook( - () => { - return useCasesToast(); - }, - { wrapper: TestProviders } - ); - result.current.showSuccessAttach({ - theCase: mockCase, - attachments: [alertComment as SupportedCaseAttachment], + it('should display the alert sync title when called with an alert attachment (1 alert)', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ + theCase: mockCase, + attachments: [alertComment as SupportedCaseAttachment], + }); + validateTitle('An alert was added to "Another horrible breach!!'); + }); + + it('should display the alert sync title when called with an alert attachment (multiple alerts)', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + const alert = { + ...alertComment, + alertId: ['1234', '54321'], + } as SupportedCaseAttachment; + + result.current.showSuccessAttach({ + theCase: mockCase, + attachments: [alert], + }); + validateTitle('Alerts were added to "Another horrible breach!!'); + }); + + it('should display a generic title when called with a non-alert attachament', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ + theCase: mockCase, + attachments: [basicComment as SupportedCaseAttachment], + }); + validateTitle('Another horrible breach!! has been updated'); }); - validateTitle('An alert was added to "Another horrible breach!!'); }); - it('should display the alert sync title when called with an alert attachment (multiple alerts)', () => { - const { result } = renderHook( - () => { - return useCasesToast(); - }, - { wrapper: TestProviders } - ); - const alert = { - ...alertComment, - alertId: ['1234', '54321'], - } as SupportedCaseAttachment; - - result.current.showSuccessAttach({ - theCase: mockCase, - attachments: [alert], + describe('Toast content', () => { + let appMockRender: AppMockRenderer; + const onViewCaseClick = jest.fn(); + beforeEach(() => { + appMockRender = createAppMockRenderer(); + onViewCaseClick.mockReset(); + }); + + it('should create a success toast when invoked with a case and a custom content', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ theCase: mockCase, content: 'Custom content' }); + validateContent('Custom content'); + }); + + it('renders an alert-specific content when called with an alert attachment and sync on', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ + theCase: mockCase, + attachments: [alertComment as SupportedCaseAttachment], + }); + validateContent('The alert statuses are synched with the case status.'); + }); + + it('renders empty content when called with an alert attachment and sync off', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + result.current.showSuccessAttach({ + theCase: { ...mockCase, settings: { ...mockCase.settings, syncAlerts: false } }, + attachments: [alertComment as SupportedCaseAttachment], + }); + validateContent('View case'); + }); + + it('renders a correct successful message content', () => { + const result = appMockRender.render( + + ); + expect(result.getByTestId('toaster-content-sync-text')).toHaveTextContent('my content'); + expect(result.getByTestId('toaster-content-case-view-link')).toHaveTextContent('View case'); + expect(onViewCaseClick).not.toHaveBeenCalled(); + }); + + it('renders a correct successful message without content', () => { + const result = appMockRender.render( + + ); + expect(result.queryByTestId('toaster-content-sync-text')).toBeFalsy(); + expect(result.getByTestId('toaster-content-case-view-link')).toHaveTextContent('View case'); + expect(onViewCaseClick).not.toHaveBeenCalled(); + }); + + it('Calls the onViewCaseClick when clicked', () => { + const result = appMockRender.render( + + ); + userEvent.click(result.getByTestId('toaster-content-case-view-link')); + expect(onViewCaseClick).toHaveBeenCalled(); }); - validateTitle('Alerts were added to "Another horrible breach!!'); }); - it('should display a generic title when called with a non-alert attachament', () => { - const { result } = renderHook( - () => { - return useCasesToast(); - }, - { wrapper: TestProviders } - ); - result.current.showSuccessAttach({ - theCase: mockCase, - attachments: [basicComment as SupportedCaseAttachment], + describe('Toast navigation', () => { + const tests = Object.entries(OWNER_INFO).map(([owner, ownerInfo]) => [ + owner, + ownerInfo.appId, + ]); + + it.each(tests)('should navigate correctly with owner %s and appId %s', (owner, appId) => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + + result.current.showSuccessAttach({ + theCase: { ...mockCase, owner }, + title: 'Custom title', + }); + + navigateToCase(); + + expect(getUrlForApp).toHaveBeenCalledWith(appId, { + deepLinkId: 'cases', + path: '/mock-id', + }); + + expect(navigateToUrl).toHaveBeenCalledWith('/app/cases/mock-id'); + }); + + it('navigates to the current app if the owner is invalid', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + + result.current.showSuccessAttach({ + theCase: { ...mockCase, owner: 'in-valid' }, + title: 'Custom title', + }); + + navigateToCase(); + + expect(getUrlForApp).toHaveBeenCalledWith('testAppId', { + deepLinkId: 'cases', + path: '/mock-id', + }); }); - validateTitle('Another horrible breach!! has been updated'); }); }); - describe('Toast content', () => { - let appMockRender: AppMockRenderer; - const onViewCaseClick = jest.fn(); - beforeEach(() => { - appMockRender = createAppMockRenderer(); - onViewCaseClick.mockReset(); - }); + describe('showErrorToast', () => { + it('should show an error toast', () => { + const error = new Error('showErrorToast: an error occurred'); - it('should create a success toast when invoked with a case and a custom content', () => { const { result } = renderHook( () => { return useCasesToast(); }, { wrapper: TestProviders } ); - result.current.showSuccessAttach({ theCase: mockCase, content: 'Custom content' }); - validateContent('Custom content'); + + result.current.showErrorToast(error); + + expect(errorMock).toHaveBeenCalledWith(error, { title: error.message }); }); - it('renders an alert-specific content when called with an alert attachment and sync on', () => { + it('should override the title', () => { + const error = new Error('showErrorToast: an error occurred'); + const { result } = renderHook( () => { return useCasesToast(); }, { wrapper: TestProviders } ); - result.current.showSuccessAttach({ - theCase: mockCase, - attachments: [alertComment as SupportedCaseAttachment], - }); - validateContent('The alert statuses are synched with the case status.'); + + result.current.showErrorToast(error, { title: 'my title' }); + + expect(errorMock).toHaveBeenCalledWith(error, { title: 'my title' }); }); - it('renders empty content when called with an alert attachment and sync off', () => { + it('should not show an error toast if the error is AbortError', () => { + const error = new Error('showErrorToast: an error occurred'); + error.name = 'AbortError'; + const { result } = renderHook( () => { return useCasesToast(); }, { wrapper: TestProviders } ); - result.current.showSuccessAttach({ - theCase: { ...mockCase, settings: { ...mockCase.settings, syncAlerts: false } }, - attachments: [alertComment as SupportedCaseAttachment], - }); - validateContent('View case'); - }); - it('renders a correct successful message content', () => { - const result = appMockRender.render( - - ); - expect(result.getByTestId('toaster-content-sync-text')).toHaveTextContent('my content'); - expect(result.getByTestId('toaster-content-case-view-link')).toHaveTextContent('View case'); - expect(onViewCaseClick).not.toHaveBeenCalled(); - }); - - it('renders a correct successful message without content', () => { - const result = appMockRender.render( - - ); - expect(result.queryByTestId('toaster-content-sync-text')).toBeFalsy(); - expect(result.getByTestId('toaster-content-case-view-link')).toHaveTextContent('View case'); - expect(onViewCaseClick).not.toHaveBeenCalled(); - }); + result.current.showErrorToast(error); - it('Calls the onViewCaseClick when clicked', () => { - const result = appMockRender.render( - - ); - userEvent.click(result.getByTestId('toaster-content-case-view-link')); - expect(onViewCaseClick).toHaveBeenCalled(); + expect(errorMock).not.toHaveBeenCalled(); }); - }); - describe('Toast navigation', () => { - const tests = Object.entries(OWNER_INFO).map(([owner, ownerInfo]) => [owner, ownerInfo.appId]); + it('should show the body message if it is a ServerError', () => { + const error = new Error('showErrorToast: an error occurred'); + // @ts-expect-error: need to create a ServerError + error.body = { message: 'message error' }; - it.each(tests)('should navigate correctly with owner %s and appId %s', (owner, appId) => { const { result } = renderHook( () => { return useCasesToast(); @@ -223,22 +326,16 @@ describe('Use cases toast hook', () => { { wrapper: TestProviders } ); - result.current.showSuccessAttach({ - theCase: { ...mockCase, owner }, - title: 'Custom title', - }); + result.current.showErrorToast(error); - navigateToCase(); - - expect(getUrlForApp).toHaveBeenCalledWith(appId, { - deepLinkId: 'cases', - path: '/mock-id', + expect(errorMock).toHaveBeenCalledWith(new Error('message error'), { + title: 'message error', }); - - expect(navigateToUrl).toHaveBeenCalledWith('/app/cases/mock-id'); }); + }); - it('navigates to the current app if the owner is invalid', () => { + describe('showSuccessToast', () => { + it('should show a success toast', () => { const { result } = renderHook( () => { return useCasesToast(); @@ -246,17 +343,9 @@ describe('Use cases toast hook', () => { { wrapper: TestProviders } ); - result.current.showSuccessAttach({ - theCase: { ...mockCase, owner: 'in-valid' }, - title: 'Custom title', - }); - - navigateToCase(); + result.current.showSuccessToast('my title'); - expect(getUrlForApp).toHaveBeenCalledWith('testAppId', { - deepLinkId: 'cases', - path: '/mock-id', - }); + expect(successMock).toHaveBeenCalledWith('my title'); }); }); }); diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx index 7d445a4edffac..42a358897bc62 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { ErrorToastOptions } from '@kbn/core/public'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; @@ -12,7 +13,7 @@ import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { Case, CommentType } from '../../common'; import { useKibana, useToasts } from './lib/kibana'; import { generateCaseViewPath } from './navigation'; -import { CaseAttachmentsWithoutOwner } from '../types'; +import { CaseAttachmentsWithoutOwner, ServerError } from '../types'; import { CASE_ALERT_SUCCESS_SYNC_TEXT, CASE_ALERT_SUCCESS_TOAST, @@ -98,6 +99,25 @@ function getToastContent({ const isValidOwner = (owner: string): owner is keyof typeof OWNER_INFO => Object.keys(OWNER_INFO).includes(owner); +const isServerError = (error: Error | ServerError): error is ServerError => + Object.hasOwn(error, 'body'); + +const getError = (error: Error | ServerError): Error => { + if (isServerError(error)) { + return new Error(error.body?.message); + } + + return error; +}; + +const getErrorMessage = (error: Error | ServerError): string => { + if (isServerError(error)) { + return error.body?.message ?? ''; + } + + return error.message; +}; + export const useCasesToast = () => { const { appId } = useCasesContext(); const { getUrlForApp, navigateToUrl } = useKibana().services.application; @@ -141,6 +161,14 @@ export const useCasesToast = () => { ), }); }, + showErrorToast: (error: Error | ServerError, opts?: ErrorToastOptions) => { + if (error.name !== 'AbortError') { + toasts.addError(getError(error), { title: getErrorMessage(error), ...opts }); + } + }, + showSuccessToast: (title: string) => { + toasts.addSuccess(title); + }, }; }; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index a504bf251ff20..1cc3a5f0263e5 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; import moment from 'moment-timezone'; -import { act, render, waitFor, screen } from '@testing-library/react'; +import { render, waitFor, screen, act } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -20,15 +20,12 @@ import { noDeleteCasesPermissions, TestProviders, } from '../../common/mock'; -import { casesStatus, useGetCasesMockState, mockCase, connectorsMock } from '../../containers/mock'; +import { useGetCasesMockState, connectorsMock } from '../../containers/mock'; import { StatusAll } from '../../../common/ui/types'; import { CaseSeverity, CaseStatuses } from '../../../common/api'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; -import { useDeleteCases } from '../../containers/use_delete_cases'; -import { useGetCasesStatus } from '../../containers/use_get_cases_status'; -import { useUpdateCases } from '../../containers/use_bulk_update_case'; import { useKibana } from '../../common/lib/kibana'; import { AllCasesList } from './all_cases_list'; import { CasesColumns, GetCasesColumn, useCasesColumns } from './columns'; @@ -37,7 +34,6 @@ import { registerConnectorsToMockActionRegistry } from '../../common/mock/regist import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; import { waitForComponentToUpdate } from '../../common/test_utils'; import { useCreateAttachments } from '../../containers/use_create_attachments'; -import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics'; import { useGetConnectors } from '../../containers/configure/use_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { useUpdateCase } from '../../containers/use_update_case'; @@ -46,13 +42,10 @@ import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; import { useLicense } from '../../common/use_license'; +import * as api from '../../containers/api'; jest.mock('../../containers/use_create_attachments'); -jest.mock('../../containers/use_bulk_update_case'); -jest.mock('../../containers/use_delete_cases'); jest.mock('../../containers/use_get_cases'); -jest.mock('../../containers/use_get_cases_status'); -jest.mock('../../containers/use_get_cases_metrics'); jest.mock('../../containers/use_get_action_license'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/user_profiles/use_get_current_user_profile'); @@ -66,11 +59,7 @@ jest.mock('../app/use_available_owners', () => ({ jest.mock('../../containers/use_update_case'); jest.mock('../../common/use_license'); -const useDeleteCasesMock = useDeleteCases as jest.Mock; const useGetCasesMock = useGetCases as jest.Mock; -const useGetCasesStatusMock = useGetCasesStatus as jest.Mock; -const useGetCasesMetricsMock = useGetCasesMetrics as jest.Mock; -const useUpdateCasesMock = useUpdateCases as jest.Mock; const useGetTagsMock = useGetTags as jest.Mock; const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; @@ -92,13 +81,7 @@ const mockKibana = () => { }; describe('AllCasesListGeneric', () => { - const dispatchResetIsDeleted = jest.fn(); - const dispatchResetIsUpdated = jest.fn(); - const handleOnDeleteConfirm = jest.fn(); - const handleToggleModal = jest.fn(); const refetchCases = jest.fn(); - const updateBulkStatus = jest.fn(); - const fetchCasesStatus = jest.fn(); const onRowClick = jest.fn(); const updateCaseProperty = jest.fn(); @@ -113,36 +96,6 @@ describe('AllCasesListGeneric', () => { refetch: refetchCases, }; - const defaultDeleteCases = { - dispatchResetIsDeleted, - handleOnDeleteConfirm, - handleToggleModal, - isDeleted: false, - isDisplayConfirmDeleteModal: false, - isLoading: false, - }; - - const defaultCasesStatus = { - ...casesStatus, - fetchCasesStatus, - isError: false, - isLoading: false, - }; - - const defaultCasesMetrics = { - mttr: 5, - isLoading: false, - fetchCasesMetrics: jest.fn(), - }; - - const defaultUpdateCases = { - isUpdated: false, - isLoading: false, - isError: false, - dispatchResetIsUpdated, - updateBulkStatus, - }; - const defaultColumnArgs = { caseDetailsNavigation: { href: jest.fn(), @@ -167,11 +120,7 @@ describe('AllCasesListGeneric', () => { beforeEach(() => { jest.clearAllMocks(); appMockRenderer = createAppMockRenderer(); - useUpdateCasesMock.mockReturnValue(defaultUpdateCases); useGetCasesMock.mockReturnValue(defaultGetCases); - useDeleteCasesMock.mockReturnValue(defaultDeleteCases); - useGetCasesStatusMock.mockReturnValue(defaultCasesStatus); - useGetCasesMetricsMock.mockReturnValue(defaultCasesMetrics); useGetTagsMock.mockReturnValue({ data: ['coke', 'pepsi'], refetch: jest.fn() }); useGetCurrentUserProfileMock.mockReturnValue({ data: userProfiles[0], isLoading: false }); useBulkGetUserProfilesMock.mockReturnValue({ data: userProfilesMap }); @@ -368,43 +317,48 @@ describe('AllCasesListGeneric', () => { expect(wrapper.find('[data-test-subj="cases-count-stats"]')).toBeTruthy(); }); - it.skip('Bulk delete', async () => { - useDeleteCasesMock - .mockReturnValueOnce({ - ...defaultDeleteCases, - isDisplayConfirmDeleteModal: false, - }) - .mockReturnValue({ - ...defaultDeleteCases, - isDisplayConfirmDeleteModal: true, - }); + it('Bulk delete', async () => { + const deleteCasesSpy = jest.spyOn(api, 'deleteCases'); + const result = appMockRenderer.render(); - const wrapper = mount( - - - - ); + act(() => { + userEvent.click(result.getByTestId('checkboxSelectAll')); + }); - wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); - wrapper.find('[data-test-subj="cases-bulk-delete-button"]').first().simulate('click'); + act(() => { + userEvent.click(result.getByText('Bulk actions')); + }); - wrapper - .find( - '[data-test-subj="confirm-delete-case-modal"] [data-test-subj="confirmModalConfirmButton"]' - ) - .last() - .simulate('click'); + await waitForEuiPopoverOpen(); + + act(() => { + userEvent.click(result.getByTestId('cases-bulk-delete-button'), undefined, { + skipPointerEventsCheck: true, + }); + }); await waitFor(() => { - expect(handleToggleModal).toBeCalled(); + expect(result.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual([ - ...useGetCasesMockState.data.cases.map(({ id, title }) => ({ id, title })), - { - id: mockCase.id, - title: mockCase.title, - }, - ]); + act(() => { + userEvent.click(result.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(deleteCasesSpy).toHaveBeenCalledWith( + [ + 'basic-case-id', + '1', + '2', + '3', + '4', + 'case-with-alerts-id', + 'case-with-alerts-syncoff-id', + 'case-with-registered-attachment', + ], + expect.anything() + ); }); }); @@ -431,6 +385,8 @@ describe('AllCasesListGeneric', () => { }); it('Bulk close status update', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + const result = appMockRenderer.render(); const theCase = useGetCasesMockState.data.cases[0]; userEvent.click(result.getByTestId('case-status-filter')); @@ -441,10 +397,16 @@ describe('AllCasesListGeneric', () => { await waitForEuiPopoverOpen(); userEvent.click(result.getByTestId('cases-bulk-close-button')); await waitFor(() => {}); - expect(updateBulkStatus).toBeCalledWith([theCase], CaseStatuses.closed); + + expect(updateCasesSpy).toBeCalledWith( + [{ id: theCase.id, version: theCase.version, status: CaseStatuses.closed }], + expect.anything() + ); }); it('Bulk open status update', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + const result = appMockRenderer.render(); const theCase = useGetCasesMockState.data.cases[0]; userEvent.click(result.getByTestId('case-status-filter')); @@ -455,10 +417,16 @@ describe('AllCasesListGeneric', () => { await waitForEuiPopoverOpen(); userEvent.click(result.getByTestId('cases-bulk-open-button')); await waitFor(() => {}); - expect(updateBulkStatus).toBeCalledWith([theCase], CaseStatuses.open); + + expect(updateCasesSpy).toBeCalledWith( + [{ id: theCase.id, version: theCase.version, status: CaseStatuses.open }], + expect.anything() + ); }); it('Bulk in-progress status update', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + const result = appMockRenderer.render(); const theCase = useGetCasesMockState.data.cases[0]; userEvent.click(result.getByTestId('case-status-filter')); @@ -469,43 +437,11 @@ describe('AllCasesListGeneric', () => { await waitForEuiPopoverOpen(); userEvent.click(result.getByTestId('cases-bulk-in-progress-button')); await waitFor(() => {}); - expect(updateBulkStatus).toBeCalledWith([theCase], CaseStatuses['in-progress']); - }); - it('isDeleted is true, refetch', async () => { - useDeleteCasesMock.mockReturnValue({ - ...defaultDeleteCases, - isDeleted: true, - }); - - mount( - - - + expect(updateCasesSpy).toBeCalledWith( + [{ id: theCase.id, version: theCase.version, status: CaseStatuses['in-progress'] }], + expect.anything() ); - await waitFor(() => { - expect(refetchCases).toBeCalled(); - // expect(fetchCasesStatus).toBeCalled(); - expect(dispatchResetIsDeleted).toBeCalled(); - }); - }); - - it('isUpdated is true, refetch', async () => { - useUpdateCasesMock.mockReturnValue({ - ...defaultUpdateCases, - isUpdated: true, - }); - - mount( - - - - ); - await waitFor(() => { - expect(refetchCases).toBeCalled(); - // expect(fetchCasesStatus).toBeCalled(); - expect(dispatchResetIsUpdated).toBeCalled(); - }); }); it('should not render table utility bar when isSelectorView=true', async () => { @@ -769,28 +705,10 @@ describe('AllCasesListGeneric', () => { expect(wrapper.find('[data-test-subj="status-badge-in-progress"]').exists()).toBeTruthy(); }); - it('should call doRefresh if provided', async () => { - const doRefresh = jest.fn(); - - const wrapper = mount( - - - - ); - - await act(async () => { - wrapper.find('[data-test-subj="all-cases-refresh"] button').first().simulate('click'); - }); - - expect(doRefresh).toHaveBeenCalled(); - }); - it('shows Solution column if there are no set owners', async () => { - const doRefresh = jest.fn(); - const wrapper = mount( - + ); @@ -801,11 +719,9 @@ describe('AllCasesListGeneric', () => { }); it('hides Solution column if there is a set owner', async () => { - const doRefresh = jest.fn(); - const wrapper = mount( - + ); diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index 0bc5e9b14b29a..c7b2d4895f94a 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -10,7 +10,6 @@ import { EuiProgress, EuiBasicTable, EuiTableSelectionType } from '@elastic/eui' import { difference, head, isEmpty } from 'lodash/fp'; import styled, { css } from 'styled-components'; -import { useQueryClient } from '@tanstack/react-query'; import { Case, CaseStatusWithAllStatus, @@ -38,11 +37,8 @@ import { } from '../../containers/use_get_cases'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; -import { - USER_PROFILES_BULK_GET_CACHE_KEY, - USER_PROFILES_CACHE_KEY, -} from '../../containers/constants'; import { getAllPermissionsExceptFrom } from '../../utils/permissions'; +import { useIsLoadingCases } from './use_is_loading_cases'; const ProgressLoader = styled(EuiProgress)` ${({ $isShow }: { $isShow: boolean }) => @@ -64,14 +60,13 @@ export interface AllCasesListProps { hiddenStatuses?: CaseStatusWithAllStatus[]; isSelectorView?: boolean; onRowClick?: (theCase?: Case) => void; - doRefresh?: () => void; } export const AllCasesList = React.memo( - ({ hiddenStatuses = [], isSelectorView = false, onRowClick, doRefresh }) => { + ({ hiddenStatuses = [], isSelectorView = false, onRowClick }) => { const { owner, permissions } = useCasesContext(); const availableSolutions = useAvailableCasesOwners(getAllPermissionsExceptFrom('delete')); - const [refresh, setRefresh] = useState(0); + const isLoading = useIsLoadingCases(); const hasOwner = !!owner.length; @@ -80,19 +75,15 @@ export const AllCasesList = React.memo( ...(!isEmpty(hiddenStatuses) && firstAvailableStatus && { status: firstAvailableStatus }), owner: hasOwner ? owner : availableSolutions, }; + const [filterOptions, setFilterOptions] = useState({ ...DEFAULT_FILTER_OPTIONS, ...initialFilterOptions, }); const [queryParams, setQueryParams] = useState(DEFAULT_QUERY_PARAMS); const [selectedCases, setSelectedCases] = useState([]); - const queryClient = useQueryClient(); - const { - data = initialData, - isFetching: isLoadingCases, - refetch: refetchCases, - } = useGetCases({ + const { data = initialData, isFetching: isLoadingCases } = useGetCases({ filterOptions, queryParams, }); @@ -126,41 +117,13 @@ export const AllCasesList = React.memo( [queryParams.sortField, queryParams.sortOrder] ); - const filterRefetch = useRef<() => void>(); const tableRef = useRef(null); - const [isLoading, handleIsLoading] = useState(false); - - const setFilterRefetch = useCallback( - (refetchFilter: () => void) => { - filterRefetch.current = refetchFilter; - }, - [filterRefetch] - ); const deselectCases = useCallback(() => { setSelectedCases([]); tableRef.current?.setSelection([]); }, [setSelectedCases]); - const refreshCases = useCallback( - (dataRefresh = true) => { - deselectCases(); - if (dataRefresh) { - refetchCases(); - queryClient.refetchQueries([USER_PROFILES_CACHE_KEY, USER_PROFILES_BULK_GET_CACHE_KEY]); - - setRefresh((currRefresh: number) => currRefresh + 1); - } - if (doRefresh) { - doRefresh(); - } - if (filterRefetch.current != null) { - filterRefetch.current(); - } - }, - [deselectCases, doRefresh, queryClient, refetchCases] - ); - const tableOnChangeCallback = useCallback( ({ page, sort }: EuiBasicTableOnChange) => { let newQueryParams = queryParams; @@ -179,9 +142,9 @@ export const AllCasesList = React.memo( }; } setQueryParams(newQueryParams); - refreshCases(false); + deselectCases(); }, - [queryParams, refreshCases, setQueryParams] + [queryParams, deselectCases, setQueryParams] ); const onFilterChangedCallback = useCallback( @@ -229,9 +192,8 @@ export const AllCasesList = React.memo( } : {}), })); - refreshCases(false); }, - [deselectCases, refreshCases, hasOwner, availableSolutions, owner] + [deselectCases, hasOwner, availableSolutions, owner] ); /** @@ -244,8 +206,6 @@ export const AllCasesList = React.memo( filterStatus: filterOptions.status ?? StatusAll, userProfiles: userProfiles ?? new Map(), currentUserProfile, - handleIsLoading, - refreshCases, isSelectorView, connectors, onRowClick, @@ -286,7 +246,7 @@ export const AllCasesList = React.memo( className="essentialAnimation" $isShow={isLoading || isLoadingCases} /> - {!isSelectorView ? : null} + {!isSelectorView ? : null} ( owner: filterOptions.owner, severity: filterOptions.severity, }} - setFilterRefetch={setFilterRefetch} hiddenStatuses={hiddenStatuses} displayCreateCaseButton={isSelectorView} onCreateCasePressed={onRowClick} @@ -315,20 +274,19 @@ export const AllCasesList = React.memo( data={data} filterOptions={filterOptions} goToCreateCase={onRowClick} - handleIsLoading={handleIsLoading} isCasesLoading={isLoadingCases} isCommentUpdating={isLoadingCases} isDataEmpty={isDataEmpty} isSelectorView={isSelectorView} onChange={tableOnChangeCallback} pagination={pagination} - refreshCases={refreshCases} selectedCases={selectedCases} selection={euiBasicTableSelectionProps} showActions={showActions} sorting={sorting} tableRef={tableRef} tableRowProps={tableRowProps} + deselectCases={deselectCases} /> ); diff --git a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.test.tsx b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.test.tsx index 6141527d59f42..323f9deead3eb 100644 --- a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.test.tsx @@ -5,46 +5,29 @@ * 2.0. */ -import { within } from '@testing-library/dom'; +import { waitFor, within } from '@testing-library/react'; import React from 'react'; import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; -import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics'; -import { useGetCasesStatus } from '../../containers/use_get_cases_status'; import { CasesMetrics } from './cases_metrics'; -jest.mock('../../containers/use_get_cases_metrics'); -jest.mock('../../containers/use_get_cases_status'); - -const useGetCasesMetricsMock = useGetCasesMetrics as jest.Mock; -const useGetCasesStatusMock = useGetCasesStatus as jest.Mock; +jest.mock('../../api'); describe('Cases metrics', () => { - useGetCasesStatusMock.mockReturnValue({ - countOpenCases: 2, - countInProgressCases: 3, - countClosedCases: 4, - isLoading: false, - fetchCasesStatus: jest.fn(), - }); - useGetCasesMetricsMock.mockReturnValue({ - // 600 seconds = 10m - mttr: 600, - isLoading: false, - fetchCasesMetrics: jest.fn(), - }); - let appMockRenderer: AppMockRenderer; beforeEach(() => { appMockRenderer = createAppMockRenderer(); }); - it('renders the correct stats', () => { - const result = appMockRenderer.render(); - expect(result.getByTestId('cases-metrics-stats')).toBeTruthy(); - expect(within(result.getByTestId('openStatsHeader')).getByText(2)).toBeTruthy(); - expect(within(result.getByTestId('inProgressStatsHeader')).getByText(3)).toBeTruthy(); - expect(within(result.getByTestId('closedStatsHeader')).getByText(4)).toBeTruthy(); - expect(within(result.getByTestId('mttrStatsHeader')).getByText('10m')).toBeTruthy(); + it('renders the correct stats', async () => { + const result = appMockRenderer.render(); + + await waitFor(() => { + expect(result.getByTestId('cases-metrics-stats')).toBeTruthy(); + expect(within(result.getByTestId('openStatsHeader')).getByText(20)).toBeTruthy(); + expect(within(result.getByTestId('inProgressStatsHeader')).getByText(40)).toBeTruthy(); + expect(within(result.getByTestId('closedStatsHeader')).getByText(130)).toBeTruthy(); + expect(within(result.getByTestId('mttrStatsHeader')).getByText('12s')).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx index e75f0fd8a6708..ca6434aa11c26 100644 --- a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FunctionComponent, useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { EuiDescriptionList, EuiFlexGroup, @@ -22,9 +22,6 @@ import { StatusStats } from '../status/status_stats'; import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics'; import { ATTC_DESCRIPTION, ATTC_STAT } from './translations'; -interface CountProps { - refresh?: number; -} const MetricsFlexGroup = styled.div` ${({ theme }) => css` .euiFlexGroup { @@ -40,29 +37,23 @@ const MetricsFlexGroup = styled.div` `} `; -export const CasesMetrics: FunctionComponent = ({ refresh }) => { +export const CasesMetrics: React.FC = () => { const { - countOpenCases, - countInProgressCases, - countClosedCases, + data: { countOpenCases, countInProgressCases, countClosedCases } = { + countOpenCases: 0, + countInProgressCases: 0, + countClosedCases: 0, + }, isLoading: isCasesStatusLoading, - fetchCasesStatus, } = useGetCasesStatus(); - const { mttr, isLoading: isCasesMetricsLoading, fetchCasesMetrics } = useGetCasesMetrics(); + const { data: { mttr } = { mttr: 0 }, isLoading: isCasesMetricsLoading } = useGetCasesMetrics(); const mttrValue = useMemo( () => (mttr != null ? prettyMilliseconds(mttr * 1000, { compact: true, verbose: false }) : '-'), [mttr] ); - useEffect(() => { - if (refresh != null) { - fetchCasesStatus(); - fetchCasesMetrics(); - } - }, [fetchCasesMetrics, fetchCasesStatus, refresh]); - return ( diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx index 00e9fb077589a..948abdbbcd2f3 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiBadgeGroup, EuiBadge, @@ -24,7 +24,7 @@ import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; import { UserProfileWithAvatar } from '@kbn/user-profile-components'; -import { Case, DeleteCase, UpdateByKey } from '../../../common/ui/types'; +import { Case, UpdateByKey } from '../../../common/ui/types'; import { CaseStatuses, ActionConnector, CaseSeverity } from '../../../common/api'; import { OWNER_INFO } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; @@ -49,6 +49,7 @@ import { getUsernameDataTestSubj } from '../user_profiles/data_test_subject'; import { CurrentUserProfile } from '../types'; import { SmallUserAvatar } from '../user_profiles/small_user_avatar'; import { useCasesFeatures } from '../../common/use_cases_features'; +import { useRefreshCases } from './use_on_refresh_cases'; export type CasesColumns = | EuiTableActionsColumnType @@ -103,8 +104,6 @@ export interface GetCasesColumn { filterStatus: string; userProfiles: Map; currentUserProfile: CurrentUserProfile; - handleIsLoading: (a: boolean) => void; - refreshCases?: (a?: boolean) => void; isSelectorView: boolean; connectors?: ActionConnector[]; onRowClick?: (theCase: Case) => void; @@ -115,41 +114,40 @@ export const useCasesColumns = ({ filterStatus, userProfiles, currentUserProfile, - handleIsLoading, - refreshCases, isSelectorView, connectors = [], onRowClick, showSolutionColumn, }: GetCasesColumn): CasesColumns[] => { - // Delete case - const { - dispatchResetIsDeleted, - handleOnDeleteConfirm, - handleToggleModal, - isDeleted, - isDisplayConfirmDeleteModal, - isLoading: isDeleting, - } = useDeleteCases(); - + const [isModalVisible, setIsModalVisible] = useState(false); + const { mutate: deleteCases } = useDeleteCases(); + const refreshCases = useRefreshCases(); const { isAlertsEnabled, caseAssignmentAuthorized } = useCasesFeatures(); const { permissions } = useCasesContext(); - - const [deleteThisCase, setDeleteThisCase] = useState({ - id: '', - title: '', - }); - + const [caseToBeDeleted, setCaseToBeDeleted] = useState(); const { updateCaseProperty, isLoading: isLoadingUpdateCase } = useUpdateCase(); - const toggleDeleteModal = useCallback( - (deleteCase: Case) => { - handleToggleModal(); - setDeleteThisCase({ id: deleteCase.id, title: deleteCase.title }); + const closeModal = useCallback(() => setIsModalVisible(false), []); + const openModal = useCallback(() => setIsModalVisible(true), []); + + const onDeleteAction = useCallback( + (theCase: Case) => { + openModal(); + setCaseToBeDeleted(theCase.id); }, - [handleToggleModal] + [openModal] ); + const onConfirmDeletion = useCallback(() => { + closeModal(); + if (caseToBeDeleted) { + deleteCases({ + caseIds: [caseToBeDeleted], + successToasterTitle: i18n.DELETED_CASES(1), + }); + } + }, [caseToBeDeleted, closeModal, deleteCases]); + const handleDispatchUpdate = useCallback( ({ updateKey, updateValue, caseData }: UpdateByKey) => { updateCaseProperty({ @@ -157,7 +155,7 @@ export const useCasesColumns = ({ updateValue, caseData, onSuccess: () => { - if (refreshCases != null) refreshCases(); + refreshCases(); }, }); }, @@ -167,9 +165,9 @@ export const useCasesColumns = ({ const actions = useMemo( () => getActions({ - deleteCaseOnClick: toggleDeleteModal, + deleteCaseOnClick: onDeleteAction, }), - [toggleDeleteModal] + [onDeleteAction] ); const assignCaseAction = useCallback( @@ -181,17 +179,6 @@ export const useCasesColumns = ({ [onRowClick] ); - useEffect(() => { - handleIsLoading(isDeleting || isLoadingUpdateCase); - }, [handleIsLoading, isDeleting, isLoadingUpdateCase]); - - useEffect(() => { - if (isDeleted) { - if (refreshCases != null) refreshCases(); - dispatchResetIsDeleted(); - } - }, [isDeleted, dispatchResetIsDeleted, refreshCases]); - return [ { name: i18n.NAME, @@ -421,12 +408,13 @@ export const useCasesColumns = ({ name: ( <> {i18n.ACTIONS} - + {isModalVisible ? ( + + ) : null} ), actions, diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index 6fd1556f7d4e1..55777dd5bd34c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -15,8 +15,7 @@ import { noCreateCasesPermissions, } from '../../common/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; -import { casesStatus, connectorsMock, useGetCasesMockState } from '../../containers/mock'; -import { useGetCasesStatus } from '../../containers/use_get_cases_status'; +import { connectorsMock, useGetCasesMockState } from '../../containers/mock'; import { useGetConnectors } from '../../containers/configure/use_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetCases } from '../../containers/use_get_cases'; @@ -33,13 +32,12 @@ jest.mock('../../containers/use_get_action_license', () => { jest.mock('../../containers/configure/use_connectors'); jest.mock('../../containers/api'); jest.mock('../../containers/use_get_cases'); -jest.mock('../../containers/use_get_cases_status'); jest.mock('../../containers/user_profiles/use_get_current_user_profile'); jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../api'); const useGetConnectorsMock = useGetConnectors as jest.Mock; const useGetCasesMock = useGetCases as jest.Mock; -const useGetCasesStatusMock = useGetCasesStatus as jest.Mock; const useGetActionLicenseMock = useGetActionLicense as jest.Mock; const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; @@ -49,7 +47,6 @@ describe('AllCases', () => { const setFilters = jest.fn(); const setQueryParams = jest.fn(); const setSelectedCases = jest.fn(); - const fetchCasesStatus = jest.fn(); const defaultGetCases = { ...useGetCasesMockState, @@ -59,13 +56,6 @@ describe('AllCases', () => { setSelectedCases, }; - const defaultCasesStatus = { - ...casesStatus, - fetchCasesStatus, - isError: false, - isLoading: false, - }; - const defaultActionLicense = { data: null, isLoading: false, @@ -75,7 +65,6 @@ describe('AllCases', () => { beforeAll(() => { (useGetTags as jest.Mock).mockReturnValue({ data: ['coke', 'pepsi'], refetch: jest.fn() }); useGetConnectorsMock.mockImplementation(() => ({ data: connectorsMock, isLoading: false })); - useGetCasesStatusMock.mockReturnValue(defaultCasesStatus); useGetActionLicenseMock.mockReturnValue(defaultActionLicense); useGetCasesMock.mockReturnValue(defaultGetCases); @@ -142,8 +131,6 @@ describe('AllCases', () => { }); it('should render the loading spinner when loading stats', async () => { - useGetCasesStatusMock.mockReturnValue({ ...defaultCasesStatus, isLoading: true }); - const result = appMockRender.render(); await waitFor(() => { diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index afe653a50e4db..208c26e47648c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -29,20 +29,19 @@ interface CasesTableProps { data: Cases; filterOptions: FilterOptions; goToCreateCase?: () => void; - handleIsLoading: (a: boolean) => void; isCasesLoading: boolean; isCommentUpdating: boolean; isDataEmpty: boolean; isSelectorView?: boolean; onChange: EuiBasicTableProps['onChange']; pagination: Pagination; - refreshCases: (a?: boolean) => void; selectedCases: Case[]; selection: EuiTableSelectionType; showActions: boolean; sorting: EuiBasicTableProps['sorting']; tableRef: MutableRefObject; tableRowProps: EuiBasicTableProps['rowProps']; + deselectCases: () => void; } const Div = styled.div` @@ -54,20 +53,19 @@ export const CasesTable: FunctionComponent = ({ data, filterOptions, goToCreateCase, - handleIsLoading, isCasesLoading, isCommentUpdating, isDataEmpty, isSelectorView, onChange, pagination, - refreshCases, selectedCases, selection, showActions, sorting, tableRef, tableRowProps, + deselectCases, }) => { const { permissions } = useCasesContext(); const { getCreateCaseUrl, navigateToCreateCase } = useCreateCaseNavigation(); @@ -93,9 +91,8 @@ export const CasesTable: FunctionComponent = ({ data={data} enableBulkActions={showActions} filterOptions={filterOptions} - handleIsLoading={handleIsLoading} selectedCases={selectedCases} - refreshCases={refreshCases} + deselectCases={deselectCases} /> { expect(onFilterChanged).toBeCalledWith({ status: CaseStatuses.closed }); }); - it('should call on load setFilterRefetch', () => { - mount( - - - - ); - expect(setFilterRefetch).toHaveBeenCalled(); - }); - it('should remove tag from selected tags when tag no longer exists', () => { const ourProps = { ...props, diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx index 5ab587ad31759..75fc22a9fee27 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx @@ -19,7 +19,6 @@ import { StatusFilter } from './status_filter'; import * as i18n from './translations'; import { SeverityFilter } from './severity_filter'; import { useGetTags } from '../../containers/use_get_tags'; -import { CASE_LIST_CACHE_KEY } from '../../containers/constants'; import { DEFAULT_FILTER_OPTIONS } from '../../containers/use_get_cases'; import { AssigneesFilterPopover } from './assignees_filter'; import { CurrentUserProfile } from '../types'; @@ -31,7 +30,6 @@ interface CasesTableFiltersProps { countOpenCases: number | null; onFilterChanged: (filterOptions: Partial) => void; initial: FilterOptions; - setFilterRefetch: (val: () => void) => void; hiddenStatuses?: CaseStatusWithAllStatus[]; availableSolutions: string[]; displayCreateCaseButton?: boolean; @@ -59,7 +57,6 @@ const CasesTableFiltersComponent = ({ countInProgressCases, onFilterChanged, initial = DEFAULT_FILTER_OPTIONS, - setFilterRefetch, hiddenStatuses, availableSolutions, displayCreateCaseButton, @@ -71,19 +68,9 @@ const CasesTableFiltersComponent = ({ const [selectedTags, setSelectedTags] = useState(initial.tags); const [selectedOwner, setSelectedOwner] = useState([]); const [selectedAssignees, setSelectedAssignees] = useState([]); - const { data: tags = [], refetch: fetchTags } = useGetTags(CASE_LIST_CACHE_KEY); + const { data: tags = [] } = useGetTags(); const { caseAssignmentAuthorized } = useCasesFeatures(); - const refetch = useCallback(() => { - fetchTags(); - }, [fetchTags]); - - useEffect(() => { - if (setFilterRefetch != null) { - setFilterRefetch(refetch); - } - }, [refetch, setFilterRefetch]); - const handleSelectedAssignees = useCallback( (newAssignees: UserProfileWithAvatar[]) => { if (!isEqual(newAssignees, selectedAssignees)) { diff --git a/x-pack/plugins/cases/public/components/all_cases/translations.ts b/x-pack/plugins/cases/public/components/all_cases/translations.ts index 0c4fe02993ea3..96a683aee5077 100644 --- a/x-pack/plugins/cases/public/components/all_cases/translations.ts +++ b/x-pack/plugins/cases/public/components/all_cases/translations.ts @@ -71,10 +71,6 @@ export const CLOSED = i18n.translate('xpack.cases.caseTable.closed', { defaultMessage: 'Closed', }); -export const DELETE = i18n.translate('xpack.cases.caseTable.delete', { - defaultMessage: 'Delete', -}); - export const SELECT = i18n.translate('xpack.cases.caseTable.select', { defaultMessage: 'Select', }); @@ -134,3 +130,40 @@ export const TOTAL_ASSIGNEES_FILTERED = (total: number) => defaultMessage: '{total, plural, one {# assignee} other {# assignees}} filtered', values: { total }, }); + +export const CLOSED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.containers.closedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const REOPENED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.containers.reopenedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const MARK_IN_PROGRESS_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.containers.markInProgressCases', { + values: { caseTitle, totalCases }, + defaultMessage: + 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', + }); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_is_loading_cases.tsx b/x-pack/plugins/cases/public/components/all_cases/use_is_loading_cases.tsx new file mode 100644 index 0000000000000..70f46464ed2f8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_is_loading_cases.tsx @@ -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. + */ + +import { useIsMutating } from '@tanstack/react-query'; +import { casesMutationsKeys } from '../../containers/constants'; + +/** + * Returns true or false if any of the queries and mutations + * are fetching in the all cases page + */ + +export const useIsLoadingCases = (): boolean => { + const isDeletingCases = useIsMutating(casesMutationsKeys.deleteCases); + const isUpdatingCases = useIsMutating(casesMutationsKeys.updateCases); + return Boolean(isDeletingCases) || Boolean(isUpdatingCases); +}; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx new file mode 100644 index 0000000000000..cd077004b5cf9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx @@ -0,0 +1,36 @@ +/* + * 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 { act, renderHook } from '@testing-library/react-hooks'; +import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; +import { casesQueriesKeys } from '../../containers/constants'; +import { useRefreshCases } from './use_on_refresh_cases'; + +describe('useRefreshCases', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('should refresh data on refresh', async () => { + const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); + + const { result } = renderHook(() => useRefreshCases(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current(); + }); + + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.tsx b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.tsx new file mode 100644 index 0000000000000..b50bed282cce1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.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 { useCallback } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { casesQueriesKeys } from '../../containers/constants'; + +/** + * Using react-query queryClient to invalidate all the + * cases table page cache namespace. + * + * This effectively clears the cache for all the cases table pages and + * forces the page to fetch all the data again. Including + * all cases, user profiles, statuses, metrics, tags, etc. + */ + +export const useRefreshCases = () => { + const queryClient = useQueryClient(); + return useCallback(() => { + queryClient.invalidateQueries(casesQueriesKeys.casesList()); + queryClient.invalidateQueries(casesQueriesKeys.tags()); + queryClient.invalidateQueries(casesQueriesKeys.userProfiles()); + }, [queryClient]); +}; diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx index a6247cedd3fac..fdfcdc17d472c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import React, { FunctionComponent, useCallback, useState } from 'react'; import { EuiContextMenuPanel } from '@elastic/eui'; +import { CaseStatuses } from '../../../common'; import { UtilityBar, UtilityBarAction, @@ -15,88 +16,70 @@ import { UtilityBarText, } from '../utility_bar'; import * as i18n from './translations'; -import { Cases, Case, DeleteCase, FilterOptions } from '../../../common/ui/types'; +import { Cases, Case, FilterOptions } from '../../../common/ui/types'; import { getBulkItems } from '../bulk_actions'; import { useDeleteCases } from '../../containers/use_delete_cases'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { useUpdateCases } from '../../containers/use_bulk_update_case'; +import { useRefreshCases } from './use_on_refresh_cases'; -interface OwnProps { +interface Props { data: Cases; enableBulkActions: boolean; filterOptions: FilterOptions; - handleIsLoading: (a: boolean) => void; - refreshCases?: (a?: boolean) => void; selectedCases: Case[]; + deselectCases: () => void; } -type Props = OwnProps; +export const getStatusToasterMessage = (status: CaseStatuses, cases: Case[]): string => { + const totalCases = cases.length; + const caseTitle = totalCases === 1 ? cases[0].title : ''; + + if (status === CaseStatuses.open) { + return i18n.REOPENED_CASES({ totalCases, caseTitle }); + } else if (status === CaseStatuses['in-progress']) { + return i18n.MARK_IN_PROGRESS_CASES({ totalCases, caseTitle }); + } else if (status === CaseStatuses.closed) { + return i18n.CLOSED_CASES({ totalCases, caseTitle }); + } + + return ''; +}; export const CasesTableUtilityBar: FunctionComponent = ({ data, enableBulkActions = false, filterOptions, - handleIsLoading, - refreshCases, selectedCases, + deselectCases, }) => { - const [deleteCases, setDeleteCases] = useState([]); - - // Delete case - const { - dispatchResetIsDeleted, - handleOnDeleteConfirm, - handleToggleModal, - isLoading: isDeleting, - isDeleted, - isDisplayConfirmDeleteModal, - } = useDeleteCases(); + const [isModalVisible, setIsModalVisible] = useState(false); + const onCloseModal = useCallback(() => setIsModalVisible(false), []); + const refreshCases = useRefreshCases(); - // Update case - const { - dispatchResetIsUpdated, - isLoading: isUpdating, - isUpdated, - updateBulkStatus, - } = useUpdateCases(); + const { mutate: deleteCases } = useDeleteCases(); + const { mutate: updateCases } = useUpdateCases(); - useEffect(() => { - handleIsLoading(isDeleting); - }, [handleIsLoading, isDeleting]); + const toggleBulkDeleteModal = useCallback((cases: Case[]) => { + setIsModalVisible(true); + }, []); - useEffect(() => { - handleIsLoading(isUpdating); - }, [handleIsLoading, isUpdating]); - useEffect(() => { - if (isDeleted) { - if (refreshCases != null) refreshCases(); - dispatchResetIsDeleted(); - } - if (isUpdated) { - if (refreshCases != null) refreshCases(); - dispatchResetIsUpdated(); - } - }, [isDeleted, isUpdated, refreshCases, dispatchResetIsDeleted, dispatchResetIsUpdated]); - - const toggleBulkDeleteModal = useCallback( - (cases: Case[]) => { - handleToggleModal(); - - const convertToDeleteCases: DeleteCase[] = cases.map(({ id, title }) => ({ - id, - title, + const handleUpdateCaseStatus = useCallback( + (status: CaseStatuses) => { + const casesToUpdate = selectedCases.map((theCase) => ({ + status, + id: theCase.id, + version: theCase.version, })); - setDeleteCases(convertToDeleteCases); - }, - [setDeleteCases, handleToggleModal] - ); - const handleUpdateCaseStatus = useCallback( - (status: string) => { - updateBulkStatus(selectedCases, status); + updateCases({ + cases: casesToUpdate, + successToasterTitle: getStatusToasterMessage(status, selectedCases), + }); }, - [selectedCases, updateBulkStatus] + [selectedCases, updateCases] ); + const getBulkItemsPopoverContent = useCallback( (closePopover: () => void) => ( = ({ [selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus] ); + const onConfirmDeletion = useCallback(() => { + setIsModalVisible(false); + deleteCases({ + caseIds: selectedCases.map(({ id }) => id), + successToasterTitle: i18n.DELETED_CASES(selectedCases.length), + }); + }, [deleteCases, selectedCases]); + + const onRefresh = useCallback(() => { + deselectCases(); + refreshCases(); + }, [deselectCases, refreshCases]); + return ( @@ -141,20 +137,20 @@ export const CasesTableUtilityBar: FunctionComponent = ({ {i18n.REFRESH} - + {isModalVisible ? ( + + ) : null} ); }; diff --git a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx index 8cf3a14ffd3ce..fcf2002f8882c 100644 --- a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx @@ -19,7 +19,7 @@ interface GetBulkItems { closePopover: () => void; deleteCasesAction: (cases: Case[]) => void; selectedCases: Case[]; - updateCaseStatus: (status: string) => void; + updateCaseStatus: (status: CaseStatuses) => void; } export const getBulkItems = ({ diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx index 8735b94d71b2c..cf15d546415be 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx @@ -8,13 +8,14 @@ import React from 'react'; import { mount } from 'enzyme'; -import { useDeleteCases } from '../../containers/use_delete_cases'; import { noDeleteCasesPermissions, TestProviders } from '../../common/mock'; import { basicCase, basicPush } from '../../containers/mock'; import { Actions } from './actions'; import * as i18n from '../case_view/translations'; -jest.mock('../../containers/use_delete_cases'); -const useDeleteCasesMock = useDeleteCases as jest.Mock; +import * as api from '../../containers/api'; +import { waitFor } from '@testing-library/dom'; + +jest.mock('../../containers/api'); jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -26,6 +27,7 @@ jest.mock('react-router-dom', () => { }), }; }); + const defaultProps = { allCasesNavigation: { href: 'all-cases-href', @@ -34,23 +36,10 @@ const defaultProps = { caseData: basicCase, currentExternalIncident: null, }; -describe('CaseView actions', () => { - const handleOnDeleteConfirm = jest.fn(); - const handleToggleModal = jest.fn(); - const dispatchResetIsDeleted = jest.fn(); - const defaultDeleteState = { - dispatchResetIsDeleted, - handleToggleModal, - handleOnDeleteConfirm, - isLoading: false, - isError: false, - isDeleted: false, - isDisplayConfirmDeleteModal: false, - }; +describe('CaseView actions', () => { beforeEach(() => { jest.resetAllMocks(); - useDeleteCasesMock.mockImplementation(() => defaultDeleteState); }); it('clicking trash toggles modal', () => { @@ -61,10 +50,9 @@ describe('CaseView actions', () => { ); expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeFalsy(); - wrapper.find('button[data-test-subj="property-actions-ellipses"]').first().simulate('click'); wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click'); - expect(handleToggleModal).toHaveBeenCalled(); + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); }); it('does not show trash icon when user does not have deletion privileges', () => { @@ -78,22 +66,26 @@ describe('CaseView actions', () => { expect(wrapper.find('button[data-test-subj="property-actions-ellipses"]').exists()).toBeFalsy(); }); - it('toggle delete modal and confirm', () => { - useDeleteCasesMock.mockImplementation(() => ({ - ...defaultDeleteState, - isDisplayConfirmDeleteModal: true, - })); + it('toggle delete modal and confirm', async () => { + const deleteCasesSpy = jest + .spyOn(api, 'deleteCases') + .mockRejectedValue(new Error('useDeleteCases: Test error')); + const wrapper = mount( ); + wrapper.find('button[data-test-subj="property-actions-ellipses"]').first().simulate('click'); + wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click'); + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([ - { id: basicCase.id, title: basicCase.title }, - ]); + + await waitFor(() => { + expect(deleteCasesSpy).toHaveBeenCalledWith(['basic-case-id'], expect.anything()); + }); }); it('displays active incident link', () => { diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx index 975cf89fc1031..2481822f76b8d 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash/fp'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import * as i18n from '../case_view/translations'; import { useDeleteCases } from '../../containers/use_delete_cases'; @@ -23,11 +23,18 @@ interface CaseViewActions { } const ActionsComponent: React.FC = ({ caseData, currentExternalIncident }) => { - // Delete case - const { handleToggleModal, handleOnDeleteConfirm, isDeleted, isDisplayConfirmDeleteModal } = - useDeleteCases(); + const { mutate: deleteCases } = useDeleteCases(); const { navigateToAllCases } = useAllCasesNavigation(); const { permissions } = useCasesContext(); + const [isModalVisible, setIsModalVisible] = useState(false); + + const openModal = useCallback(() => { + setIsModalVisible(true); + }, []); + + const closeModal = useCallback(() => { + setIsModalVisible(false); + }, []); const propertyActions = useMemo( () => [ @@ -36,7 +43,7 @@ const ActionsComponent: React.FC = ({ caseData, currentExternal { iconType: 'trash', label: i18n.DELETE_CASE(), - onClick: handleToggleModal, + onClick: openModal, }, ] : []), @@ -50,13 +57,16 @@ const ActionsComponent: React.FC = ({ caseData, currentExternal ] : []), ], - [handleToggleModal, currentExternalIncident, permissions.delete] + [permissions.delete, openModal, currentExternalIncident] ); - if (isDeleted) { - navigateToAllCases(); - return null; - } + const onConfirmDeletion = useCallback(() => { + setIsModalVisible(false); + deleteCases( + { caseIds: [caseData.id], successToasterTitle: i18n.DELETED_CASES(1) }, + { onSuccess: navigateToAllCases } + ); + }, [caseData.id, deleteCases, navigateToAllCases]); if (propertyActions.length === 0) { return null; @@ -65,12 +75,13 @@ const ActionsComponent: React.FC = ({ caseData, currentExternal return ( - + {isModalVisible ? ( + + ) : null} ); }; diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx index 20cf2c8d0d9b4..6a5fd8fad8113 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -30,7 +30,7 @@ import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; import CaseView from '.'; import { waitFor } from '@testing-library/dom'; import { useGetTags } from '../../containers/use_get_tags'; -import { CASE_VIEW_CACHE_KEY } from '../../containers/constants'; +import { casesQueriesKeys } from '../../containers/constants'; import { alertsHit, caseViewProps, @@ -173,7 +173,8 @@ describe('CaseView', () => { const queryClientSpy = jest.spyOn(appMockRenderer.queryClient, 'invalidateQueries'); const result = appMockRenderer.render(); userEvent.click(result.getByTestId('case-refresh')); - expect(queryClientSpy).toHaveBeenCalledWith(['case']); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); }); describe('when a `refreshRef` prop is provided', () => { @@ -205,7 +206,8 @@ describe('CaseView', () => { it('should refresh actions and comments', async () => { refreshRef!.current!.refreshCase(); await waitFor(() => { - expect(queryClientSpy).toHaveBeenCalledWith([CASE_VIEW_CACHE_KEY]); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); }); }); }); diff --git a/x-pack/plugins/cases/public/components/case_view/use_on_refresh_case_view_page.tsx b/x-pack/plugins/cases/public/components/case_view/use_on_refresh_case_view_page.tsx index 396f180a074a0..d12ddc258e761 100644 --- a/x-pack/plugins/cases/public/components/case_view/use_on_refresh_case_view_page.tsx +++ b/x-pack/plugins/cases/public/components/case_view/use_on_refresh_case_view_page.tsx @@ -7,7 +7,7 @@ import { useCallback } from 'react'; import { useQueryClient } from '@tanstack/react-query'; -import { CASE_TAGS_CACHE_KEY, CASE_VIEW_CACHE_KEY } from '../../containers/constants'; +import { casesQueriesKeys } from '../../containers/constants'; /** * Using react-query queryClient to invalidate all the @@ -17,10 +17,11 @@ import { CASE_TAGS_CACHE_KEY, CASE_VIEW_CACHE_KEY } from '../../containers/const * forces the page to fetch all the data again. Including * metrics, actions, comments, etc. */ + export const useRefreshCaseViewPage = () => { const queryClient = useQueryClient(); return useCallback(() => { - queryClient.invalidateQueries([CASE_VIEW_CACHE_KEY]); - queryClient.invalidateQueries([CASE_TAGS_CACHE_KEY]); + queryClient.invalidateQueries(casesQueriesKeys.caseView()); + queryClient.invalidateQueries(casesQueriesKeys.tags()); }, [queryClient]); }; diff --git a/x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx b/x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx index ce8287310fb17..e1c4e0c1fa02d 100644 --- a/x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx +++ b/x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx @@ -10,35 +10,28 @@ import { EuiConfirmModal } from '@elastic/eui'; import * as i18n from './translations'; interface ConfirmDeleteCaseModalProps { - caseTitle: string; - isModalVisible: boolean; - caseQuantity?: number; + totalCasesToBeDeleted: number; onCancel: () => void; onConfirm: () => void; } const ConfirmDeleteCaseModalComp: React.FC = ({ - caseTitle, - isModalVisible, - caseQuantity = 1, + totalCasesToBeDeleted, onCancel, onConfirm, }) => { - if (!isModalVisible) { - return null; - } return ( - {i18n.CONFIRM_QUESTION(caseQuantity)} + {i18n.CONFIRM_QUESTION(totalCasesToBeDeleted)} ); }; diff --git a/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts index f8e4ab2a83a73..e6f5072f08f89 100644 --- a/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts +++ b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts @@ -14,12 +14,6 @@ export const DELETE_TITLE = (caseTitle: string) => defaultMessage: 'Delete "{caseTitle}"', }); -export const DELETE_SELECTED_CASES = (quantity: number, title: string) => - i18n.translate('xpack.cases.confirmDeleteCase.selectedCases', { - values: { quantity, title }, - defaultMessage: 'Delete "{quantity, plural, =1 {{title}} other {Selected {quantity} cases}}"', - }); - export const CONFIRM_QUESTION = (quantity: number) => i18n.translate('xpack.cases.confirmDeleteCase.confirmQuestion', { values: { quantity }, diff --git a/x-pack/plugins/cases/public/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts index 2b43135123080..d01f927d3c324 100644 --- a/x-pack/plugins/cases/public/containers/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts @@ -8,7 +8,6 @@ import { ActionLicense, Cases, - BulkUpdateStatus, Case, CasesStatus, CaseUserActions, @@ -28,7 +27,7 @@ import { pushedCase, tags, } from '../mock'; -import { ResolvedCase, SeverityAll } from '../../../common/ui/types'; +import { CaseUpdateRequest, ResolvedCase, SeverityAll } from '../../../common/ui/types'; import { CasePatchRequest, CasePostRequest, @@ -99,8 +98,8 @@ export const patchCase = async ( signal: AbortSignal ): Promise => Promise.resolve([basicCase]); -export const patchCasesStatus = async ( - cases: BulkUpdateStatus[], +export const updateCases = async ( + cases: CaseUpdateRequest[], signal: AbortSignal ): Promise => Promise.resolve(allCases.cases); diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx index e51224dc593dc..d5618ff8a7e9b 100644 --- a/x-pack/plugins/cases/public/containers/api.test.tsx +++ b/x-pack/plugins/cases/public/containers/api.test.tsx @@ -25,7 +25,7 @@ import { getCaseUserActions, getTags, patchCase, - patchCasesStatus, + updateCases, patchComment, postCase, createAttachments, @@ -449,7 +449,7 @@ describe('Cases API', () => { }); }); - describe('patchCasesStatus', () => { + describe('updateCases', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(casesSnake); @@ -464,7 +464,7 @@ describe('Cases API', () => { ]; test('should be called with correct check url, method, signal', async () => { - await patchCasesStatus(data, abortCtrl.signal); + await updateCases(data, abortCtrl.signal); expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, { method: 'PATCH', body: JSON.stringify({ cases: data }), @@ -473,7 +473,7 @@ describe('Cases API', () => { }); test('should return correct response should not covert to camel case registered attachments', async () => { - const resp = await patchCasesStatus(data, abortCtrl.signal); + const resp = await updateCases(data, abortCtrl.signal); expect(resp).toEqual(cases); }); }); diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts index 697635030eb49..ff8d05ef653d9 100644 --- a/x-pack/plugins/cases/public/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -10,6 +10,7 @@ import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/const import { isEmpty } from 'lodash'; import { Cases, + CaseUpdateRequest, FetchCasesProps, ResolvedCase, SeverityAll, @@ -58,7 +59,6 @@ import { import { ActionLicense, - BulkUpdateStatus, Case, SingleCaseMetrics, SingleCaseMetricsFeature, @@ -238,8 +238,8 @@ export const patchCase = async ( return convertCasesToCamelCase(decodeCasesResponse(response)); }; -export const patchCasesStatus = async ( - cases: BulkUpdateStatus[], +export const updateCases = async ( + cases: CaseUpdateRequest[], signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch(CASES_URL, { diff --git a/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx b/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx index 76a48e5d09ca3..8503ccd6eddf8 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx @@ -9,13 +9,13 @@ import { useQuery } from '@tanstack/react-query'; import * as i18n from '../translations'; import { fetchActionTypes } from './api'; import { useToasts } from '../../common/lib/kibana'; -import { CASE_CONFIGURATION_CACHE_KEY } from '../constants'; +import { casesQueriesKeys } from '../constants'; import { ServerError } from '../../types'; export const useGetActionTypes = () => { const toasts = useToasts(); return useQuery( - [CASE_CONFIGURATION_CACHE_KEY, 'actionTypes'], + casesQueriesKeys.connectorTypes(), () => { const abortController = new AbortController(); return fetchActionTypes({ signal: abortController.signal }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx b/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx index 95124af988fb6..5e96dd86ae985 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx @@ -9,14 +9,14 @@ import { useQuery } from '@tanstack/react-query'; import { fetchConnectors } from './api'; import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana'; import * as i18n from './translations'; -import { CASE_CONNECTORS_CACHE_KEY } from '../constants'; +import { casesQueriesKeys } from '../constants'; import { ServerError } from '../../types'; export function useGetConnectors() { const toasts = useToasts(); const { actions } = useApplicationCapabilities(); return useQuery( - [CASE_CONNECTORS_CACHE_KEY], + casesQueriesKeys.connectorsList(), async () => { if (!actions.read) { return []; diff --git a/x-pack/plugins/cases/public/containers/constants.ts b/x-pack/plugins/cases/public/containers/constants.ts index a87d773303447..a6acbbd68d412 100644 --- a/x-pack/plugins/cases/public/containers/constants.ts +++ b/x-pack/plugins/cases/public/containers/constants.ts @@ -5,23 +5,36 @@ * 2.0. */ +import { SingleCaseMetricsFeature } from './types'; + export const DEFAULT_TABLE_ACTIVE_PAGE = 1; export const DEFAULT_TABLE_LIMIT = 5; -export const CASE_VIEW_CACHE_KEY = 'case'; -export const CASE_VIEW_ACTIONS_CACHE_KEY = 'user-actions'; -export const CASE_VIEW_METRICS_CACHE_KEY = 'metrics'; -export const CASE_CONFIGURATION_CACHE_KEY = 'case-configuration'; -export const CASE_LIST_CACHE_KEY = 'case-list'; -export const CASE_CONNECTORS_CACHE_KEY = 'case-connectors'; -export const CASE_LICENSE_CACHE_KEY = 'case-license-action'; -export const CASE_TAGS_CACHE_KEY = 'case-tags'; - -/** - * User profiles - */ +export const casesQueriesKeys = { + all: ['cases'] as const, + users: ['users'] as const, + connectors: ['connectors'] as const, + connectorsList: () => [...casesQueriesKeys.connectors, 'list'] as const, + casesList: () => [...casesQueriesKeys.all, 'list'] as const, + casesMetrics: () => [...casesQueriesKeys.casesList(), 'metrics'] as const, + casesStatuses: () => [...casesQueriesKeys.casesList(), 'statuses'] as const, + cases: (params: unknown) => [...casesQueriesKeys.casesList(), 'all-cases', params] as const, + caseView: () => [...casesQueriesKeys.all, 'case'] as const, + case: (id: string) => [...casesQueriesKeys.caseView(), id] as const, + caseMetrics: (id: string, features: SingleCaseMetricsFeature[]) => + [...casesQueriesKeys.case(id), 'metrics', features] as const, + userActions: (id: string, connectorId: string) => + [...casesQueriesKeys.case(id), 'user-actions', connectorId] as const, + userProfiles: () => [...casesQueriesKeys.users, 'user-profiles'] as const, + userProfilesList: (ids: string[]) => [...casesQueriesKeys.userProfiles(), ids] as const, + currentUser: () => [...casesQueriesKeys.users, 'current-user'] as const, + suggestUsers: (params: unknown) => [...casesQueriesKeys.users, 'suggest', params] as const, + connectorTypes: () => [...casesQueriesKeys.connectors, 'types'] as const, + license: () => [...casesQueriesKeys.connectors, 'license'] as const, + tags: () => [...casesQueriesKeys.all, 'tags'] as const, +}; -export const USER_PROFILES_CACHE_KEY = 'user-profiles'; -export const USER_PROFILES_SUGGEST_CACHE_KEY = 'suggest'; -export const USER_PROFILES_BULK_GET_CACHE_KEY = 'bulk-get'; -export const USER_PROFILES_GET_CURRENT_CACHE_KEY = 'get-current'; +export const casesMutationsKeys = { + deleteCases: ['delete-cases'] as const, + updateCases: ['update-cases'] as const, +}; diff --git a/x-pack/plugins/cases/public/containers/translations.ts b/x-pack/plugins/cases/public/containers/translations.ts index 72aeb66772c52..5bf4acf385fce 100644 --- a/x-pack/plugins/cases/public/containers/translations.ts +++ b/x-pack/plugins/cases/public/containers/translations.ts @@ -23,48 +23,9 @@ export const UPDATED_CASE = (caseTitle: string) => defaultMessage: 'Updated "{caseTitle}"', }); -export const DELETED_CASES = (totalCases: number, caseTitle?: string) => - i18n.translate('xpack.cases.containers.deletedCases', { - values: { caseTitle, totalCases }, - defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', - }); - -export const CLOSED_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.closedCases', { - values: { caseTitle, totalCases }, - defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', - }); - -export const REOPENED_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.reopenedCases', { - values: { caseTitle, totalCases }, - defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', - }); - -export const MARK_IN_PROGRESS_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.markInProgressCases', { - values: { caseTitle, totalCases }, - defaultMessage: - 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', - }); +export const UPDATED_CASES = i18n.translate('xpack.cases.containers.updatedCases', { + defaultMessage: 'Updated cases', +}); export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => i18n.translate('xpack.cases.containers.pushToExternalService', { diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx index d00b361828a6e..d46f79569622f 100644 --- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx @@ -6,126 +6,89 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../common/api'; -import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case'; -import { basicCase } from './mock'; +import { useUpdateCases } from './use_bulk_update_case'; +import { allCases } from './mock'; +import { useToasts } from '../common/lib/kibana'; import * as api from './api'; +import { createAppMockRenderer, AppMockRenderer } from '../common/mock'; +import { casesQueriesKeys } from './constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); describe('useUpdateCases', () => { const abortCtrl = new AbortController(); + const addSuccess = jest.fn(); + const addError = jest.fn(); + + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + let appMockRender: AppMockRenderer; + beforeEach(() => { + appMockRender = createAppMockRenderer(); jest.clearAllMocks(); - jest.restoreAllMocks(); }); - it('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ - isLoading: false, - isError: false, - isUpdated: false, - updateBulkStatus: result.current.updateBulkStatus, - dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, - }); + it('calls the api when invoked with the correct parameters', async () => { + const spy = jest.spyOn(api, 'updateCases'); + const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + wrapper: appMockRender.AppWrapper, }); - }); - it('calls patchCase with correct arguments', async () => { - const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus'); - - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - await waitForNextUpdate(); - - result.current.updateBulkStatus([basicCase], CaseStatuses.closed); - await waitForNextUpdate(); - expect(spyOnPatchCases).toBeCalledWith( - [ - { - status: CaseStatuses.closed, - id: basicCase.id, - version: basicCase.version, - }, - ], - abortCtrl.signal - ); + act(() => { + result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); - }); - it('patch cases', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - await waitForNextUpdate(); - result.current.updateBulkStatus([basicCase], CaseStatuses.closed); - await waitForNextUpdate(); - expect(result.current).toEqual({ - isUpdated: true, - isLoading: false, - isError: false, - updateBulkStatus: result.current.updateBulkStatus, - dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, - }); - }); + await waitForNextUpdate(); + + expect(spy).toHaveBeenCalledWith(allCases.cases, abortCtrl.signal); }); - it('set isLoading to true when posting case', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - await waitForNextUpdate(); - result.current.updateBulkStatus([basicCase], CaseStatuses.closed); + it('invalidates the queries correctly', async () => { + const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); + const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + wrapper: appMockRender.AppWrapper, + }); - expect(result.current.isLoading).toBe(true); + act(() => { + result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); }); - it('dispatchResetIsUpdated resets is updated', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - - await waitForNextUpdate(); - result.current.updateBulkStatus([basicCase], CaseStatuses.closed); - await waitForNextUpdate(); - expect(result.current.isUpdated).toBeTruthy(); - result.current.dispatchResetIsUpdated(); - expect(result.current.isUpdated).toBeFalsy(); + it('shows a success toaster', async () => { + const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(addSuccess).toHaveBeenCalledWith('Success title'); }); - it('unhappy path', async () => { - const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus'); - spyOnPatchCases.mockImplementation(() => { - throw new Error('Something went wrong'); + it('shows a toast error when the api return an error', async () => { + jest.spyOn(api, 'updateCases').mockRejectedValue(new Error('useUpdateCases: Test error')); + + const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + wrapper: appMockRender.AppWrapper, }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useUpdateCases() - ); - await waitForNextUpdate(); - result.current.updateBulkStatus([basicCase], CaseStatuses.closed); - - expect(result.current).toEqual({ - isUpdated: false, - isLoading: false, - isError: true, - updateBulkStatus: result.current.updateBulkStatus, - dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, - }); + act(() => { + result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(addError).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx index 715b0c611c3b8..e0866bf0166a6 100644 --- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx @@ -5,149 +5,42 @@ * 2.0. */ -import { useCallback, useReducer, useRef, useEffect } from 'react'; -import { CaseStatuses } from '../../common/api'; +import { useQueryClient, useMutation } from '@tanstack/react-query'; import * as i18n from './translations'; -import { patchCasesStatus } from './api'; -import { BulkUpdateStatus, Case } from './types'; -import { useToasts } from '../common/lib/kibana'; - -interface UpdateState { - isUpdated: boolean; - isLoading: boolean; - isError: boolean; -} -type Action = - | { type: 'FETCH_INIT' } - | { type: 'FETCH_SUCCESS'; payload: boolean } - | { type: 'FETCH_FAILURE' } - | { type: 'RESET_IS_UPDATED' }; - -const dataFetchReducer = (state: UpdateState, action: Action): UpdateState => { - switch (action.type) { - case 'FETCH_INIT': - return { - ...state, - isLoading: true, - isError: false, - }; - case 'FETCH_SUCCESS': - return { - ...state, - isLoading: false, - isError: false, - isUpdated: action.payload, - }; - case 'FETCH_FAILURE': - return { - ...state, - isLoading: false, - isError: true, - }; - case 'RESET_IS_UPDATED': - return { - ...state, - isUpdated: false, - }; - default: - return state; - } -}; -export interface UseUpdateCases extends UpdateState { - updateBulkStatus: (cases: Case[], status: string) => void; - dispatchResetIsUpdated: () => void; +import { updateCases } from './api'; +import { CaseUpdateRequest } from './types'; +import { useCasesToast } from '../common/use_cases_toast'; +import { ServerError } from '../types'; +import { casesQueriesKeys, casesMutationsKeys } from './constants'; + +interface MutationArgs { + cases: CaseUpdateRequest[]; + successToasterTitle: string; } -const getStatusToasterMessage = ( - status: CaseStatuses, - messageArgs: { - totalCases: number; - caseTitle?: string; - } -): string => { - if (status === CaseStatuses.open) { - return i18n.REOPENED_CASES(messageArgs); - } else if (status === CaseStatuses['in-progress']) { - return i18n.MARK_IN_PROGRESS_CASES(messageArgs); - } else if (status === CaseStatuses.closed) { - return i18n.CLOSED_CASES(messageArgs); - } - - return ''; -}; - -export const useUpdateCases = (): UseUpdateCases => { - const [state, dispatch] = useReducer(dataFetchReducer, { - isLoading: false, - isError: false, - isUpdated: false, - }); - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); - - const dispatchUpdateCases = useCallback(async (cases: BulkUpdateStatus[], action: string) => { - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); - - dispatch({ type: 'FETCH_INIT' }); - const patchResponse = await patchCasesStatus(cases, abortCtrlRef.current.signal); - - if (!isCancelledRef.current) { - const resultCount = Object.keys(patchResponse).length; - const firstTitle = patchResponse[0].title; - - dispatch({ type: 'FETCH_SUCCESS', payload: true }); - const messageArgs = { - totalCases: resultCount, - caseTitle: resultCount === 1 ? firstTitle : '', - }; +export const useUpdateCases = () => { + const queryClient = useQueryClient(); + const { showErrorToast, showSuccessToast } = useCasesToast(); - const message = - action === 'status' ? getStatusToasterMessage(patchResponse[0].status, messageArgs) : ''; - - toasts.addSuccess(message); - } - } catch (error) { - if (!isCancelledRef.current) { - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_TITLE } - ); - } - dispatch({ type: 'FETCH_FAILURE' }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const dispatchResetIsUpdated = useCallback(() => { - dispatch({ type: 'RESET_IS_UPDATED' }); - }, []); - - const updateBulkStatus = useCallback( - (cases: Case[], status: string) => { - const updateCasesStatus: BulkUpdateStatus[] = cases.map((theCase) => ({ - status, - id: theCase.id, - version: theCase.version, - })); - dispatchUpdateCases(updateCasesStatus, 'status'); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - useEffect( - () => () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); + return useMutation( + ({ cases }: MutationArgs) => { + const abortCtrlRef = new AbortController(); + return updateCases(cases, abortCtrlRef.signal); }, - [] + { + mutationKey: casesMutationsKeys.updateCases, + onSuccess: (_, { successToasterTitle }) => { + queryClient.invalidateQueries(casesQueriesKeys.casesList()); + queryClient.invalidateQueries(casesQueriesKeys.tags()); + queryClient.invalidateQueries(casesQueriesKeys.userProfiles()); + + showSuccessToast(successToasterTitle); + }, + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_DELETING }); + }, + } ); - - return { ...state, updateBulkStatus, dispatchResetIsUpdated }; }; + +export type UseUpdateCases = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx index 88f6db42144f6..623a01746e3cb 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx @@ -7,125 +7,88 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; +import { useDeleteCases } from './use_delete_cases'; import * as api from './api'; +import { useToasts } from '../common/lib/kibana'; +import { AppMockRenderer, createAppMockRenderer } from '../common/mock'; +import { casesQueriesKeys } from './constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); describe('useDeleteCases', () => { const abortCtrl = new AbortController(); - const deleteObj = [ - { id: '1', title: 'case 1' }, - { id: '2', title: 'case 2' }, - { id: '3', title: 'case 3' }, - ]; - const deleteArr = ['1', '2', '3']; - it('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ - isDisplayConfirmDeleteModal: false, - isLoading: false, - isError: false, - isDeleted: false, - dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, - handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, - handleToggleModal: result.current.handleToggleModal, - }); - }); - }); + const addSuccess = jest.fn(); + const addError = jest.fn(); - it('calls deleteCases with correct arguments', async () => { - const spyOnDeleteCases = jest.spyOn(api, 'deleteCases'); + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); + let appMockRender: AppMockRenderer; - result.current.handleOnDeleteConfirm(deleteObj); - await waitForNextUpdate(); - expect(spyOnDeleteCases).toBeCalledWith(deleteArr, abortCtrl.signal); - }); + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); }); - it('deletes cases', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); - result.current.handleToggleModal(); - result.current.handleOnDeleteConfirm(deleteObj); - await waitForNextUpdate(); - expect(result.current).toEqual({ - isDisplayConfirmDeleteModal: false, - isLoading: false, - isError: false, - isDeleted: true, - dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, - handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, - handleToggleModal: result.current.handleToggleModal, - }); + it('calls the api when invoked with the correct parameters', async () => { + const spy = jest.spyOn(api, 'deleteCases'); + const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(spy).toHaveBeenCalledWith(['1', '2'], abortCtrl.signal); }); - it('resets is deleting', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); - result.current.handleToggleModal(); - result.current.handleOnDeleteConfirm(deleteObj); - await waitForNextUpdate(); - expect(result.current.isDeleted).toBeTruthy(); - result.current.handleToggleModal(); - result.current.dispatchResetIsDeleted(); - expect(result.current.isDeleted).toBeFalsy(); + it('invalidates the queries correctly', async () => { + const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); + const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); }); - it('set isLoading to true when deleting cases', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); - result.current.handleToggleModal(); - result.current.handleOnDeleteConfirm(deleteObj); - expect(result.current.isLoading).toBe(true); + it('shows a success toaster', async () => { + const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + wrapper: appMockRender.AppWrapper, }); + + act(() => { + result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); + }); + + await waitForNextUpdate(); + + expect(addSuccess).toHaveBeenCalledWith('Success title'); }); - it('unhappy path', async () => { - const spyOnDeleteCases = jest.spyOn(api, 'deleteCases'); - spyOnDeleteCases.mockImplementation(() => { - throw new Error('Something went wrong'); + it('shows a toast error when the api return an error', async () => { + jest.spyOn(api, 'deleteCases').mockRejectedValue(new Error('useDeleteCases: Test error')); + + const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + wrapper: appMockRender.AppWrapper, }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteCases() - ); - await waitForNextUpdate(); - result.current.handleToggleModal(); - result.current.handleOnDeleteConfirm(deleteObj); - - expect(result.current).toEqual({ - isDisplayConfirmDeleteModal: false, - isLoading: false, - isError: true, - isDeleted: false, - dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, - handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, - handleToggleModal: result.current.handleToggleModal, - }); + act(() => { + result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); + + await waitForNextUpdate(); + + expect(addError).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.tsx index 7ccec4436ec0b..da2258f8f5d82 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_cases.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.tsx @@ -5,139 +5,41 @@ * 2.0. */ -import { useCallback, useReducer, useRef, useEffect } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import * as i18n from './translations'; import { deleteCases } from './api'; -import { DeleteCase } from './types'; -import { useToasts } from '../common/lib/kibana'; +import { ServerError } from '../types'; +import { casesQueriesKeys, casesMutationsKeys } from './constants'; +import { useCasesToast } from '../common/use_cases_toast'; -interface DeleteState { - isDisplayConfirmDeleteModal: boolean; - isDeleted: boolean; - isLoading: boolean; - isError: boolean; +interface MutationArgs { + caseIds: string[]; + successToasterTitle: string; } -type Action = - | { type: 'DISPLAY_MODAL'; payload: boolean } - | { type: 'FETCH_INIT' } - | { type: 'FETCH_SUCCESS'; payload: boolean } - | { type: 'FETCH_FAILURE' } - | { type: 'RESET_IS_DELETED' }; -const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => { - switch (action.type) { - case 'DISPLAY_MODAL': - return { - ...state, - isDisplayConfirmDeleteModal: action.payload, - }; - case 'FETCH_INIT': - return { - ...state, - isLoading: true, - isError: false, - }; - case 'FETCH_SUCCESS': - return { - ...state, - isLoading: false, - isError: false, - isDeleted: action.payload, - }; - case 'FETCH_FAILURE': - return { - ...state, - isLoading: false, - isError: true, - }; - case 'RESET_IS_DELETED': - return { - ...state, - isDeleted: false, - }; - default: - return state; - } -}; - -export interface UseDeleteCase extends DeleteState { - dispatchResetIsDeleted: () => void; - handleOnDeleteConfirm: (cases: DeleteCase[]) => void; - handleToggleModal: () => void; -} - -export const useDeleteCases = (): UseDeleteCase => { - const [state, dispatch] = useReducer(dataFetchReducer, { - isDisplayConfirmDeleteModal: false, - isLoading: false, - isError: false, - isDeleted: false, - }); - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); - - const dispatchDeleteCases = useCallback(async (cases: DeleteCase[]) => { - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); - dispatch({ type: 'FETCH_INIT' }); - - const caseIds = cases.map((theCase) => theCase.id); - if (cases.length > 0) { - await deleteCases(caseIds, abortCtrlRef.current.signal); - } - - if (!isCancelledRef.current) { - dispatch({ type: 'FETCH_SUCCESS', payload: true }); - toasts.addSuccess( - i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : '') - ); - } - } catch (error) { - if (!isCancelledRef.current) { - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_DELETING } - ); - } - dispatch({ type: 'FETCH_FAILURE' }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); +export const useDeleteCases = () => { + const queryClient = useQueryClient(); + const { showErrorToast, showSuccessToast } = useCasesToast(); - const dispatchToggleDeleteModal = useCallback(() => { - dispatch({ type: 'DISPLAY_MODAL', payload: !state.isDisplayConfirmDeleteModal }); - }, [state.isDisplayConfirmDeleteModal]); - - const dispatchResetIsDeleted = useCallback(() => { - dispatch({ type: 'RESET_IS_DELETED' }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.isDisplayConfirmDeleteModal]); - - const handleOnDeleteConfirm = useCallback( - (cases: DeleteCase[]) => { - dispatchDeleteCases(cases); - dispatchToggleDeleteModal(); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [state.isDisplayConfirmDeleteModal] - ); - const handleToggleModal = useCallback(() => { - dispatchToggleDeleteModal(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.isDisplayConfirmDeleteModal]); - - useEffect( - () => () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); + return useMutation( + ({ caseIds }: MutationArgs) => { + const abortCtrlRef = new AbortController(); + return deleteCases(caseIds, abortCtrlRef.signal); }, - [] + { + mutationKey: casesMutationsKeys.deleteCases, + onSuccess: (_, { successToasterTitle }) => { + queryClient.invalidateQueries(casesQueriesKeys.casesList()); + queryClient.invalidateQueries(casesQueriesKeys.tags()); + queryClient.invalidateQueries(casesQueriesKeys.userProfiles()); + + showSuccessToast(successToasterTitle); + }, + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_DELETING }); + }, + } ); - - return { ...state, dispatchResetIsDeleted, handleOnDeleteConfirm, handleToggleModal }; }; + +export type UseDeleteCases = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx index 8e9aa28de440a..7f05012cbbe6a 100644 --- a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx @@ -10,7 +10,7 @@ import { useToasts } from '../common/lib/kibana'; import { getActionLicense } from './api'; import * as i18n from './translations'; import { ConnectorTypes } from '../../common/api'; -import { CASE_LICENSE_CACHE_KEY } from './constants'; +import { casesQueriesKeys } from './constants'; import { ServerError } from '../types'; const MINIMUM_LICENSE_REQUIRED_CONNECTOR = ConnectorTypes.jira; @@ -18,7 +18,7 @@ const MINIMUM_LICENSE_REQUIRED_CONNECTOR = ConnectorTypes.jira; export const useGetActionLicense = () => { const toasts = useToasts(); return useQuery( - [CASE_LICENSE_CACHE_KEY], + casesQueriesKeys.license(), async () => { const abortCtrl = new AbortController(); const response = await getActionLicense(abortCtrl.signal); diff --git a/x-pack/plugins/cases/public/containers/use_get_case.tsx b/x-pack/plugins/cases/public/containers/use_get_case.tsx index ded91240239a1..bf588cc1e71d0 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.tsx @@ -11,12 +11,12 @@ import * as i18n from './translations'; import { useToasts } from '../common/lib/kibana'; import { resolveCase } from './api'; import { ServerError } from '../types'; -import { CASE_VIEW_CACHE_KEY } from './constants'; +import { casesQueriesKeys } from './constants'; export const useGetCase = (caseId: string) => { const toasts = useToasts(); return useQuery( - [CASE_VIEW_CACHE_KEY, caseId], + casesQueriesKeys.case(caseId), () => { const abortCtrlRef = new AbortController(); return resolveCase(caseId, true, abortCtrlRef.signal); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_metrics.tsx b/x-pack/plugins/cases/public/containers/use_get_case_metrics.tsx index 1e294c4a5ba6e..32d63fcc3b42e 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_metrics.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_metrics.tsx @@ -11,13 +11,13 @@ import { useToasts } from '../common/lib/kibana'; import { getSingleCaseMetrics } from './api'; import { ServerError } from '../types'; import { ERROR_TITLE } from './translations'; -import { CASE_VIEW_CACHE_KEY, CASE_VIEW_METRICS_CACHE_KEY } from './constants'; +import { casesQueriesKeys } from './constants'; export const useGetCaseMetrics = (caseId: string, features: SingleCaseMetricsFeature[]) => { const toasts = useToasts(); const abortCtrlRef = new AbortController(); return useQuery( - [CASE_VIEW_CACHE_KEY, CASE_VIEW_METRICS_CACHE_KEY, caseId, features], + casesQueriesKeys.caseMetrics(caseId, features), async () => { const response: SingleCaseMetrics = await getSingleCaseMetrics( caseId, diff --git a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx index fde45207b673e..c92d56b41ea76 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx @@ -20,7 +20,7 @@ import { import { ServerError } from '../types'; import { useToasts } from '../common/lib/kibana'; import { ERROR_TITLE } from './translations'; -import { CASE_VIEW_ACTIONS_CACHE_KEY, CASE_VIEW_CACHE_KEY } from './constants'; +import { casesQueriesKeys } from './constants'; export interface CaseService extends CaseExternalService { firstPushIndex: number; @@ -238,7 +238,7 @@ export const useGetCaseUserActions = (caseId: string, caseConnectorId: string) = const toasts = useToasts(); const abortCtrlRef = new AbortController(); return useQuery( - [CASE_VIEW_CACHE_KEY, CASE_VIEW_ACTIONS_CACHE_KEY, caseId, caseConnectorId], + casesQueriesKeys.userActions(caseId, caseConnectorId), async () => { const response = await getCaseUserActions(caseId, abortCtrlRef.signal); const participants = !isEmpty(response) diff --git a/x-pack/plugins/cases/public/containers/use_get_cases.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.tsx index 7b046cac3f13f..d630534957e53 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.tsx @@ -6,7 +6,7 @@ */ import { useQuery, UseQueryResult } from '@tanstack/react-query'; -import { CASE_LIST_CACHE_KEY, DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from './constants'; +import { casesQueriesKeys, DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from './constants'; import { Cases, FilterOptions, QueryParams, SortFieldCase, StatusAll, SeverityAll } from './types'; import { useToasts } from '../common/lib/kibana'; import * as i18n from './translations'; @@ -51,7 +51,7 @@ export const useGetCases = ( ): UseQueryResult => { const toasts = useToasts(); return useQuery( - [CASE_LIST_CACHE_KEY, 'cases', params], + casesQueriesKeys.cases(params), () => { const abortCtrl = new AbortController(); return getCases({ diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx index a8747a2bd43a5..0b0cdc59a487e 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx @@ -5,124 +5,56 @@ * 2.0. */ -import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; import * as api from '../api'; -import { TestProviders } from '../common/mock'; -import { useGetCasesMetrics, UseGetCasesMetrics } from './use_get_cases_metrics'; +import { AppMockRenderer, createAppMockRenderer } from '../common/mock'; +import { useGetCasesMetrics } from './use_get_cases_metrics'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; +import { useToasts } from '../common/lib/kibana'; jest.mock('../api'); jest.mock('../common/lib/kibana'); describe('useGetCasesMetrics', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - }); + const abortCtrl = new AbortController(); + const addSuccess = jest.fn(); + const addError = jest.fn(); - it('init', async () => { - const { result } = renderHook(() => useGetCasesMetrics(), { - wrapper: ({ children }) => {children}, - }); + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - await act(async () => { - expect(result.current).toEqual({ - mttr: 0, - isLoading: true, - isError: false, - fetchCasesMetrics: result.current.fetchCasesMetrics, - }); - }); + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); }); - it('calls getCasesMetrics api', async () => { + it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getCasesMetrics'); - await act(async () => { - const { waitForNextUpdate } = renderHook( - () => useGetCasesMetrics(), - { - wrapper: ({ children }) => {children}, - } - ); - - await waitForNextUpdate(); - expect(spy).toBeCalledWith({ - http: expect.anything(), - signal: expect.anything(), - query: { - features: ['mttr'], - owner: [SECURITY_SOLUTION_OWNER], - }, - }); + const { waitForNextUpdate } = renderHook(() => useGetCasesMetrics(), { + wrapper: appMockRender.AppWrapper, }); - }); - it('fetch cases metrics', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCasesMetrics(), - { - wrapper: ({ children }) => {children}, - } - ); + await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - mttr: 12, - isLoading: false, - isError: false, - fetchCasesMetrics: result.current.fetchCasesMetrics, - }); + expect(spy).toHaveBeenCalledWith({ + http: expect.anything(), + signal: abortCtrl.signal, + query: { owner: [SECURITY_SOLUTION_OWNER], features: ['mttr'] }, }); }); - it('fetches metrics when fetchCasesMetrics is invoked', async () => { - const spy = jest.spyOn(api, 'getCasesMetrics'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCasesMetrics(), - { - wrapper: ({ children }) => {children}, - } - ); - - await waitForNextUpdate(); - expect(spy).toBeCalledWith({ - http: expect.anything(), - signal: expect.anything(), - query: { - features: ['mttr'], - owner: [SECURITY_SOLUTION_OWNER], - }, - }); - result.current.fetchCasesMetrics(); - await waitForNextUpdate(); - expect(spy).toHaveBeenCalledTimes(2); - }); - }); + it('shows a toast error when the api return an error', async () => { + jest + .spyOn(api, 'getCasesMetrics') + .mockRejectedValue(new Error('useGetCasesMetrics: Test error')); - it('unhappy path', async () => { - const spy = jest.spyOn(api, 'getCasesMetrics'); - spy.mockImplementation(() => { - throw new Error('Oh on. this is impossible'); + const { waitForNextUpdate } = renderHook(() => useGetCasesMetrics(), { + wrapper: appMockRender.AppWrapper, }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCasesMetrics(), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); + await waitForNextUpdate(); - expect(result.current).toEqual({ - mttr: 0, - isLoading: false, - isError: true, - fetchCasesMetrics: result.current.fetchCasesMetrics, - }); - }); + expect(addError).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.tsx index a5cb116acc559..b43266e55340d 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.tsx @@ -5,88 +5,37 @@ * 2.0. */ -import { useCallback, useEffect, useState, useRef } from 'react'; - +import { useQuery } from '@tanstack/react-query'; import { useCasesContext } from '../components/cases_context/use_cases_context'; import * as i18n from './translations'; -import { useHttp, useToasts } from '../common/lib/kibana'; +import { useHttp } from '../common/lib/kibana'; import { getCasesMetrics } from '../api'; import { CasesMetrics } from './types'; +import { useCasesToast } from '../common/use_cases_toast'; +import { ServerError } from '../types'; +import { casesQueriesKeys } from './constants'; -interface CasesMetricsState extends CasesMetrics { - isLoading: boolean; - isError: boolean; -} - -const initialData: CasesMetricsState = { - mttr: 0, - isLoading: true, - isError: false, -}; - -export interface UseGetCasesMetrics extends CasesMetricsState { - fetchCasesMetrics: () => void; -} - -export const useGetCasesMetrics = (): UseGetCasesMetrics => { +export const useGetCasesMetrics = () => { const http = useHttp(); const { owner } = useCasesContext(); - const [casesMetricsState, setCasesMetricsState] = useState(initialData); - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); - - const fetchCasesMetrics = useCallback(async () => { - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); - setCasesMetricsState({ - ...initialData, - isLoading: true, - }); + const { showErrorToast } = useCasesToast(); - const response = await getCasesMetrics({ + return useQuery( + casesQueriesKeys.casesMetrics(), + () => { + const abortCtrlRef = new AbortController(); + return getCasesMetrics({ http, - signal: abortCtrlRef.current.signal, + signal: abortCtrlRef.signal, query: { owner, features: ['mttr'] }, }); - - if (!isCancelledRef.current) { - setCasesMetricsState({ - ...response, - isLoading: false, - isError: false, - }); - } - } catch (error) { - if (!isCancelledRef.current) { - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_TITLE } - ); - } - setCasesMetricsState({ - mttr: 0, - isLoading: false, - isError: true, - }); - } + }, + { + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_TITLE }); + }, } - }, [http, owner, toasts]); - - useEffect(() => { - fetchCasesMetrics(); - - return () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); - }; - }, [fetchCasesMetrics]); - - return { - ...casesMetricsState, - fetchCasesMetrics, - }; + ); }; + +export type UseGetCasesMetrics = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx index 3978e944db949..4f2572093a285 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx @@ -5,104 +5,56 @@ * 2.0. */ -import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { useGetCasesStatus, UseGetCasesStatus } from './use_get_cases_status'; -import { casesStatus } from './mock'; +import { renderHook } from '@testing-library/react-hooks'; +import { useGetCasesStatus } from './use_get_cases_status'; import * as api from '../api'; -import { TestProviders } from '../common/mock'; +import { AppMockRenderer, createAppMockRenderer } from '../common/mock'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; +import { useToasts } from '../common/lib/kibana'; jest.mock('../api'); jest.mock('../common/lib/kibana'); -describe('useGetCasesStatus', () => { +describe('useGetCasesMetrics', () => { const abortCtrl = new AbortController(); + const addSuccess = jest.fn(); + const addError = jest.fn(); + + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + let appMockRender: AppMockRenderer; + beforeEach(() => { + appMockRender = createAppMockRenderer(); jest.clearAllMocks(); - jest.restoreAllMocks(); }); - it('init', async () => { - const { result } = renderHook(() => useGetCasesStatus(), { - wrapper: ({ children }) => {children}, - }); - - await act(async () => { - expect(result.current).toEqual({ - countClosedCases: 0, - countOpenCases: 0, - countInProgressCases: 0, - isLoading: true, - isError: false, - fetchCasesStatus: result.current.fetchCasesStatus, - }); + it('calls the api when invoked with the correct parameters', async () => { + const spy = jest.spyOn(api, 'getCasesStatus'); + const { waitForNextUpdate } = renderHook(() => useGetCasesStatus(), { + wrapper: appMockRender.AppWrapper, }); - }); - it('calls getCasesStatus api', async () => { - const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus'); - await act(async () => { - const { waitForNextUpdate } = renderHook( - () => useGetCasesStatus(), - { - wrapper: ({ children }) => {children}, - } - ); + await waitForNextUpdate(); - await waitForNextUpdate(); - expect(spyOnGetCasesStatus).toBeCalledWith({ - http: expect.anything(), - signal: abortCtrl.signal, - query: { owner: [SECURITY_SOLUTION_OWNER] }, - }); + expect(spy).toHaveBeenCalledWith({ + http: expect.anything(), + signal: abortCtrl.signal, + query: { owner: [SECURITY_SOLUTION_OWNER] }, }); }); - it('fetch statuses', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCasesStatus(), - { - wrapper: ({ children }) => {children}, - } - ); + it('shows a toast error when the api return an error', async () => { + jest + .spyOn(api, 'getCasesStatus') + .mockRejectedValue(new Error('useGetCasesMetrics: Test error')); - await waitForNextUpdate(); - expect(result.current).toEqual({ - countClosedCases: casesStatus.countClosedCases, - countOpenCases: casesStatus.countOpenCases, - countInProgressCases: casesStatus.countInProgressCases, - isLoading: false, - isError: false, - fetchCasesStatus: result.current.fetchCasesStatus, - }); + const { waitForNextUpdate } = renderHook(() => useGetCasesStatus(), { + wrapper: appMockRender.AppWrapper, }); - }); - it('unhappy path', async () => { - const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus'); - spyOnGetCasesStatus.mockImplementation(() => { - throw new Error('Something went wrong'); - }); + await waitForNextUpdate(); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCasesStatus(), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - countClosedCases: 0, - countOpenCases: 0, - countInProgressCases: 0, - isLoading: false, - isError: true, - fetchCasesStatus: result.current.fetchCasesStatus, - }); - }); + expect(addError).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx index 6530236a2fee6..c2ba6659edcbd 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx @@ -5,94 +5,37 @@ * 2.0. */ -import { useCallback, useEffect, useState, useRef } from 'react'; - +import { useQuery } from '@tanstack/react-query'; import { useCasesContext } from '../components/cases_context/use_cases_context'; import * as i18n from './translations'; import { CasesStatus } from './types'; -import { useHttp, useToasts } from '../common/lib/kibana'; +import { useHttp } from '../common/lib/kibana'; import { getCasesStatus } from '../api'; +import { useCasesToast } from '../common/use_cases_toast'; +import { ServerError } from '../types'; +import { casesQueriesKeys } from './constants'; -interface CasesStatusState extends CasesStatus { - isLoading: boolean; - isError: boolean; -} - -const initialData: CasesStatusState = { - countClosedCases: 0, - countInProgressCases: 0, - countOpenCases: 0, - isLoading: true, - isError: false, -}; - -export interface UseGetCasesStatus extends CasesStatusState { - fetchCasesStatus: () => void; -} - -export const useGetCasesStatus = (): UseGetCasesStatus => { +export const useGetCasesStatus = () => { const http = useHttp(); const { owner } = useCasesContext(); - const [casesStatusState, setCasesStatusState] = useState(initialData); - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); - - const fetchCasesStatus = useCallback(async () => { - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); - setCasesStatusState({ - ...initialData, - isLoading: true, - }); + const { showErrorToast } = useCasesToast(); - const response = await getCasesStatus({ + return useQuery( + casesQueriesKeys.casesStatuses(), + () => { + const abortCtrlRef = new AbortController(); + return getCasesStatus({ http, - signal: abortCtrlRef.current.signal, + signal: abortCtrlRef.signal, query: { owner }, }); - - if (!isCancelledRef.current) { - setCasesStatusState({ - ...response, - isLoading: false, - isError: false, - }); - } - } catch (error) { - if (!isCancelledRef.current) { - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_TITLE } - ); - } - setCasesStatusState({ - countClosedCases: 0, - countInProgressCases: 0, - countOpenCases: 0, - isLoading: false, - isError: true, - }); - } + }, + { + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_TITLE }); + }, } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - fetchCasesStatus(); - - return () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { - ...casesStatusState, - fetchCasesStatus, - }; + ); }; + +export type UseGetCasesStatus = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_get_tags.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.tsx index 1696a9d1413a5..da56521536cbe 100644 --- a/x-pack/plugins/cases/public/containers/use_get_tags.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_tags.tsx @@ -10,15 +10,14 @@ import { useToasts } from '../common/lib/kibana'; import { useCasesContext } from '../components/cases_context/use_cases_context'; import { ServerError } from '../types'; import { getTags } from './api'; -import { CASE_TAGS_CACHE_KEY } from './constants'; +import { casesQueriesKeys } from './constants'; import * as i18n from './translations'; -export const useGetTags = (cacheKey?: string) => { +export const useGetTags = () => { const toasts = useToasts(); const { owner } = useCasesContext(); - const key = [...(cacheKey ? [cacheKey] : []), CASE_TAGS_CACHE_KEY]; return useQuery( - key, + casesQueriesKeys.tags(), () => { const abortCtrl = new AbortController(); return getTags(abortCtrl.signal, owner); diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts index de180b5970f3b..b2928295dbb37 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts @@ -10,7 +10,7 @@ import { UserProfileWithAvatar } from '@kbn/user-profile-components'; import * as i18n from '../translations'; import { useKibana, useToasts } from '../../common/lib/kibana'; import { ServerError } from '../../types'; -import { USER_PROFILES_CACHE_KEY, USER_PROFILES_BULK_GET_CACHE_KEY } from '../constants'; +import { casesQueriesKeys } from '../constants'; import { bulkGetUserProfiles } from './api'; const profilesToMap = (profiles: UserProfileWithAvatar[]): Map => @@ -25,7 +25,7 @@ export const useBulkGetUserProfiles = ({ uids }: { uids: string[] }) => { const toasts = useToasts(); return useQuery>( - [USER_PROFILES_CACHE_KEY, USER_PROFILES_BULK_GET_CACHE_KEY, uids], + casesQueriesKeys.userProfilesList(uids), () => { return bulkGetUserProfiles({ security, uids }); }, diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.ts index 37c29fa0b2d01..d6e3483672554 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.ts @@ -10,7 +10,7 @@ import { UserProfile } from '@kbn/security-plugin/common'; import * as i18n from '../translations'; import { useKibana, useToasts } from '../../common/lib/kibana'; import { ServerError } from '../../types'; -import { USER_PROFILES_CACHE_KEY, USER_PROFILES_GET_CURRENT_CACHE_KEY } from '../constants'; +import { casesQueriesKeys } from '../constants'; import { getCurrentUserProfile } from './api'; export const useGetCurrentUserProfile = () => { @@ -19,7 +19,7 @@ export const useGetCurrentUserProfile = () => { const toasts = useToasts(); return useQuery( - [USER_PROFILES_CACHE_KEY, USER_PROFILES_GET_CURRENT_CACHE_KEY], + casesQueriesKeys.currentUser(), () => { return getCurrentUserProfile({ security }); }, diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.ts index 26e03d0163c8e..74c492850acd4 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.ts @@ -14,7 +14,7 @@ import { DEFAULT_USER_SIZE, SEARCH_DEBOUNCE_MS } from '../../../common/constants import * as i18n from '../translations'; import { useKibana, useToasts } from '../../common/lib/kibana'; import { ServerError } from '../../types'; -import { USER_PROFILES_CACHE_KEY, USER_PROFILES_SUGGEST_CACHE_KEY } from '../constants'; +import { casesQueriesKeys } from '../constants'; import { suggestUserProfiles, SuggestUserProfilesArgs } from './api'; type Props = Omit & { onDebounce?: () => void }; @@ -49,11 +49,7 @@ export const useSuggestUserProfiles = ({ const toasts = useToasts(); return useQuery( - [ - USER_PROFILES_CACHE_KEY, - USER_PROFILES_SUGGEST_CACHE_KEY, - { name: debouncedName, owners, size }, - ], + casesQueriesKeys.suggestUsers({ name: debouncedName, owners, size }), () => { const abortCtrlRef = new AbortController(); return suggestUserProfiles({ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e0ff0ef2793f7..ce86f1de798de 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9286,7 +9286,6 @@ "xpack.cases.confirmDeleteCase.confirmQuestion": "Si vous supprimez {quantity, plural, =1 {ce cas} other {ces cas}}, toutes les données des cas connexes seront définitivement retirées et vous ne pourrez plus transmettre de données à un système de gestion des incidents externes. Voulez-vous vraiment continuer ?", "xpack.cases.confirmDeleteCase.deleteCase": "Supprimer {quantity, plural, =1 {le cas} other {les cas}}", "xpack.cases.confirmDeleteCase.deleteTitle": "Supprimer \"{caseTitle}\"", - "xpack.cases.confirmDeleteCase.selectedCases": "Supprimer \"{quantity, plural, =1 {{title}} other {{quantity} cas sélectionnés}}\"", "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "Le type d'objet \"{id}\" n'est pas enregistré.", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "Le type d'objet \"{id}\" est déjà enregistré.", "xpack.cases.connectors.card.createCommentWarningDesc": "Configurez les champs Create Comment URL et Create Comment Objects pour que le connecteur {connectorName} puisse partager les commentaires.", @@ -9296,7 +9295,6 @@ "xpack.cases.connectors.cases.externalIncidentUpdated": "(mis à jour le {date} par {user})", "xpack.cases.connectors.jira.unableToGetIssueMessage": "Impossible d'obtenir le problème ayant l'ID {id}", "xpack.cases.containers.closedCases": "Fermeture de {totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases} cas}} effectuée", - "xpack.cases.containers.deletedCases": "Suppression de {totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases} cas}} effectuée", "xpack.cases.containers.markInProgressCases": "Marquage effectué de {totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases} cas}} comme étant en cours", "xpack.cases.containers.pushToExternalService": "Envoyé avec succès à { serviceName }", "xpack.cases.containers.reopenedCases": "Ouverture de {totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases} cas}} effectuée", @@ -9328,7 +9326,6 @@ "xpack.cases.caseTable.changeStatus": "Modifier le statut", "xpack.cases.caseTable.closed": "Fermé", "xpack.cases.caseTable.closedCases": "Cas fermés", - "xpack.cases.caseTable.delete": "Supprimer", "xpack.cases.caseTable.incidentSystem": "Système de gestion des incidents", "xpack.cases.caseTable.inProgressCases": "Cas en cours", "xpack.cases.caseTable.noCases.body": "Créer un cas ou modifiez vos filtres.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8715883e4251c..5b56cc076172c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9273,7 +9273,6 @@ "xpack.cases.confirmDeleteCase.confirmQuestion": "{quantity, plural, =1 {このケース} other {これらのケース}}を削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", "xpack.cases.confirmDeleteCase.deleteCase": "{quantity, plural, other {ケース}}を削除", "xpack.cases.confirmDeleteCase.deleteTitle": "「{caseTitle}」を削除", - "xpack.cases.confirmDeleteCase.selectedCases": "\"{quantity, plural, =1 {{title}} other {選択した{quantity}個のケース}}\"を削除", "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」は登録されていません。", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」はすでに登録されています。", "xpack.cases.connectors.card.createCommentWarningDesc": "コメントを外部で共有するには、{connectorName}コネクターの[コメントURLを作成]および[コメントオブジェクトを作成]フィールドを構成します。", @@ -9283,7 +9282,6 @@ "xpack.cases.connectors.cases.externalIncidentUpdated": "({date}に{user}が更新)", "xpack.cases.connectors.jira.unableToGetIssueMessage": "ID {id}の問題を取得できません", "xpack.cases.containers.closedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}をクローズしました", - "xpack.cases.containers.deletedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を削除しました", "xpack.cases.containers.markInProgressCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を進行中に設定しました", "xpack.cases.containers.pushToExternalService": "{ serviceName }への送信が正常に完了しました", "xpack.cases.containers.reopenedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}をオープンしました", @@ -9315,7 +9313,6 @@ "xpack.cases.caseTable.changeStatus": "ステータスの変更", "xpack.cases.caseTable.closed": "終了", "xpack.cases.caseTable.closedCases": "終了したケース", - "xpack.cases.caseTable.delete": "削除", "xpack.cases.caseTable.incidentSystem": "インシデント管理システム", "xpack.cases.caseTable.inProgressCases": "進行中のケース", "xpack.cases.caseTable.noCases.body": "ケースを作成するか、フィルターを編集します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ee88a0ed92217..09fb50c164e9c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9291,7 +9291,6 @@ "xpack.cases.confirmDeleteCase.confirmQuestion": "删除{quantity, plural, =1 {此案例} other {这些案例}}即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", "xpack.cases.confirmDeleteCase.deleteCase": "删除{quantity, plural, other {案例}}", "xpack.cases.confirmDeleteCase.deleteTitle": "删除“{caseTitle}”", - "xpack.cases.confirmDeleteCase.selectedCases": "删除“{quantity, plural, =1 {{title}} other {选定的 {quantity} 个案例}}”", "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "对象类型“{id}”未注册。", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "已注册对象类型“{id}”。", "xpack.cases.connectors.card.createCommentWarningDesc": "为 {connectorName} 连接器配置“创建注释 URL”和“创建注释对象”字段以在外部共享注释。", @@ -9301,7 +9300,6 @@ "xpack.cases.connectors.cases.externalIncidentUpdated": "(由 {user} 于 {date}更新)", "xpack.cases.connectors.jira.unableToGetIssueMessage": "无法获取 ID 为 {id} 的问题", "xpack.cases.containers.closedCases": "已关闭{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.cases.containers.deletedCases": "已删除{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", "xpack.cases.containers.markInProgressCases": "已将{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}标记为进行中", "xpack.cases.containers.pushToExternalService": "已成功发送到 { serviceName }", "xpack.cases.containers.reopenedCases": "已打开{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", @@ -9333,7 +9331,6 @@ "xpack.cases.caseTable.changeStatus": "更改状态", "xpack.cases.caseTable.closed": "已关闭", "xpack.cases.caseTable.closedCases": "已关闭案例", - "xpack.cases.caseTable.delete": "删除", "xpack.cases.caseTable.incidentSystem": "事件管理系统", "xpack.cases.caseTable.inProgressCases": "进行中的案例", "xpack.cases.caseTable.noCases.body": "创建案例或编辑筛选。", From 61a300818056c36885874db0d805aa2ab37e24a5 Mon Sep 17 00:00:00 2001 From: Julian Gernun Date: Wed, 28 Sep 2022 19:39:44 +0200 Subject: [PATCH 133/172] add ownfocus flyout prop (#142085) --- .../sections/alerts_table/alerts_flyout/alerts_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx index e57e8c9902069..3ddefe1c4cf68 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx @@ -99,7 +99,7 @@ export const AlertsFlyout: React.FunctionComponent = ({ ); return ( - + {isLoading && } From 13b283ac4d1d4602e0c700599ef2449d90ee8a23 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 28 Sep 2022 13:46:32 -0400 Subject: [PATCH 134/172] [Fleet] Fix preconfigured ca_trusted_fingerprint in output (#142109) --- .../services/preconfiguration/outputs.test.ts | 27 +++++++++++++++++++ .../services/preconfiguration/outputs.ts | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts index a8eaa39d427df..e6fa2e008e4bb 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts @@ -112,6 +112,33 @@ describe('output preconfiguration', () => { expect(spyAgentPolicyServicBumpAllAgentPoliciesForOutput).not.toBeCalled(); }); + it('should create a preconfigured output with ca_trusted_fingerprint that does not exists', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await createOrUpdatePreconfiguredOutputs(soClient, esClient, [ + { + id: 'non-existing-output-1', + name: 'Output 1', + type: 'elasticsearch', + is_default: false, + is_default_monitoring: false, + hosts: ['http://test.fr'], + ca_trusted_fingerprint: 'testfingerprint', + }, + ]); + + expect(mockedOutputService.create).toBeCalled(); + expect(mockedOutputService.create).toBeCalledWith( + expect.anything(), + expect.objectContaining({ + ca_trusted_fingerprint: 'testfingerprint', + }), + expect.anything() + ); + expect(mockedOutputService.update).not.toBeCalled(); + expect(spyAgentPolicyServicBumpAllAgentPoliciesForOutput).not.toBeCalled(); + }); + it('should create preconfigured logstash output that does not exist', async () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.ts b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.ts index a61d8316dcc5f..d7f43ce181a42 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.ts @@ -78,7 +78,7 @@ export async function createOrUpdatePreconfiguredOutputs( config_yaml: configYaml ?? null, // Set value to null to update these fields on update ca_sha256: outputData.ca_sha256 ?? null, - ca_trusted_fingerprint: outputData.ca_sha256 ?? null, + ca_trusted_fingerprint: outputData.ca_trusted_fingerprint ?? null, ssl: outputData.ssl ?? null, }; From d05eca48e857118ad0ae357d93584051ce62a186 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:24:40 -0400 Subject: [PATCH 135/172] Removing action subgroups (#141794) --- x-pack/plugins/alerting/README.md | 21 +- .../plugins/alerting/common/alert_summary.ts | 1 - .../alerting/server/alert/alert.test.ts | 210 +--------------- x-pack/plugins/alerting/server/alert/alert.ts | 57 +---- .../lib/alert_summary_from_event_log.test.ts | 13 - .../lib/alert_summary_from_event_log.ts | 3 - .../alerting_event_logger.test.ts | 10 +- .../alerting_event_logger.ts | 12 +- ...eate_alert_event_log_record_object.test.ts | 4 - .../create_alert_event_log_record_object.ts | 5 +- .../server/rules_client/rules_client.ts | 3 +- .../server/rules_client/tests/disable.test.ts | 2 - .../tests/get_alert_summary.test.ts | 3 - .../task_runner/create_execution_handler.ts | 3 - .../alerting/server/task_runner/fixtures.ts | 16 +- .../alerting/server/task_runner/log_alerts.ts | 18 +- .../schedule_actions_for_alerts.ts | 23 +- .../server/task_runner/task_runner.test.ts | 95 +------ .../transform_action_params.test.ts | 7 +- .../task_runner/transform_action_params.ts | 4 - .../alerting/server/task_runner/types.ts | 1 - .../routes/rules/preview_rules_route.ts | 1 - .../preview/alert_instance_factory_stub.ts | 10 - .../plugins/alerts/server/alert_types.ts | 22 +- .../group2/tests/alerting/alerts.ts | 76 ------ .../spaces_only/tests/alerting/event_log.ts | 232 ------------------ .../spaces_only/tests/alerting/notify_when.ts | 91 ------- 27 files changed, 42 insertions(+), 901 deletions(-) diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index eed17e563860b..ffe17b91b8aeb 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -770,25 +770,10 @@ This factory returns an instance of `Alert`. The `Alert` class has the following |Method|Description| |---|---| |getState()|Get the current state of the alert.| -|scheduleActions(actionGroup, context)|Call this to schedule the execution of actions. The actionGroup is a string `id` that relates to the group of alert `actions` to execute and the context will be used for templating purposes. `scheduleActions` or `scheduleActionsWithSubGroup` should only be called once per alert.| -|scheduleActionsWithSubGroup(actionGroup, subgroup, context)|Call this to schedule the execution of actions within a subgroup. The actionGroup is a string `id` that relates to the group of alert `actions` to execute, the `subgroup` is a dynamic string that denotes a subgroup within the actionGroup and the context will be used for templating purposes. `scheduleActions` or `scheduleActionsWithSubGroup` should only be called once per alert.| -|replaceState(state)|Used to replace the current state of the alert. This doesn't work like React, the entire state must be provided. Use this feature as you see fit. The state that is set will persist between rule executions whenever you re-create an alert with the same id. The alert state will be erased when `scheduleActions` or `scheduleActionsWithSubGroup` aren't called during an execution.| +|scheduleActions(actionGroup, context)|Call this to schedule the execution of actions. The actionGroup is a string `id` that relates to the group of alert `actions` to execute and the context will be used for templating purposes. `scheduleActions` should only be called once per alert.| +|replaceState(state)|Used to replace the current state of the alert. This doesn't work like React, the entire state must be provided. Use this feature as you see fit. The state that is set will persist between rule executions whenever you re-create an alert with the same id. The alert state will be erased when `scheduleActions`isn't called during an execution.| |setContext(context)|Call this to set the context for this alert that is used for templating purposes. -### When should I use `scheduleActions` and `scheduleActionsWithSubGroup`? -The `scheduleActions` or `scheduleActionsWithSubGroup` methods are both used to achieve the same thing: schedule actions to be run under a specific action group. -It's important to note that when actions are scheduled for an alert, we check whether the alert was already active in this action group after the previous execution. If it was, then we might throttle the actions (adhering to the user's configuration), as we don't consider this a change in the alert. - -What happens though, if the alert _has_ changed, but they just happen to be in the same action group after this change? This is where subgroups come in. By specifying a subgroup (using the `scheduleActionsWithSubGroup` method), the alert becomes active within the action group, but it will also keep track of the subgroup. -If the subgroup changes, then the framework will treat the alert as if it had been placed in a new action group. It is important to note that we only use the subgroup to denote a change if both the current execution and the previous one specified a subgroup. - -You might wonder, why bother using a subgroup if you can just add a new action group? -Action Groups are static, and have to be define when the rule type is defined. -Action Subgroups are dynamic, and can be defined on the fly. - -This approach enables users to specify actions under specific action groups, but they can't specify actions that are specific to subgroups. -As subgroups fall under action groups, we will schedule the actions specified for the action group, but the subgroup allows the RuleType implementer to reuse the same action group for multiple different active subgroups. - ### When should I use `setContext`? `setContext` is intended to be used for setting context for recovered alerts. While rule type executors make the determination as to which alerts are active for an execution, the Alerting Framework automatically determines which alerts are recovered for an execution. `setContext` empowers rule type executors to provide additional contextual information for these recovered alerts that will be templated into actions. @@ -798,7 +783,7 @@ There needs to be a way to map rule context into action parameters. For this, we When an alert executes, the first argument is the `group` of actions to execute and the second is the context the rule exposes to templates. We iterate through each action parameter attributes recursively and render templates if they are a string. Templates have access to the following "variables": -- `context` - provided by context argument of `.scheduleActions(...)`, `.scheduleActionsWithSubGroup(...)` and `setContext(...)` on an alert. +- `context` - provided by context argument of `.scheduleActions(...)` and `setContext(...)` on an alert. - `state` - the alert's `state` provided by the most recent `replaceState` call on an alert. - `alertId` - the id of the rule - `alertInstanceId` - the alert id diff --git a/x-pack/plugins/alerting/common/alert_summary.ts b/x-pack/plugins/alerting/common/alert_summary.ts index b04ce59eed1bd..fc35e3403fe92 100644 --- a/x-pack/plugins/alerting/common/alert_summary.ts +++ b/x-pack/plugins/alerting/common/alert_summary.ts @@ -35,6 +35,5 @@ export interface AlertStatus { status: AlertStatusValues; muted: boolean; actionGroupId?: string; - actionSubgroup?: string; activeStartDate?: string; } diff --git a/x-pack/plugins/alerting/server/alert/alert.test.ts b/x-pack/plugins/alerting/server/alert/alert.test.ts index eae1b18164b0f..d9e05e55a67fd 100644 --- a/x-pack/plugins/alerting/server/alert/alert.test.ts +++ b/x-pack/plugins/alerting/server/alert/alert.test.ts @@ -81,10 +81,10 @@ describe('isThrottled', () => { }); }); -describe('scheduledActionGroupOrSubgroupHasChanged()', () => { +describe('scheduledActionGroupHasChanged()', () => { test('should be false if no last scheduled and nothing scheduled', () => { const alert = new Alert('1'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); + expect(alert.scheduledActionGroupHasChanged()).toEqual(false); }); test('should be false if group does not change', () => { @@ -97,54 +97,13 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }, }); alert.scheduleActions('default'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); - }); - - test('should be false if group and subgroup does not change', () => { - const alert = new Alert('1', { - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'subgroup', - }, - }, - }); - alert.scheduleActionsWithSubGroup('default', 'subgroup'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); - }); - - test('should be false if group does not change and subgroup goes from undefined to defined', () => { - const alert = new Alert('1', { - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - }, - }, - }); - alert.scheduleActionsWithSubGroup('default', 'subgroup'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); - }); - - test('should be false if group does not change and subgroup goes from defined to undefined', () => { - const alert = new Alert('1', { - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'subgroup', - }, - }, - }); - alert.scheduleActions('default'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); + expect(alert.scheduledActionGroupHasChanged()).toEqual(false); }); test('should be true if no last scheduled and has scheduled action', () => { const alert = new Alert('1'); alert.scheduleActions('default'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); + expect(alert.scheduledActionGroupHasChanged()).toEqual(true); }); test('should be true if group does change', () => { @@ -157,35 +116,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }, }); alert.scheduleActions('penguin'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); - }); - - test('should be true if group does change and subgroup does change', () => { - const alert = new Alert('1', { - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'subgroup', - }, - }, - }); - alert.scheduleActionsWithSubGroup('penguin', 'fish'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); - }); - - test('should be true if group does not change and subgroup does change', () => { - const alert = new Alert('1', { - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'subgroup', - }, - }, - }); - alert.scheduleActionsWithSubGroup('default', 'fish'); - expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); + expect(alert.scheduledActionGroupHasChanged()).toEqual(true); }); }); @@ -296,137 +227,6 @@ describe('scheduleActions()', () => { }); }); -describe('scheduleActionsWithSubGroup()', () => { - test('makes hasScheduledActions() return true', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - }, - }, - }); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.hasScheduledActions()).toEqual(true); - }); - - test('makes isThrottled() return true when throttled and subgroup is the same', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'subgroup', - }, - }, - }); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.isThrottled('1m')).toEqual(true); - }); - - test('makes isThrottled() return true when throttled and last schedule had no subgroup', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - }, - }, - }); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.isThrottled('1m')).toEqual(true); - }); - - test('makes isThrottled() return false when throttled and subgroup is the different', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - subgroup: 'prev-subgroup', - }, - }, - }); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.isThrottled('1m')).toEqual(false); - }); - - test('make isThrottled() return false when throttled expired', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: { - lastScheduledActions: { - date: new Date(), - group: 'default', - }, - }, - }); - clock.tick(120000); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.isThrottled('1m')).toEqual(false); - }); - - test('makes getScheduledActionOptions() return given options', () => { - const alert = new Alert('1', { - state: { foo: true }, - meta: {}, - }); - alert - .replaceState({ otherField: true }) - .scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(alert.getScheduledActionOptions()).toEqual({ - actionGroup: 'default', - subgroup: 'subgroup', - context: { field: true }, - state: { otherField: true }, - }); - }); - - test('cannot schdule for execution twice', () => { - const alert = new Alert('1'); - alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(() => - alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Alert instance execution has already been scheduled, cannot schedule twice"` - ); - }); - - test('cannot schdule for execution twice with different subgroups', () => { - const alert = new Alert('1'); - alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); - expect(() => - alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Alert instance execution has already been scheduled, cannot schedule twice"` - ); - }); - - test('cannot schdule for execution twice whether there are subgroups', () => { - const alert = new Alert('1'); - alert.scheduleActions('default', { field: true }); - expect(() => - alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Alert instance execution has already been scheduled, cannot schedule twice"` - ); - }); -}); - describe('replaceState()', () => { test('replaces previous state', () => { const alert = new Alert('1', { diff --git a/x-pack/plugins/alerting/server/alert/alert.ts b/x-pack/plugins/alerting/server/alert/alert.ts index bf29cacf556c1..e24b15de41db4 100644 --- a/x-pack/plugins/alerting/server/alert/alert.ts +++ b/x-pack/plugins/alerting/server/alert/alert.ts @@ -23,7 +23,6 @@ interface ScheduledExecutionOptions< ActionGroupIds extends string = DefaultActionGroupId > { actionGroup: ActionGroupIds; - subgroup?: string; context: Context; state: State; } @@ -34,13 +33,7 @@ export type PublicAlert< ActionGroupIds extends string = DefaultActionGroupId > = Pick< Alert, - | 'getState' - | 'replaceState' - | 'scheduleActions' - | 'scheduleActionsWithSubGroup' - | 'setContext' - | 'getContext' - | 'hasContext' + 'getState' | 'replaceState' | 'scheduleActions' | 'setContext' | 'getContext' | 'hasContext' >; export class Alert< @@ -80,10 +73,6 @@ export class Alert< this.meta.lastScheduledActions, this.scheduledExecutionOptions ) && - this.scheduledActionSubgroupIsUnchanged( - this.meta.lastScheduledActions, - this.scheduledExecutionOptions - ) && this.meta.lastScheduledActions.date.getTime() + throttleMills > Date.now() ) { return true; @@ -91,7 +80,7 @@ export class Alert< return false; } - scheduledActionGroupOrSubgroupHasChanged(): boolean { + scheduledActionGroupHasChanged(): boolean { if (!this.meta.lastScheduledActions && this.scheduledExecutionOptions) { // it is considered a change when there are no previous scheduled actions // and new scheduled actions @@ -100,18 +89,11 @@ export class Alert< if (this.meta.lastScheduledActions && this.scheduledExecutionOptions) { // compare previous and new scheduled actions if both exist - return ( - !this.scheduledActionGroupIsUnchanged( - this.meta.lastScheduledActions, - this.scheduledExecutionOptions - ) || - !this.scheduledActionSubgroupIsUnchanged( - this.meta.lastScheduledActions, - this.scheduledExecutionOptions - ) + return !this.scheduledActionGroupIsUnchanged( + this.meta.lastScheduledActions, + this.scheduledExecutionOptions ); } - // no previous and no new scheduled actions return false; } @@ -123,15 +105,6 @@ export class Alert< return lastScheduledActions.group === scheduledExecutionOptions.actionGroup; } - private scheduledActionSubgroupIsUnchanged( - lastScheduledActions: NonNullable, - scheduledExecutionOptions: ScheduledExecutionOptions - ) { - return lastScheduledActions.subgroup && scheduledExecutionOptions.subgroup - ? lastScheduledActions.subgroup === scheduledExecutionOptions.subgroup - : true; - } - getLastScheduledActions() { return this.meta.lastScheduledActions; } @@ -168,22 +141,6 @@ export class Alert< return this; } - scheduleActionsWithSubGroup( - actionGroup: ActionGroupIds, - subgroup: string, - context: Context = {} as Context - ) { - this.ensureHasNoScheduledActions(); - this.setContext(context); - this.scheduledExecutionOptions = { - actionGroup, - subgroup, - context, - state: this.state, - }; - return this; - } - setContext(context: Context) { this.context = context; return this; @@ -200,8 +157,8 @@ export class Alert< return this; } - updateLastScheduledActions(group: ActionGroupIds, subgroup?: string) { - this.meta.lastScheduledActions = { group, subgroup, date: new Date() }; + updateLastScheduledActions(group: ActionGroupIds) { + this.meta.lastScheduledActions = { group, date: new Date() }; } /** diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts index d2040f8e63f3a..56a862f2ad6ca 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts @@ -121,14 +121,12 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "alert-2": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -233,7 +231,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -274,7 +271,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -314,7 +310,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -355,7 +350,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": "action group A", - "actionSubgroup": undefined, "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", @@ -396,7 +390,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", @@ -437,7 +430,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": "action group B", - "actionSubgroup": undefined, "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", @@ -476,7 +468,6 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": "action group A", - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "Active", @@ -519,14 +510,12 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": "action group A", - "actionSubgroup": undefined, "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": true, "status": "Active", }, "alert-2": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -576,14 +565,12 @@ describe('alertSummaryFromEventLog', () => { "alerts": Object { "alert-1": Object { "actionGroupId": "action group B", - "actionSubgroup": undefined, "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", }, "alert-2": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index 54ac23bf94f2a..d8e5f4dea9b41 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -87,14 +87,12 @@ export function alertSummaryFromEventLog(params: AlertSummaryFromEventLogParams) case EVENT_LOG_ACTIONS.activeInstance: status.status = 'Active'; status.actionGroupId = event?.kibana?.alerting?.action_group_id; - status.actionSubgroup = event?.kibana?.alerting?.action_subgroup; break; case LEGACY_EVENT_LOG_ACTIONS.resolvedInstance: case EVENT_LOG_ACTIONS.recoveredInstance: status.status = 'OK'; status.activeStartDate = undefined; status.actionGroupId = undefined; - status.actionSubgroup = undefined; } } @@ -153,7 +151,6 @@ function getAlertStatus(alerts: Map, alertId: string): Aler status: 'OK', muted: false, actionGroupId: undefined, - actionSubgroup: undefined, activeStartDate: undefined, }; alerts.set(alertId, status); diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index 91904901adcfd..8fe081864b118 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -59,9 +59,8 @@ const contextWithName = { ...contextWithScheduleDelay, ruleName: 'my-super-cool- const alert = { action: EVENT_LOG_ACTIONS.activeInstance, id: 'aaabbb', - message: `.test-rule-type:123: 'my rule' active alert: 'aaabbb' in actionGroup: 'aGroup'; actionSubGroup: 'bSubGroup'`, + message: `.test-rule-type:123: 'my rule' active alert: 'aaabbb' in actionGroup: 'aGroup';`, group: 'aGroup', - subgroup: 'bSubgroup', state: { start: '2020-01-01T02:00:00.000Z', end: '2020-01-01T03:00:00.000Z', @@ -74,7 +73,6 @@ const action = { typeId: '.email', alertId: '123', alertGroup: 'aGroup', - alertSubgroup: 'bSubgroup', }; describe('AlertingEventLogger', () => { @@ -1006,14 +1004,13 @@ describe('createAlertRecord', () => { expect(record.event?.end).toEqual(alert.state.end); expect(record.event?.duration).toEqual(alert.state.duration); expect(record.message).toEqual( - `.test-rule-type:123: 'my rule' active alert: 'aaabbb' in actionGroup: 'aGroup'; actionSubGroup: 'bSubGroup'` + `.test-rule-type:123: 'my rule' active alert: 'aaabbb' in actionGroup: 'aGroup';` ); expect(record.kibana?.alert?.rule?.rule_type_id).toEqual(contextWithName.ruleType.id); expect(record.kibana?.alert?.rule?.consumer).toEqual(contextWithName.consumer); expect(record.kibana?.alert?.rule?.execution?.uuid).toEqual(contextWithName.executionId); expect(record.kibana?.alerting?.instance_id).toEqual(alert.id); expect(record.kibana?.alerting?.action_group_id).toEqual(alert.group); - expect(record.kibana?.alerting?.action_subgroup).toEqual(alert.subgroup); expect(record.kibana?.saved_objects).toEqual([ { id: contextWithName.ruleId, @@ -1059,14 +1056,13 @@ describe('createActionExecuteRecord', () => { expect(record.event?.kind).toEqual('alert'); expect(record.event?.category).toEqual([contextWithName.ruleType.producer]); expect(record.message).toEqual( - `alert: test:123: 'my-super-cool-rule' instanceId: '123' scheduled actionGroup(subgroup): 'aGroup(bSubgroup)' action: .email:abc` + `alert: test:123: 'my-super-cool-rule' instanceId: '123' scheduled actionGroup: 'aGroup' action: .email:abc` ); expect(record.kibana?.alert?.rule?.rule_type_id).toEqual(contextWithName.ruleType.id); expect(record.kibana?.alert?.rule?.consumer).toEqual(contextWithName.consumer); expect(record.kibana?.alert?.rule?.execution?.uuid).toEqual(contextWithName.executionId); expect(record.kibana?.alerting?.instance_id).toEqual(action.alertId); expect(record.kibana?.alerting?.action_group_id).toEqual(action.alertGroup); - expect(record.kibana?.alerting?.action_subgroup).toEqual(action.alertSubgroup); expect(record.kibana?.saved_objects).toEqual([ { id: contextWithName.ruleId, diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts index 96be872d81b8c..18e4044172eb4 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts @@ -47,7 +47,6 @@ interface AlertOpts { id: string; message: string; group?: string; - subgroup?: string; state?: AlertInstanceState; } @@ -56,7 +55,6 @@ interface ActionOpts { typeId: string; alertId: string; alertGroup?: string; - alertSubgroup?: string; } export class AlertingEventLogger { @@ -232,7 +230,6 @@ export function createAlertRecord(context: RuleContextOpts, alert: AlertOpts) { state: alert.state, instanceId: alert.id, group: alert.group, - subgroup: alert.subgroup, message: alert.message, savedObjects: [ { @@ -257,14 +254,7 @@ export function createActionExecuteRecord(context: RuleContextOpts, action: Acti action: EVENT_LOG_ACTIONS.executeAction, instanceId: action.alertId, group: action.alertGroup, - subgroup: action.alertSubgroup, - message: `alert: ${context.ruleType.id}:${context.ruleId}: '${context.ruleName}' instanceId: '${ - action.alertId - }' scheduled ${ - action.alertSubgroup - ? `actionGroup(subgroup): '${action.alertGroup}(${action.alertSubgroup})'` - : `actionGroup: '${action.alertGroup}'` - } action: ${action.typeId}:${action.id}`, + message: `alert: ${context.ruleType.id}:${context.ruleId}: '${context.ruleName}' instanceId: '${action.alertId}' scheduled actionGroup: '${action.alertGroup}' action: ${action.typeId}:${action.id}`, savedObjects: [ { id: context.ruleId, diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts index ba16b7c553e86..8f73ba3a56860 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts @@ -99,7 +99,6 @@ describe('createAlertEventLogRecordObject', () => { group: 'group 1', message: 'message text here', namespace: 'default', - subgroup: 'subgroup value', state: { start: '1970-01-01T00:00:00.000Z', end: '1970-01-01T00:05:00.000Z', @@ -136,7 +135,6 @@ describe('createAlertEventLogRecordObject', () => { }, alerting: { action_group_id: 'group 1', - action_subgroup: 'subgroup value', instance_id: 'test1', }, saved_objects: [ @@ -174,7 +172,6 @@ describe('createAlertEventLogRecordObject', () => { group: 'group 1', message: 'action execution start', namespace: 'default', - subgroup: 'subgroup value', state: { start: '1970-01-01T00:00:00.000Z', end: '1970-01-01T00:05:00.000Z', @@ -216,7 +213,6 @@ describe('createAlertEventLogRecordObject', () => { }, alerting: { action_group_id: 'group 1', - action_subgroup: 'subgroup value', instance_id: 'test1', }, saved_objects: [ diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts index cd7eda500d15b..a0f229c0b46d9 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts @@ -23,7 +23,6 @@ interface CreateAlertEventLogRecordParams { message?: string; state?: AlertInstanceState; group?: string; - subgroup?: string; namespace?: string; timestamp?: string; task?: { @@ -48,18 +47,16 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor task, ruleId, group, - subgroup, namespace, consumer, spaceId, } = params; const alerting = - params.instanceId || group || subgroup + params.instanceId || group ? { alerting: { ...(params.instanceId ? { instance_id: params.instanceId } : {}), ...(group ? { action_group_id: group } : {}), - ...(subgroup ? { action_subgroup: subgroup } : {}), }, } : undefined; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index f865a0472465a..89ce20b59ae9b 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -2404,7 +2404,7 @@ export class RulesClient { const recoveredAlertInstanceIds = Object.keys(recoveredAlertInstances); for (const instanceId of recoveredAlertInstanceIds) { - const { group: actionGroup, subgroup: actionSubgroup } = + const { group: actionGroup } = recoveredAlertInstances[instanceId].getLastScheduledActions() ?? {}; const instanceState = recoveredAlertInstances[instanceId].getState(); const message = `instance '${instanceId}' has recovered due to the rule was disabled`; @@ -2419,7 +2419,6 @@ export class RulesClient { message, state: instanceState, group: actionGroup, - subgroup: actionSubgroup, namespace: this.namespace, spaceId: this.spaceId, savedObjects: [ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index cca7cd0d70322..558d33ecca87c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -250,7 +250,6 @@ describe('disable()', () => { meta: { lastScheduledActions: { group: 'default', - subgroup: 'newSubgroup', date: new Date().toISOString(), }, }, @@ -319,7 +318,6 @@ describe('disable()', () => { }, alerting: { action_group_id: 'default', - action_subgroup: 'newSubgroup', instance_id: '1', }, saved_objects: [ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index 6dcd64565c562..4aa7ae40f8782 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -156,21 +156,18 @@ describe('getAlertSummary()', () => { "alerts": Object { "alert-currently-active": Object { "actionGroupId": "action group A", - "actionSubgroup": undefined, "activeStartDate": "2019-02-12T21:01:22.479Z", "muted": false, "status": "Active", }, "alert-muted-no-activity": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "alert-previously-active": Object { "actionGroupId": undefined, - "actionSubgroup": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 7146ef4a5225c..51ba1404b2a4f 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -65,7 +65,6 @@ export function createExecutionHandler< return async ({ actionGroup, - actionSubgroup, context, state, ruleRunMetricsStore, @@ -92,7 +91,6 @@ export function createExecutionHandler< alertInstanceId: alertId, alertActionGroup: actionGroup, alertActionGroupName: ruleTypeActionGroups.get(actionGroup)!, - alertActionSubgroup: actionSubgroup, context, actionParams: action.params, actionId: action.id, @@ -203,7 +201,6 @@ export function createExecutionHandler< typeId: actionTypeId, alertId, alertGroup: actionGroup, - alertSubgroup: actionSubgroup, }); } diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 39b90efbc786e..0a90456dbdad6 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -178,7 +178,7 @@ export const mockTaskInstance = () => ({ ownerId: null, }); -export const generateAlertOpts = ({ action, group, subgroup, state, id }: GeneratorParams = {}) => { +export const generateAlertOpts = ({ action, group, state, id }: GeneratorParams = {}) => { id = id ?? '1'; let message: string = ''; switch (action) { @@ -186,9 +186,7 @@ export const generateAlertOpts = ({ action, group, subgroup, state, id }: Genera message = `test:1: 'rule-name' created new alert: '${id}'`; break; case EVENT_LOG_ACTIONS.activeInstance: - message = subgroup - ? `test:1: 'rule-name' active alert: '${id}' in actionGroup(subgroup): 'default(${subgroup})'` - : `test:1: 'rule-name' active alert: '${id}' in actionGroup: 'default'`; + message = `test:1: 'rule-name' active alert: '${id}' in actionGroup: 'default'`; break; case EVENT_LOG_ACTIONS.recoveredInstance: message = `test:1: 'rule-name' alert '${id}' has recovered`; @@ -200,21 +198,14 @@ export const generateAlertOpts = ({ action, group, subgroup, state, id }: Genera message, state, ...(group ? { group } : {}), - ...(subgroup ? { subgroup } : {}), }; }; -export const generateActionOpts = ({ - subgroup, - id, - alertGroup, - alertId, -}: GeneratorParams = {}) => ({ +export const generateActionOpts = ({ id, alertGroup, alertId }: GeneratorParams = {}) => ({ id: id ?? '1', typeId: 'action', alertId: alertId ?? '1', alertGroup: alertGroup ?? 'default', - ...(subgroup ? { alertSubgroup: subgroup } : {}), }); export const generateRunnerResult = ({ @@ -280,7 +271,6 @@ export const generateAlertInstance = ({ id, duration, start }: GeneratorParams = lastScheduledActions: { date: new Date(DATE_1970), group: 'default', - subgroup: undefined, }, }, state: { diff --git a/x-pack/plugins/alerting/server/task_runner/log_alerts.ts b/x-pack/plugins/alerting/server/task_runner/log_alerts.ts index 87da1fb67f332..2abe72ed06cb5 100644 --- a/x-pack/plugins/alerting/server/task_runner/log_alerts.ts +++ b/x-pack/plugins/alerting/server/task_runner/log_alerts.ts @@ -92,8 +92,7 @@ export function logAlerts< ruleRunMetricsStore.setNumberOfRecoveredAlerts(recoveredAlertIds.length); for (const id of recoveredAlertIds) { - const { group: actionGroup, subgroup: actionSubgroup } = - recoveredAlerts[id].getLastScheduledActions() ?? {}; + const { group: actionGroup } = recoveredAlerts[id].getLastScheduledActions() ?? {}; const state = recoveredAlerts[id].getState(); const message = `${ruleLogPrefix} alert '${id}' has recovered`; @@ -101,41 +100,32 @@ export function logAlerts< action: EVENT_LOG_ACTIONS.recoveredInstance, id, group: actionGroup, - subgroup: actionSubgroup, message, state, }); } for (const id of newAlertIds) { - const { actionGroup, subgroup: actionSubgroup } = - activeAlerts[id].getScheduledActionOptions() ?? {}; + const { actionGroup } = activeAlerts[id].getScheduledActionOptions() ?? {}; const state = activeAlerts[id].getState(); const message = `${ruleLogPrefix} created new alert: '${id}'`; alertingEventLogger.logAlert({ action: EVENT_LOG_ACTIONS.newInstance, id, group: actionGroup, - subgroup: actionSubgroup, message, state, }); } for (const id of activeAlertIds) { - const { actionGroup, subgroup: actionSubgroup } = - activeAlerts[id].getScheduledActionOptions() ?? {}; + const { actionGroup } = activeAlerts[id].getScheduledActionOptions() ?? {}; const state = activeAlerts[id].getState(); - const message = `${ruleLogPrefix} active alert: '${id}' in ${ - actionSubgroup - ? `actionGroup(subgroup): '${actionGroup}(${actionSubgroup})'` - : `actionGroup: '${actionGroup}'` - }`; + const message = `${ruleLogPrefix} active alert: '${id}' in actionGroup: '${actionGroup}'`; alertingEventLogger.logAlert({ action: EVENT_LOG_ACTIONS.activeInstance, id, group: actionGroup, - subgroup: actionSubgroup, message, state, }); diff --git a/x-pack/plugins/alerting/server/task_runner/schedule_actions_for_alerts.ts b/x-pack/plugins/alerting/server/task_runner/schedule_actions_for_alerts.ts index e0cc6d5b81c3a..8c1d62ecf4e32 100644 --- a/x-pack/plugins/alerting/server/task_runner/schedule_actions_for_alerts.ts +++ b/x-pack/plugins/alerting/server/task_runner/schedule_actions_for_alerts.ts @@ -49,16 +49,8 @@ export async function scheduleActionsForAlerts< notifyWhen ); if (executeAction && alert.hasScheduledActions()) { - const { actionGroup, subgroup: actionSubgroup, state } = alert.getScheduledActionOptions()!; - await executeAlert( - alertId, - alert, - executionHandler, - ruleRunMetricsStore, - actionGroup, - state, - actionSubgroup - ); + const { actionGroup, state } = alert.getScheduledActionOptions()!; + await executeAlert(alertId, alert, executionHandler, ruleRunMetricsStore, actionGroup, state); } } @@ -94,14 +86,12 @@ async function executeAlert< executionHandler: ExecutionHandler, ruleRunMetricsStore: RuleRunMetricsStore, actionGroup: ActionGroupIds | RecoveryActionGroupId, - state: InstanceState, - actionSubgroup?: string + state: InstanceState ) { - alert.updateLastScheduledActions(actionGroup, actionSubgroup); + alert.updateLastScheduledActions(actionGroup); alert.unscheduleActions(); return executionHandler({ actionGroup, - actionSubgroup, context: alert.getContext(), state, alertId, @@ -133,10 +123,7 @@ function shouldExecuteAction< muted ? 'muted' : 'throttled' }` ); - } else if ( - notifyWhen === 'onActionGroupChange' && - !alert.scheduledActionGroupOrSubgroupHasChanged() - ) { + } else if (notifyWhen === 'onActionGroupChange' && !alert.scheduledActionGroupHasChanged()) { executeAction = false; logger.debug( `skipping scheduling of actions for '${alertId}' in rule ${ruleLabel}: alert is active but action group has not changed` diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 4ce85f54c3dc5..a199fb3b55998 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -312,9 +312,7 @@ describe('Task Runner', () => { AlertInstanceContext, string >) => { - executorServices.alertFactory - .create('1') - .scheduleActionsWithSubGroup('default', 'subDefault'); + executorServices.alertFactory.create('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( @@ -360,7 +358,6 @@ describe('Task Runner', () => { generateAlertOpts({ action: EVENT_LOG_ACTIONS.newInstance, group: 'default', - subgroup: 'subDefault', state: { start: DATE_1970, duration: '0' }, }) ); @@ -369,14 +366,10 @@ describe('Task Runner', () => { generateAlertOpts({ action: EVENT_LOG_ACTIONS.activeInstance, group: 'default', - subgroup: 'subDefault', state: { start: DATE_1970, duration: '0' }, }) ); - expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith( - 1, - generateActionOpts({ subgroup: 'subDefault' }) - ); + expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith(1, generateActionOpts()); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -859,90 +852,6 @@ describe('Task Runner', () => { } ); - test.each(ephemeralTestParams)( - 'actionsPlugin.execute is called when notifyWhen=onActionGroupChange and alert state subgroup has changed %s', - async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => { - customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue( - true - ); - - customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue( - true - ); - ruleType.executor.mockImplementation( - async ({ - services: executorServices, - }: RuleExecutorOptions< - RuleTypeParams, - RuleTypeState, - AlertInstanceState, - AlertInstanceContext, - string - >) => { - executorServices.alertFactory - .create('1') - .scheduleActionsWithSubGroup('default', 'subgroup1'); - } - ); - const taskRunner = new TaskRunner( - ruleType, - { - ...mockedTaskInstance, - state: { - ...mockedTaskInstance.state, - alertInstances: { - '1': { - meta: { - lastScheduledActions: { - group: 'default', - subgroup: 'newSubgroup', - date: new Date().toISOString(), - }, - }, - state: { bar: false }, - }, - }, - }, - }, - customTaskRunnerFactoryInitializerParams, - inMemoryMetrics - ); - expect(AlertingEventLogger).toHaveBeenCalled(); - - rulesClient.get.mockResolvedValue({ - ...mockedRuleTypeSavedObject, - notifyWhen: 'onActionGroupChange', - }); - encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); - await taskRunner.run(); - - testAlertingEventLogCalls({ - activeAlerts: 1, - triggeredActions: 1, - generatedActions: 1, - status: 'active', - logAlert: 1, - logAction: 1, - }); - expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith( - 1, - generateAlertOpts({ - action: EVENT_LOG_ACTIONS.activeInstance, - state: { bar: false }, - group: 'default', - subgroup: 'subgroup1', - }) - ); - expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith( - 1, - generateActionOpts({ subgroup: 'subgroup1' }) - ); - - expect(enqueueFunction).toHaveBeenCalledTimes(1); - expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); - } - ); - test.each(ephemeralTestParams)( 'includes the apiKey in the request used to initialize the actionsClient %s', async ( diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts index 6235e630ba0bb..ababe16dea378 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts @@ -417,7 +417,6 @@ test('rule variables are passed to templates', () => { alertInstanceId: '2', alertActionGroup: 'action-group', alertActionGroupName: 'Action Group', - alertActionSubgroup: 'subgroup', alertParams: {}, }); expect(result).toMatchInlineSnapshot(` @@ -429,8 +428,7 @@ test('rule variables are passed to templates', () => { test('rule alert variables are passed to templates', () => { const actionParams = { - message: - 'Value "{{alert.id}}", "{{alert.actionGroup}}", "{{alert.actionGroupName}}" and "{{alert.actionSubgroup}}" exist', + message: 'Value "{{alert.id}}", "{{alert.actionGroup}}" and "{{alert.actionGroupName}}" exist', }; const result = transformActionParams({ actionsPlugin, @@ -447,12 +445,11 @@ test('rule alert variables are passed to templates', () => { alertInstanceId: '2', alertActionGroup: 'action-group', alertActionGroupName: 'Action Group', - alertActionSubgroup: 'subgroup', alertParams: {}, }); expect(result).toMatchInlineSnapshot(` Object { - "message": "Value \\"2\\", \\"action-group\\", \\"Action Group\\" and \\"subgroup\\" exist", + "message": "Value \\"2\\", \\"action-group\\" and \\"Action Group\\" exist", } `); }); diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 322a16c1c238e..61ec54c48886f 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -25,7 +25,6 @@ interface TransformActionParamsOptions { alertInstanceId: string; alertActionGroup: string; alertActionGroupName: string; - alertActionSubgroup?: string; actionParams: RuleActionParams; alertParams: RuleTypeParams; state: AlertInstanceState; @@ -44,7 +43,6 @@ export function transformActionParams({ tags, alertInstanceId, alertActionGroup, - alertActionSubgroup, alertActionGroupName, context, actionParams, @@ -63,7 +61,6 @@ export function transformActionParams({ alertInstanceId, alertActionGroup, alertActionGroupName, - alertActionSubgroup, context, date: new Date().toISOString(), state, @@ -80,7 +77,6 @@ export function transformActionParams({ id: alertInstanceId, actionGroup: alertActionGroup, actionGroupName: alertActionGroupName, - actionSubgroup: alertActionSubgroup, }, }; return actionsPlugin.renderActionParameterTemplates( diff --git a/x-pack/plugins/alerting/server/task_runner/types.ts b/x-pack/plugins/alerting/server/task_runner/types.ts index 87bd65b028481..ce439fa3b3b0a 100644 --- a/x-pack/plugins/alerting/server/task_runner/types.ts +++ b/x-pack/plugins/alerting/server/task_runner/types.ts @@ -116,7 +116,6 @@ export interface CreateExecutionHandlerOptions< export interface ExecutionHandlerOptions { actionGroup: ActionGroupIds; - actionSubgroup?: string; alertId: string; context: AlertInstanceContext; state: AlertInstanceState; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index a147118b782d5..15b6ffe47d349 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -182,7 +182,6 @@ export const previewRulesRoute = async ( | 'getState' | 'replaceState' | 'scheduleActions' - | 'scheduleActionsWithSubGroup' | 'setContext' | 'getContext' | 'hasContext' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts index b73119d15077a..a3cf4ebc95d7c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts @@ -37,16 +37,6 @@ export const alertInstanceFactoryStub = < meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }); }, - scheduleActionsWithSubGroup( - actionGroup: TActionGroupIds, - subgroup: string, - alertcontext: TInstanceContext - ) { - return new Alert('', { - state: {} as TInstanceState, - meta: { lastScheduledActions: { group: 'default', date: new Date() } }, - }); - }, setContext(alertContext: TInstanceContext) { return new Alert('', { state: {} as TInstanceState, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index 3d68c420e4442..068c516618616 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -109,31 +109,21 @@ async function alwaysFiringExecutor(alertExecutorOptions: any) { rule, } = alertExecutorOptions; let group: string | null = 'default'; - let subgroup: string | null = null; const alertInfo = { alertId, spaceId, namespace, name, tags, createdBy, updatedBy, ...rule }; if (params.groupsToScheduleActionsInSeries) { const index = state.groupInSeriesIndex || 0; - const [scheduledGroup, scheduledSubgroup] = ( - params.groupsToScheduleActionsInSeries[index] ?? '' - ).split(':'); + const [scheduledGroup] = (params.groupsToScheduleActionsInSeries[index] ?? '').split(':'); group = scheduledGroup; - subgroup = scheduledSubgroup; } if (group) { const instance = services.alertFactory.create('1').replaceState({ instanceStateValue: true }); - if (subgroup) { - instance.scheduleActionsWithSubGroup(group, subgroup, { - instanceContextValue: true, - }); - } else { - instance.scheduleActions(group, { - instanceContextValue: true, - }); - } + instance.scheduleActions(group, { + instanceContextValue: true, + }); } await services.scopedClusterClient.asCurrentUser.index({ @@ -506,9 +496,7 @@ function getPatternFiringAlertType() { deep: DeepContextVariables, }); } else if (typeof scheduleByPattern === 'string') { - services.alertFactory - .create(instanceId) - .scheduleActionsWithSubGroup('default', scheduleByPattern); + services.alertFactory.create(instanceId).scheduleActions('default', scheduleByPattern); } } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts index 63c70e93ea194..8f1b8047e4331 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts @@ -953,82 +953,6 @@ instanceStateValue: true } }); - it('should not throttle when changing subgroups', async () => { - const testStart = new Date(); - const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringAction({ - reference, - overwrites: { - schedule: { interval: '1s' }, - params: { - index: ES_TEST_INDEX_NAME, - reference, - groupsToScheduleActionsInSeries: ['default:prev', 'default:next'], - }, - actions: [ - { - group: 'default', - id: indexRecordActionId, - params: { - index: ES_TEST_INDEX_NAME, - reference, - message: 'from:{{alertActionGroup}}:{{alertActionSubgroup}}', - }, - }, - ], - }, - }); - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - case 'global_read at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.always-firing', - 'alertsFixture' - ), - statusCode: 403, - }); - break; - case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: `Unauthorized to get actions`, - statusCode: 403, - }); - break; - case 'space_1_all at space1': - case 'space_1_all_with_restricted_fixture at space1': - case 'superuser at space1': - expect(response.statusCode).to.eql(200); - // Wait for actions to execute twice before disabling the alert and waiting for tasks to finish - await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2); - await alertUtils.disable(response.body.id); - await taskManagerUtils.waitForDisabled(response.body.id, testStart); - - // Ensure only 2 actions with proper params exists - const searchResult = await esTestIndexTool.search( - 'action:test.index-record', - reference - ); - // @ts-expect-error doesnt handle total: number - expect(searchResult.body.hits.total.value).to.eql(2); - const messages: string[] = searchResult.body.hits.hits.map( - // @ts-expect-error _source: unknown - (hit: { _source: { params: { message: string } } }) => hit._source.params.message - ); - expect(messages.sort()).to.eql(['from:default:next', 'from:default:prev']); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - }); - it('should reset throttle window when not firing', async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 206036ef7fcac..2e63bed197864 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import uuid from 'uuid'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { Spaces } from '../../scenarios'; import { @@ -447,237 +446,6 @@ export default function eventLogTests({ getService }: FtrProviderContext) { } }); - it('should generate expected events for normal operation with subgroups', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'MY action', - connector_type_id: 'test.noop', - config: {}, - secrets: {}, - }) - .expect(200); - - // pattern of when the alert should fire - const [firstSubgroup, secondSubgroup] = [uuid.v4(), uuid.v4()]; - const pattern = { - instance: [false, firstSubgroup, secondSubgroup], - }; - - const response = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - rule_type_id: 'test.patternFiring', - schedule: { interval: '1s' }, - throttle: null, - params: { - pattern, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: {}, - }, - ], - }) - ); - - expect(response.status).to.eql(200); - const alertId = response.body.id; - objectRemover.add(space.id, alertId, 'rule', 'alerting'); - - // get the events we're expecting - const events = await retry.try(async () => { - return await getEventLog({ - getService, - spaceId: space.id, - type: 'alert', - id: alertId, - provider: 'alerting', - actions: new Map([ - // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 4 }], - ['execute', { gte: 4 }], - ['execute-action', { equal: 2 }], - ['new-instance', { equal: 1 }], - ['active-instance', { gte: 2 }], - ['recovered-instance', { equal: 1 }], - ]), - }); - }); - - const executeEvents = getEventsByAction(events, 'execute'); - const executeStartEvents = getEventsByAction(events, 'execute-start'); - const newInstanceEvents = getEventsByAction(events, 'new-instance'); - const recoveredInstanceEvents = getEventsByAction(events, 'recovered-instance'); - - // make sure the events are in the right temporal order - const executeTimes = getTimestamps(executeEvents); - const executeStartTimes = getTimestamps(executeStartEvents); - const newInstanceTimes = getTimestamps(newInstanceEvents); - const recoveredInstanceTimes = getTimestamps(recoveredInstanceEvents); - - expect(executeTimes[0] < newInstanceTimes[0]).to.be(true); - expect(executeTimes[1] >= newInstanceTimes[0]).to.be(true); - expect(executeTimes[2] > newInstanceTimes[0]).to.be(true); - expect(executeStartTimes.length === executeTimes.length).to.be(true); - expect(recoveredInstanceTimes[0] > newInstanceTimes[0]).to.be(true); - - // validate each event - let executeCount = 0; - let numActiveAlerts = 0; - let numNewAlerts = 0; - let numRecoveredAlerts = 0; - let currentExecutionId; - const executeStatuses = ['ok', 'active', 'active']; - for (const event of events) { - switch (event?.event?.action) { - case 'execute-start': - currentExecutionId = event?.kibana?.alert?.rule?.execution?.uuid; - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - ], - message: `rule execution start: "${alertId}"`, - shouldHaveTask: true, - executionId: currentExecutionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - }, - consumer: 'alertsFixture', - }); - break; - case 'execute-action': - expect( - [firstSubgroup, secondSubgroup].includes( - event?.kibana?.alerting?.action_subgroup! - ) - ).to.be(true); - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - { type: 'action', id: createdAction.id, type_id: 'test.noop' }, - ], - message: `alert: test.patternFiring:${alertId}: 'abc' instanceId: 'instance' scheduled actionGroup(subgroup): 'default(${event?.kibana?.alerting?.action_subgroup})' action: test.noop:${createdAction.id}`, - instanceId: 'instance', - actionGroupId: 'default', - executionId: currentExecutionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - name: response.body.name, - }, - consumer: 'alertsFixture', - }); - break; - case 'new-instance': - numNewAlerts++; - validateInstanceEvent( - event, - `created new alert: 'instance'`, - false, - currentExecutionId - ); - break; - case 'recovered-instance': - numRecoveredAlerts++; - validateInstanceEvent( - event, - `alert 'instance' has recovered`, - true, - currentExecutionId - ); - break; - case 'active-instance': - numActiveAlerts++; - expect( - [firstSubgroup, secondSubgroup].includes( - event?.kibana?.alerting?.action_subgroup! - ) - ).to.be(true); - validateInstanceEvent( - event, - `active alert: 'instance' in actionGroup(subgroup): 'default(${event?.kibana?.alerting?.action_subgroup})'`, - false, - currentExecutionId - ); - break; - case 'execute': - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - ], - outcome: 'success', - message: `rule executed: test.patternFiring:${alertId}: 'abc'`, - status: executeStatuses[executeCount++], - shouldHaveTask: true, - executionId: currentExecutionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - name: response.body.name, - }, - consumer: 'alertsFixture', - numActiveAlerts, - numNewAlerts, - numRecoveredAlerts, - }); - numActiveAlerts = 0; - numNewAlerts = 0; - numRecoveredAlerts = 0; - break; - // this will get triggered as we add new event actions - default: - throw new Error(`unexpected event action "${event?.event?.action}"`); - } - } - - function validateInstanceEvent( - event: IValidatedEvent, - subMessage: string, - shouldHaveEventEnd: boolean, - executionId?: string - ) { - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - ], - message: `test.patternFiring:${alertId}: 'abc' ${subMessage}`, - instanceId: 'instance', - actionGroupId: 'default', - shouldHaveEventEnd, - executionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - name: response.body.name, - }, - consumer: 'alertsFixture', - }); - } - }); - it('should generate events for execution errors', async () => { const response = await supertest .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts index 5024a1489f4c8..a7996f19554e8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts @@ -174,97 +174,6 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext ); expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); }); - - it(`alert with notifyWhen=onActionGroupChange should only execute actions when action subgroup changes`, async () => { - const { body: defaultAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My Default Action', - connector_type_id: 'test.noop', - config: {}, - secrets: {}, - }) - .expect(200); - - const { body: recoveredAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My Recovered Action', - connector_type_id: 'test.noop', - config: {}, - secrets: {}, - }) - .expect(200); - - const pattern = { - instance: [ - 'subgroup1', - 'subgroup1', - false, - false, - 'subgroup1', - 'subgroup2', - 'subgroup2', - false, - ], - }; - const expectedActionGroupBasedOnPattern = [ - 'default', - 'recovered', - 'default', - 'default', - 'recovered', - ]; - - const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - rule_type_id: 'test.patternFiring', - params: { pattern }, - schedule: { interval: '1s' }, - throttle: null, - notify_when: 'onActionGroupChange', - actions: [ - { - id: defaultAction.id, - group: 'default', - params: {}, - }, - { - id: recoveredAction.id, - group: 'recovered', - params: {}, - }, - ], - }) - ) - .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const events = await retry.try(async () => { - return await getEventLog({ - getService, - spaceId: Spaces.space1.id, - type: 'alert', - id: createdAlert.id, - provider: 'alerting', - actions: new Map([ - ['execute-action', { gte: 5 }], - ['new-instance', { equal: 2 }], - ]), - }); - }); - - const executeActionEvents = getEventsByAction(events, 'execute-action'); - const executeActionEventsActionGroup = executeActionEvents.map( - (event) => event?.kibana?.alerting?.action_group_id - ); - expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); - }); }); } From 77867e162f1eb2a377865605196515c1e1b4ea45 Mon Sep 17 00:00:00 2001 From: Wafaa Nasr Date: Wed, 28 Sep 2022 20:36:05 +0200 Subject: [PATCH 136/172] Exceptions List component (#140985) * add components with a draft exception-list-details to test * fix jest config in xPack=> security=> Public * fix tests * fix header test and use RTL * covert meta test to use RTL and header * fix utlity messageid * fix messageid in utilty * create osCondition, entryContent and entryContent.helper from Conditions.tsx * comment test until fixing all * create package with first components + test + jest config * add constants for url * [CI] Auto-commit changed files from 'node scripts/generate codeowners' * disable tests until finishing moving rest of components or fix it+ add securityLinkAnchorComponent temp; * add exceptionList-components +fixning build issues * add exceptionList-components +fixning build issues * fix translations id + pass comments as props * move utiitly out of package until moving all + fixing css * copy non-js/ts files through babel * remove list-details-components * apply comments * apply comments in references * fix meta tests * update tests + add some descriptions * fix camelcase file name in Readme * fix camelcase file name in Readme Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: spalger --- .github/CODEOWNERS | 1 + .i18nrc.json | 1 + package.json | 2 + packages/BUILD.bazel | 2 + .../BUILD.bazel | 163 ++++++++ .../README.md | 27 ++ .../index.ts | 16 + .../jest.config.js | 21 ++ .../kibana.jsonc | 7 + .../package.json | 8 + .../setup_test.ts | 9 + ...on_product_no_results_magnifying_glass.svg | 1 + .../src/custom.d.ts | 13 + .../empty_viewer_state.test.tsx | 138 +++++++ .../empty_viewer_state/empty_viewer_state.tsx | 131 +++++++ .../exception_item_card/comments/comments.tsx | 45 +++ .../conditions/conditions.config.ts | 34 ++ .../conditions/conditions.styles.tsx | 36 ++ .../conditions/conditions.test.tsx | 350 ++++++++++++++++++ .../conditions/conditions.tsx | 56 +++ .../entry_content/entry_content.helper.tsx | 48 +++ .../entry_content/entry_content.tsx | 71 ++++ .../os_conditions/os_conditions.tsx | 34 ++ .../exception_item_card/conditions/types.ts | 32 ++ .../exception_item_card.tsx | 136 +++++++ .../header/header.test.tsx | 76 ++++ .../src/exception_item_card/header/header.tsx | 83 +++++ .../src/exception_item_card/index.test.tsx | 172 +++++++++ .../src/exception_item_card/index.ts | 13 + .../meta/details_info/details_info.tsx | 59 +++ .../exception_item_card/meta/meta.test.tsx | 155 ++++++++ .../src/exception_item_card/meta/meta.tsx | 123 ++++++ .../src/exception_item_card/translations.ts | 166 +++++++++ .../exception_items/exception_items.test.tsx | 103 ++++++ .../src/exception_items/exception_items.tsx | 139 +++++++ .../src/pagination/pagination.test.tsx | 78 ++++ .../src/pagination/pagination.tsx | 50 +++ .../src/pagination/use_pagination.test.ts | 67 ++++ .../src/pagination/use_pagination.ts | 55 +++ .../src/search_bar/search_bar.test.tsx | 90 +++++ .../src/search_bar/search_bar.tsx | 118 ++++++ .../src/translations.ts | 57 +++ .../src/types/index.ts | 65 ++++ .../src/value_with_space_warning/index.ts | 9 + .../use_value_with_space_warning.test.ts | 54 +++ .../use_value_with_space_warning.ts | 32 ++ .../value_with_space_warning.test.tsx | 48 +++ .../value_with_space_warning.tsx | 47 +++ .../tsconfig.json | 21 ++ .../security_solution/public/jest.config.js | 12 +- yarn.lock | 8 + 51 files changed, 3281 insertions(+), 1 deletion(-) create mode 100644 packages/kbn-securitysolution-exception-list-components/BUILD.bazel create mode 100644 packages/kbn-securitysolution-exception-list-components/README.md create mode 100644 packages/kbn-securitysolution-exception-list-components/index.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/jest.config.js create mode 100644 packages/kbn-securitysolution-exception-list-components/kibana.jsonc create mode 100644 packages/kbn-securitysolution-exception-list-components/package.json create mode 100644 packages/kbn-securitysolution-exception-list-components/setup_test.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/assets/images/illustration_product_no_results_magnifying_glass.svg create mode 100644 packages/kbn-securitysolution-exception-list-components/src/custom.d.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.config.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.styles.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.helper.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/os_conditions/os_conditions.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/types.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/details_info/details_info.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_item_card/translations.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/translations.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/types/index.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/index.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.ts create mode 100644 packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.test.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx create mode 100644 packages/kbn-securitysolution-exception-list-components/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d802eb4024a08..0a1fcc60d55b6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -915,6 +915,7 @@ packages/kbn-rule-data-utils @elastic/apm-ui packages/kbn-safer-lodash-set @elastic/kibana-security packages/kbn-securitysolution-autocomplete @elastic/security-solution-platform packages/kbn-securitysolution-es-utils @elastic/security-solution-platform +packages/kbn-securitysolution-exception-list-components @elastic/security-solution-platform packages/kbn-securitysolution-hook-utils @elastic/security-solution-platform packages/kbn-securitysolution-io-ts-alerting-types @elastic/security-solution-platform packages/kbn-securitysolution-io-ts-list-types @elastic/security-solution-platform diff --git a/.i18nrc.json b/.i18nrc.json index 20b2588ca2fab..462daff20de63 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -60,6 +60,7 @@ "kibana-react": "src/plugins/kibana_react", "kibanaOverview": "src/plugins/kibana_overview", "lists": "packages/kbn-securitysolution-list-utils/src", + "exceptionList-components": "packages/kbn-securitysolution-exception-list-components/src", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "monaco": "packages/kbn-monaco/src", "navigation": "src/plugins/navigation", diff --git a/package.json b/package.json index ebbff20d91733..eedb8db4b605a 100644 --- a/package.json +++ b/package.json @@ -335,6 +335,7 @@ "@kbn/safer-lodash-set": "link:bazel-bin/packages/kbn-safer-lodash-set", "@kbn/securitysolution-autocomplete": "link:bazel-bin/packages/kbn-securitysolution-autocomplete", "@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils", + "@kbn/securitysolution-exception-list-components": "link:bazel-bin/packages/kbn-securitysolution-exception-list-components", "@kbn/securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils", "@kbn/securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types", "@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types", @@ -1086,6 +1087,7 @@ "@types/kbn__rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils/npm_module_types", "@types/kbn__securitysolution-autocomplete": "link:bazel-bin/packages/kbn-securitysolution-autocomplete/npm_module_types", "@types/kbn__securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils/npm_module_types", + "@types/kbn__securitysolution-exception-list-components": "link:bazel-bin/packages/kbn-securitysolution-exception-list-components/npm_module_types", "@types/kbn__securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils/npm_module_types", "@types/kbn__securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types/npm_module_types", "@types/kbn__securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 455b851a4936f..cf97e501df09c 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -251,6 +251,7 @@ filegroup( "//packages/kbn-safer-lodash-set:build", "//packages/kbn-securitysolution-autocomplete:build", "//packages/kbn-securitysolution-es-utils:build", + "//packages/kbn-securitysolution-exception-list-components:build", "//packages/kbn-securitysolution-hook-utils:build", "//packages/kbn-securitysolution-io-ts-alerting-types:build", "//packages/kbn-securitysolution-io-ts-list-types:build", @@ -574,6 +575,7 @@ filegroup( "//packages/kbn-safer-lodash-set:build_types", "//packages/kbn-securitysolution-autocomplete:build_types", "//packages/kbn-securitysolution-es-utils:build_types", + "//packages/kbn-securitysolution-exception-list-components:build_types", "//packages/kbn-securitysolution-hook-utils:build_types", "//packages/kbn-securitysolution-io-ts-alerting-types:build_types", "//packages/kbn-securitysolution-io-ts-list-types:build_types", diff --git a/packages/kbn-securitysolution-exception-list-components/BUILD.bazel b/packages/kbn-securitysolution-exception-list-components/BUILD.bazel new file mode 100644 index 0000000000000..6436793fa5f30 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/BUILD.bazel @@ -0,0 +1,163 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + + +PKG_DIRNAME = "kbn-securitysolution-exception-list-components" +PKG_REQUIRE_NAME = "@kbn/securitysolution-exception-list-components" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + "**/*.svg", + "**/*.d.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "jest.config.js" +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ + "@npm//react", + "//packages/kbn-securitysolution-io-ts-list-types", + "//packages/kbn-securitysolution-autocomplete", + "//packages/kbn-ui-theme", + "//packages/kbn-i18n-react", + "//packages/kbn-i18n", + "@npm//@elastic/eui", + "@npm//@emotion/css", + "@npm//@emotion/react", + "@npm//@testing-library/jest-dom", + "@npm//jest", +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/react", + "//packages/kbn-securitysolution-io-ts-list-types:npm_module_types", + "//packages/kbn-securitysolution-autocomplete:npm_module_types", + "//packages/kbn-ui-theme:npm_module_types", + "//packages/kbn-i18n-react:npm_module_types", + "@npm//@elastic/eui", + "@npm//@emotion/css", + "@npm//@emotion/react", + "@npm//jest", + +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), + additional_args = [ + "--copy-files" + ], +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, + additional_args = [ + "--copy-files" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-securitysolution-exception-list-components/README.md b/packages/kbn-securitysolution-exception-list-components/README.md new file mode 100644 index 0000000000000..e23b85e409960 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/README.md @@ -0,0 +1,27 @@ +# @kbn/securitysolution-exception-list-components + +This is where the building UI components of the Exception-List live +Most of the components here are imported from `x-pack/plugins/security_solutions/public/detection_engine` + +# Aim + +TODO + +# Pattern used + +``` +component + index.tsx + index.styles.ts <-- to hold styles if the component has many custom styles + use_component.ts <-- for logic if the Presentational Component has logic + index.test.tsx + use_component.test.tsx +``` + +# Next + +- Now the `ExceptionItems, ExceptionItemCard +and ExceptionItemCardMetaInfo + ` receive `securityLinkAnchorComponent, exceptionsUtilityComponent +, and exceptionsUtilityComponent +` as props to avoid moving all the `common` components under the `x-pack` at once, later we should move all building blocks to this `kbn-package` diff --git a/packages/kbn-securitysolution-exception-list-components/index.ts b/packages/kbn-securitysolution-exception-list-components/index.ts new file mode 100644 index 0000000000000..f5001ff35fd33 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/search_bar/search_bar'; +export * from './src/empty_viewer_state/empty_viewer_state'; +export * from './src/pagination/pagination'; +// export * from './src/exceptions_utility/exceptions_utility'; +export * from './src/exception_items/exception_items'; +export * from './src/exception_item_card'; +export * from './src/value_with_space_warning'; +export * from './src/types'; diff --git a/packages/kbn-securitysolution-exception-list-components/jest.config.js b/packages/kbn-securitysolution-exception-list-components/jest.config.js new file mode 100644 index 0000000000000..37a11c23c75ba --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/jest.config.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-securitysolution-exception-list-components'], + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/packages/kbn-securitysolution-exception-list-components/**/*.{ts,tsx}', + '!/packages/kbn-securitysolution-exception-list-components/**/*.test', + ], + setupFilesAfterEnv: [ + '/packages/kbn-securitysolution-exception-list-components/setup_test.ts', + ], +}; diff --git a/packages/kbn-securitysolution-exception-list-components/kibana.jsonc b/packages/kbn-securitysolution-exception-list-components/kibana.jsonc new file mode 100644 index 0000000000000..081c50d35af0d --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/securitysolution-exception-list-components", + "owner": "@elastic/security-solution-platform", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-securitysolution-exception-list-components/package.json b/packages/kbn-securitysolution-exception-list-components/package.json new file mode 100644 index 0000000000000..263863d725c1e --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/securitysolution-exception-list-components", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-securitysolution-exception-list-components/setup_test.ts b/packages/kbn-securitysolution-exception-list-components/setup_test.ts new file mode 100644 index 0000000000000..bb55d97ec9302 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/setup_test.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/assets/images/illustration_product_no_results_magnifying_glass.svg b/packages/kbn-securitysolution-exception-list-components/src/assets/images/illustration_product_no_results_magnifying_glass.svg new file mode 100644 index 0000000000000..b9a0df1630b20 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/assets/images/illustration_product_no_results_magnifying_glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/kbn-securitysolution-exception-list-components/src/custom.d.ts b/packages/kbn-securitysolution-exception-list-components/src/custom.d.ts new file mode 100644 index 0000000000000..9169166fe7af9 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/custom.d.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +declare module '*.svg' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} diff --git a/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.test.tsx new file mode 100644 index 0000000000000..43943e0e8fb97 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.test.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import { EmptyViewerState } from './empty_viewer_state'; +import { ListTypeText, ViewerStatus } from '../types'; + +describe('EmptyViewerState', () => { + it('it should render "error" with the default title and body', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('errorViewerState')).toBeTruthy(); + expect(wrapper.getByTestId('errorTitle')).toHaveTextContent('Unable to load exception items'); + expect(wrapper.getByTestId('errorBody')).toHaveTextContent( + 'There was an error loading the exception items. Contact your administrator for help.' + ); + }); + it('it should render "error" when sending the title and body props', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('errorViewerState')).toBeTruthy(); + expect(wrapper.getByTestId('errorTitle')).toHaveTextContent('Error title'); + expect(wrapper.getByTestId('errorBody')).toHaveTextContent('Error body'); + }); + it('it should render loading', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('loadingViewerState')).toBeTruthy(); + }); + it('it should render empty search with the default title and body', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('emptySearchViewerState')).toBeTruthy(); + expect(wrapper.getByTestId('emptySearchTitle')).toHaveTextContent( + 'No results match your search criteria' + ); + expect(wrapper.getByTestId('emptySearchBody')).toHaveTextContent('Try modifying your search'); + }); + it('it should render empty search when sending title and body props', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('emptySearchViewerState')).toBeTruthy(); + expect(wrapper.getByTestId('emptySearchTitle')).toHaveTextContent('Empty search title'); + expect(wrapper.getByTestId('emptySearchBody')).toHaveTextContent('Empty search body'); + }); + it('it should render no items screen when sending title and body props', () => { + const wrapper = render( + + ); + + const { getByTestId } = wrapper; + expect(getByTestId('emptyBody')).toHaveTextContent('There are no endpoint exceptions.'); + expect(getByTestId('emptyStateButton')).toHaveTextContent('Add endpoint exception'); + expect(getByTestId('emptyViewerState')).toBeTruthy(); + }); + it('it should render no items with default title and body props', () => { + const wrapper = render( + + ); + + const { getByTestId } = wrapper; + expect(getByTestId('emptyViewerState')).toBeTruthy(); + expect(getByTestId('emptyTitle')).toHaveTextContent('Add exceptions to this rule'); + expect(getByTestId('emptyBody')).toHaveTextContent( + 'There is no exception in your rule. Create your first rule exception.' + ); + expect(getByTestId('emptyStateButton')).toHaveTextContent('Create rule exception'); + }); + it('it should render no items screen with default title and body props and listType endPoint', () => { + const wrapper = render( + + ); + + const { getByTestId } = wrapper; + expect(getByTestId('emptyViewerState')).toBeTruthy(); + expect(getByTestId('emptyTitle')).toHaveTextContent('Add exceptions to this rule'); + expect(getByTestId('emptyBody')).toHaveTextContent( + 'There is no exception in your rule. Create your first rule exception.' + ); + expect(getByTestId('emptyStateButton')).toHaveTextContent('Create endpoint exception'); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.tsx b/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.tsx new file mode 100644 index 0000000000000..060d2ecc15061 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import type { FC } from 'react'; +import { css } from '@emotion/react'; +import { + EuiLoadingContent, + EuiImage, + EuiEmptyPrompt, + EuiButton, + useEuiTheme, + EuiPanel, +} from '@elastic/eui'; +import type { ExpressionColor } from '@elastic/eui/src/components/expression/expression'; +import type { EuiFacetGroupLayout } from '@elastic/eui/src/components/facet/facet_group'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { ListTypeText, ViewerStatus } from '../types'; +import * as i18n from '../translations'; +import illustration from '../assets/images/illustration_product_no_results_magnifying_glass.svg'; + +interface EmptyViewerStateProps { + title?: string; + body?: string; + buttonText?: string; + listType?: ListTypeText; + isReadOnly: boolean; + viewerStatus: ViewerStatus; + onCreateExceptionListItem?: () => void | null; +} + +const panelCss = css` + margin: ${euiThemeVars.euiSizeL} 0; + padding: ${euiThemeVars.euiSizeL} 0; +`; +const EmptyViewerStateComponent: FC = ({ + title, + body, + buttonText, + listType, + isReadOnly, + viewerStatus, + onCreateExceptionListItem, +}) => { + const { euiTheme } = useEuiTheme(); + + const euiEmptyPromptProps = useMemo(() => { + switch (viewerStatus) { + case ViewerStatus.ERROR: { + return { + color: 'danger' as ExpressionColor, + iconType: 'alert', + title: ( +

    {title || i18n.EMPTY_VIEWER_STATE_ERROR_TITLE}

    + ), + body:

    {body || i18n.EMPTY_VIEWER_STATE_ERROR_BODY}

    , + 'data-test-subj': 'errorViewerState', + }; + } + case ViewerStatus.EMPTY: + return { + color: 'subdued' as ExpressionColor, + iconType: 'plusInCircle', + iconColor: euiTheme.colors.darkestShade, + title: ( +

    {title || i18n.EMPTY_VIEWER_STATE_EMPTY_TITLE}

    + ), + body:

    {body || i18n.EMPTY_VIEWER_STATE_EMPTY_BODY}

    , + 'data-test-subj': 'emptyViewerState', + actions: [ + + {buttonText || i18n.EMPTY_VIEWER_STATE_EMPTY_VIEWER_BUTTON(listType || 'rule')} + , + ], + }; + case ViewerStatus.EMPTY_SEARCH: + return { + color: 'plain' as ExpressionColor, + layout: 'horizontal' as EuiFacetGroupLayout, + hasBorder: true, + hasShadow: false, + icon: , + title: ( +

    + {title || i18n.EMPTY_VIEWER_STATE_EMPTY_SEARCH_TITLE} +

    + ), + body: ( +

    + {body || i18n.EMPTY_VIEWER_STATE_EMPTY_SEARCH_BODY} +

    + ), + 'data-test-subj': 'emptySearchViewerState', + }; + } + }, [ + viewerStatus, + euiTheme.colors.darkestShade, + title, + body, + onCreateExceptionListItem, + isReadOnly, + buttonText, + listType, + ]); + + if (viewerStatus === ViewerStatus.LOADING || viewerStatus === ViewerStatus.SEARCHING) + return ; + + return ( + + + + ); +}; + +export const EmptyViewerState = React.memo(EmptyViewerStateComponent); + +EmptyViewerState.displayName = 'EmptyViewerState'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx new file mode 100644 index 0000000000000..ca08d10f0a049 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo } from 'react'; +import type { EuiCommentProps } from '@elastic/eui'; +import { EuiAccordion, EuiCommentList, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import * as i18n from '../translations'; + +const accordionCss = css` + color: ${euiThemeVars.euiColorPrimary}; +`; + +export interface ExceptionItemCardCommentsProps { + comments: EuiCommentProps[]; +} + +export const ExceptionItemCardComments = memo(({ comments }) => { + return ( + + + {i18n.exceptionItemCardCommentsAccordion(comments.length)} + + } + arrowDisplay="none" + data-test-subj="exceptionsViewerCommentAccordion" + > + + + + + + ); +}); + +ExceptionItemCardComments.displayName = 'ExceptionItemCardComments'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.config.ts b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.config.ts new file mode 100644 index 0000000000000..08514a64feac0 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.config.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import * as i18n from '../translations'; + +export const OS_LABELS = Object.freeze({ + linux: i18n.OS_LINUX, + mac: i18n.OS_MAC, + macos: i18n.OS_MAC, + windows: i18n.OS_WINDOWS, +}); + +export const OPERATOR_TYPE_LABELS_INCLUDED = Object.freeze({ + [ListOperatorTypeEnum.NESTED]: i18n.CONDITION_OPERATOR_TYPE_NESTED, + [ListOperatorTypeEnum.MATCH_ANY]: i18n.CONDITION_OPERATOR_TYPE_MATCH_ANY, + [ListOperatorTypeEnum.MATCH]: i18n.CONDITION_OPERATOR_TYPE_MATCH, + [ListOperatorTypeEnum.WILDCARD]: i18n.CONDITION_OPERATOR_TYPE_WILDCARD_MATCHES, + [ListOperatorTypeEnum.EXISTS]: i18n.CONDITION_OPERATOR_TYPE_EXISTS, + [ListOperatorTypeEnum.LIST]: i18n.CONDITION_OPERATOR_TYPE_LIST, +}); + +export const OPERATOR_TYPE_LABELS_EXCLUDED = Object.freeze({ + [ListOperatorTypeEnum.MATCH_ANY]: i18n.CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY, + [ListOperatorTypeEnum.MATCH]: i18n.CONDITION_OPERATOR_TYPE_NOT_MATCH, + [ListOperatorTypeEnum.WILDCARD]: i18n.CONDITION_OPERATOR_TYPE_WILDCARD_DOES_NOT_MATCH, + [ListOperatorTypeEnum.EXISTS]: i18n.CONDITION_OPERATOR_TYPE_DOES_NOT_EXIST, + [ListOperatorTypeEnum.LIST]: i18n.CONDITION_OPERATOR_TYPE_NOT_IN_LIST, +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.styles.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.styles.tsx new file mode 100644 index 0000000000000..3ad2d7ef21fba --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.styles.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { cx } from '@emotion/css'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; + +// TODO check font Roboto Mono +export const nestedGroupSpaceCss = css` + margin-left: ${euiThemeVars.euiSizeXL}; + margin-bottom: ${euiThemeVars.euiSizeXS}; + padding-top: ${euiThemeVars.euiSizeXS}; +`; + +export const borderCss = cx( + 'eui-xScroll', + ` + border: 1px; + border-color: #d3dae6; + border-style: solid; +` +); + +export const valueContainerCss = css` + display: flex; + align-items: center; + margin-left: ${euiThemeVars.euiSizeS}; +`; +export const expressionContainerCss = css` + display: flex; + align-items: center; +`; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.test.tsx new file mode 100644 index 0000000000000..ae4b76a4a7dc0 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.test.tsx @@ -0,0 +1,350 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; + +import { ExceptionItemCardConditions } from './conditions'; + +interface TestEntry { + field: string; + operator: 'included' | 'excluded'; + type: unknown; + value?: string | string[]; + entries?: TestEntry[]; + list?: { id: string; type: string }; +} +const getEntryKey = ( + entry: TestEntry, + index: string, + list?: { id: string; type: string } | null +) => { + if (list && Object.keys(list)) { + const { field, type, list: entryList } = entry; + const { id } = entryList || {}; + return `${field}${type}${id || ''}${index}`; + } + const { field, type, value } = entry; + return `${field}${type}${value || ''}${index}`; +}; + +describe('ExceptionItemCardConditions', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + it('it includes os condition if one exists', () => { + const entries: TestEntry[] = [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'host', + }, + { + field: 'threat.indicator.port', + operator: 'included', + type: 'exists', + }, + { + entries: [ + { + field: 'valid', + operator: 'included', + type: 'match', + value: 'true', + }, + ], + field: 'file.Ext.code_signature', + type: 'nested', + operator: 'included', + }, + ]; + const wrapper = render( + + ); + expect(wrapper.getByTestId('exceptionItemConditionsOs')).toHaveTextContent('OSIS Linux'); + + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[0], '0')}EntryContent`) + ).toHaveTextContent('host.nameIS host'); + + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[1], '1')}EntryContent`) + ).toHaveTextContent('AND threat.indicator.portexists'); + + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[2], '2')}EntryContent`) + ).toHaveTextContent('AND file.Ext.code_signature'); + + if (entries[2] && entries[2].entries) { + expect( + wrapper.getByTestId( + `exceptionItemConditions${getEntryKey(entries[2].entries[0], '0')}EntryContent` + ) + ).toHaveTextContent('validIS true'); + } + }); + + it('it renders item conditions', () => { + const entries: TestEntry[] = [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'host', + }, + { + field: 'host.name', + operator: 'excluded', + type: 'match', + value: 'host', + }, + { + field: 'host.name', + operator: 'included', + type: 'match_any', + value: ['foo', 'bar'], + }, + { + field: 'host.name', + operator: 'excluded', + type: 'match_any', + value: ['foo', 'bar'], + }, + { + field: 'user.name', + operator: 'included', + type: 'wildcard', + value: 'foo*', + }, + { + field: 'user.name', + operator: 'excluded', + type: 'wildcard', + value: 'foo*', + }, + { + field: 'threat.indicator.port', + operator: 'included', + type: 'exists', + }, + { + field: 'threat.indicator.port', + operator: 'excluded', + type: 'exists', + }, + { + entries: [ + { + field: 'valid', + operator: 'included', + type: 'match', + value: 'true', + }, + ], + field: 'file.Ext.code_signature', + type: 'nested', + operator: 'included', + }, + ]; + const wrapper = render( + + ); + expect(wrapper.queryByTestId('exceptionItemConditionsOs')).not.toBeInTheDocument(); + + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[0], '0')}EntryContent`) + ).toHaveTextContent('host.nameIS host'); + // Match; + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[1], '1')}EntryContent`) + ).toHaveTextContent('AND host.nameIS NOT host'); + // MATCH_ANY; + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[2], '2')}EntryContent`) + ).toHaveTextContent('AND host.nameis one of foobar'); + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[3], '3')}EntryContent`) + ).toHaveTextContent('AND host.nameis not one of foobar'); + // WILDCARD; + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[4], '4')}EntryContent`) + ).toHaveTextContent('AND user.nameMATCHES foo*'); + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[5], '5')}EntryContent`) + ).toHaveTextContent('AND user.nameDOES NOT MATCH foo*'); + // EXISTS; + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[6], '6')}EntryContent`) + ).toHaveTextContent('AND threat.indicator.portexists'); + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[7], '7')}EntryContent`) + ).toHaveTextContent('AND threat.indicator.portdoes not exist'); + // NESTED; + expect( + wrapper.getByTestId(`exceptionItemConditions${getEntryKey(entries[8], '8')}EntryContent`) + ).toHaveTextContent('AND file.Ext.code_signature'); + if (entries[8] && entries[8].entries) { + expect( + wrapper.getByTestId( + `exceptionItemConditions${getEntryKey(entries[8].entries[0], '0')}EntryContent` + ) + ).toHaveTextContent('validIS true'); + } + }); + it('it renders list conditions', () => { + const entries: TestEntry[] = [ + { + field: 'host.name', + list: { + id: 'ips.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + { + field: 'host.name', + list: { + id: 'ips.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ]; + const wrapper = render( + + ); + // /exceptionItemConditionshost.namelist0EntryContent + expect( + wrapper.getByTestId( + `exceptionItemConditions${getEntryKey(entries[0], '0', entries[0].list)}EntryContent` + ) + ).toHaveTextContent('host.nameincluded in ips.txt'); + + expect( + wrapper.getByTestId( + `exceptionItemConditions${getEntryKey(entries[1], '1', entries[1].list)}EntryContent` + ) + ).toHaveTextContent('AND host.nameis not included in ips.txt'); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.tsx new file mode 100644 index 0000000000000..8b85a7343afc1 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo } from 'react'; +import { EuiPanel } from '@elastic/eui'; + +import { borderCss } from './conditions.styles'; +import { EntryContent } from './entry_content/entry_content'; +import { OsCondition } from './os_conditions/os_conditions'; +import type { CriteriaConditionsProps, Entry } from './types'; + +export const ExceptionItemCardConditions = memo( + ({ os, entries, dataTestSubj }) => { + return ( + + {os?.length ? : null} + {entries.map((entry: Entry, index: number) => { + const nestedEntries = 'entries' in entry ? entry.entries : []; + return ( +
    + + {nestedEntries?.length + ? nestedEntries.map((nestedEntry: Entry, nestedIndex: number) => ( + + )) + : null} +
    + ); + })} +
    + ); + } +); +ExceptionItemCardConditions.displayName = 'ExceptionItemCardConditions'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.helper.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.helper.tsx new file mode 100644 index 0000000000000..6a64bcc810c08 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.helper.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiExpression, EuiBadge } from '@elastic/eui'; +import type { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { ValueWithSpaceWarning } from '../../../..'; +import { OPERATOR_TYPE_LABELS_EXCLUDED, OPERATOR_TYPE_LABELS_INCLUDED } from '../conditions.config'; +import type { Entry } from '../types'; + +const getEntryValue = (type: string, value?: string | string[]) => { + if (type === 'match_any' && Array.isArray(value)) { + return value.map((currentValue) => {currentValue}); + } + return value ?? ''; +}; + +export const getEntryOperator = (type: ListOperatorTypeEnum, operator: string) => { + if (type === 'nested') return ''; + return operator === 'included' + ? OPERATOR_TYPE_LABELS_INCLUDED[type] ?? type + : OPERATOR_TYPE_LABELS_EXCLUDED[type] ?? type; +}; + +export const getValue = (entry: Entry) => { + if (entry.type === 'list') return entry.list.id; + + return 'value' in entry ? entry.value : ''; +}; + +export const getValueExpression = ( + type: ListOperatorTypeEnum, + operator: string, + value: string | string[] +) => ( + <> + + + +); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.tsx new file mode 100644 index 0000000000000..6c321a6d0ce04 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/entry_content/entry_content.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo } from 'react'; +import { EuiExpression, EuiToken, EuiFlexGroup } from '@elastic/eui'; +import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { + nestedGroupSpaceCss, + valueContainerCss, + expressionContainerCss, +} from '../conditions.styles'; +import type { Entry } from '../types'; +import * as i18n from '../../translations'; +import { getValue, getValueExpression } from './entry_content.helper'; + +export const EntryContent = memo( + ({ + entry, + index, + isNestedEntry = false, + dataTestSubj, + }: { + entry: Entry; + index: number; + isNestedEntry?: boolean; + dataTestSubj?: string; + }) => { + const { field, type } = entry; + const value = getValue(entry); + const operator = 'operator' in entry ? entry.operator : ''; + + const entryKey = `${field}${type}${value}${index}`; + return ( +
    +
    + {isNestedEntry ? ( + + + +
    + + {getValueExpression(type as ListOperatorTypeEnum, operator, value)} +
    +
    + ) : ( + <> + + + {getValueExpression(type as ListOperatorTypeEnum, operator, value)} + + )} +
    +
    + ); + } +); +EntryContent.displayName = 'EntryContent'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/os_conditions/os_conditions.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/os_conditions/os_conditions.tsx new file mode 100644 index 0000000000000..701529ae6717d --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/os_conditions/os_conditions.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo, useMemo } from 'react'; +import { EuiExpression } from '@elastic/eui'; + +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { OS_LABELS } from '../conditions.config'; +import * as i18n from '../../translations'; + +export interface OsConditionsProps { + dataTestSubj: string; + os: ExceptionListItemSchema['os_types']; +} + +export const OsCondition = memo(({ os, dataTestSubj }) => { + const osLabel = useMemo(() => { + return os.map((osValue) => OS_LABELS[osValue] ?? osValue).join(', '); + }, [os]); + return osLabel ? ( +
    + + + + +
    + ) : null; +}); +OsCondition.displayName = 'OsCondition'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/types.ts b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/types.ts new file mode 100644 index 0000000000000..09067e84cafc7 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + EntryExists, + EntryList, + EntryMatch, + EntryMatchAny, + EntryMatchWildcard, + EntryNested, + ExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; + +export type Entry = + | EntryExists + | EntryList + | EntryMatch + | EntryMatchAny + | EntryMatchWildcard + | EntryNested; + +export type Entries = ExceptionListItemSchema['entries']; +export interface CriteriaConditionsProps { + entries: Entries; + dataTestSubj: string; + os?: ExceptionListItemSchema['os_types']; +} diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx new file mode 100644 index 0000000000000..c3705750d015d --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, useCallback, FC } from 'react'; +import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiCommentProps } from '@elastic/eui'; +import type { + CommentsArray, + ExceptionListItemSchema, + ExceptionListTypeEnum, +} from '@kbn/securitysolution-io-ts-list-types'; + +import * as i18n from './translations'; +import { + ExceptionItemCardHeader, + ExceptionItemCardConditions, + ExceptionItemCardMetaInfo, + ExceptionItemCardComments, +} from '.'; + +import type { ExceptionListItemIdentifiers } from '../types'; + +export interface ExceptionItemProps { + dataTestSubj?: string; + disableActions?: boolean; + exceptionItem: ExceptionListItemSchema; + listType: ExceptionListTypeEnum; + ruleReferences: any[]; // rulereferences + editActionLabel?: string; + deleteActionLabel?: string; + securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + formattedDateComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + getFormattedComments: (comments: CommentsArray) => EuiCommentProps[]; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + onDeleteException: (arg: ExceptionListItemIdentifiers) => void; + onEditException: (item: ExceptionListItemSchema) => void; +} + +const ExceptionItemCardComponent: FC = ({ + disableActions = false, + exceptionItem, + listType, + ruleReferences, + dataTestSubj, + editActionLabel, + deleteActionLabel, + securityLinkAnchorComponent, + formattedDateComponent, + getFormattedComments, + onDeleteException, + onEditException, +}) => { + const handleDelete = useCallback((): void => { + onDeleteException({ + id: exceptionItem.id, + name: exceptionItem.name, + namespaceType: exceptionItem.namespace_type, + }); + }, [onDeleteException, exceptionItem.id, exceptionItem.name, exceptionItem.namespace_type]); + + const handleEdit = useCallback((): void => { + onEditException(exceptionItem); + }, [onEditException, exceptionItem]); + + const formattedComments = useMemo((): EuiCommentProps[] => { + return getFormattedComments(exceptionItem.comments); + }, [exceptionItem.comments, getFormattedComments]); + + const actions: Array<{ + key: string; + icon: string; + label: string | boolean; + onClick: () => void; + }> = useMemo( + () => [ + { + key: 'edit', + icon: 'controlsHorizontal', + label: editActionLabel || i18n.exceptionItemCardEditButton(listType), + onClick: handleEdit, + }, + { + key: 'delete', + icon: 'trash', + label: deleteActionLabel || listType === i18n.exceptionItemCardDeleteButton(listType), + onClick: handleDelete, + }, + ], + [editActionLabel, listType, deleteActionLabel, handleDelete, handleEdit] + ); + return ( + + + + + + + + + + + + {formattedComments.length > 0 && ( + + )} + + + ); +}; + +ExceptionItemCardComponent.displayName = 'ExceptionItemCardComponent'; + +export const ExceptionItemCard = React.memo(ExceptionItemCardComponent); + +ExceptionItemCard.displayName = 'ExceptionItemCard'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.test.tsx new file mode 100644 index 0000000000000..78feab598c145 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; + +import * as i18n from '../translations'; +import { ExceptionItemCardHeader } from './header'; +import { fireEvent, render } from '@testing-library/react'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +const handleEdit = jest.fn(); +const handleDelete = jest.fn(); +const actions = [ + { + key: 'edit', + icon: 'pencil', + label: i18n.exceptionItemCardEditButton(ExceptionListTypeEnum.DETECTION), + onClick: handleEdit, + }, + { + key: 'delete', + icon: 'trash', + label: i18n.exceptionItemCardDeleteButton(ExceptionListTypeEnum.DETECTION), + onClick: handleDelete, + }, +]; +describe('ExceptionItemCardHeader', () => { + it('it renders item name', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('exceptionItemHeaderTitle')).toHaveTextContent('some name'); + }); + + it('it displays actions', () => { + const wrapper = render( + + ); + + // click on popover + fireEvent.click(wrapper.getByTestId('exceptionItemHeaderActionButton')); + fireEvent.click(wrapper.getByTestId('exceptionItemHeaderActionItemedit')); + expect(handleEdit).toHaveBeenCalled(); + + fireEvent.click(wrapper.getByTestId('exceptionItemHeaderActionItemdelete')); + expect(handleDelete).toHaveBeenCalled(); + }); + + it('it disables actions if disableActions is true', () => { + const wrapper = render( + + ); + + expect(wrapper.getByTestId('exceptionItemHeaderActionButton')).toBeDisabled(); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx new file mode 100644 index 0000000000000..d58cb8d99b7a1 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo, useMemo, useState } from 'react'; +import type { EuiContextMenuPanelProps } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiTitle, + EuiContextMenuItem, +} from '@elastic/eui'; +import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; + +export interface ExceptionItemCardHeaderProps { + item: ExceptionListItemSchema; + actions: Array<{ key: string; icon: string; label: string | boolean; onClick: () => void }>; + disableActions?: boolean; + dataTestSubj: string; +} + +export const ExceptionItemCardHeader = memo( + ({ item, actions, disableActions = false, dataTestSubj }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onItemActionsClick = () => setIsPopoverOpen((isOpen) => !isOpen); + const onClosePopover = () => setIsPopoverOpen(false); + + const itemActions = useMemo((): EuiContextMenuPanelProps['items'] => { + return actions.map((action) => ( + { + onClosePopover(); + action.onClick(); + }} + > + {action.label} + + )); + }, [dataTestSubj, actions]); + + return ( + + + +

    {item.name}

    +
    +
    + + + } + panelPaddingSize="none" + isOpen={isPopoverOpen} + closePopover={onClosePopover} + data-test-subj={`${dataTestSubj}Items`} + > + + + +
    + ); + } +); + +ExceptionItemCardHeader.displayName = 'ExceptionItemCardHeader'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.test.tsx new file mode 100644 index 0000000000000..e97b03607bb6a --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.test.tsx @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { ExceptionItemCard } from '.'; +import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; +import { getCommentsArrayMock } from '@kbn/lists-plugin/common/schemas/types/comment.mock'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +const ruleReferences: unknown[] = [ + { + exception_lists: [ + { + id: '123', + list_id: 'i_exist', + namespace_type: 'single', + type: 'detection', + }, + { + id: '456', + list_id: 'i_exist_2', + namespace_type: 'single', + type: 'detection', + }, + ], + id: '1a2b3c', + name: 'Simple Rule Query', + rule_id: 'rule-2', + }, +]; +describe('ExceptionItemCard', () => { + it('it renders header, item meta information and conditions', () => { + const exceptionItem = { ...getExceptionListItemSchemaMock(), comments: [] }; + + const wrapper = render( + null} + formattedDateComponent={() => null} + getFormattedComments={() => []} + /> + ); + + expect(wrapper.getByTestId('exceptionItemCardHeaderContainer')).toBeInTheDocument(); + // expect(wrapper.getByTestId('exceptionItemCardMetaInfo')).toBeInTheDocument(); + expect(wrapper.getByTestId('exceptionItemCardConditions')).toBeInTheDocument(); + // expect(wrapper.queryByTestId('exceptionsViewerCommentAccordion')).not.toBeInTheDocument(); + }); + + it('it renders header, item meta information, conditions, and comments if any exist', () => { + const exceptionItem = { ...getExceptionListItemSchemaMock(), comments: getCommentsArrayMock() }; + + const wrapper = render( + null} + formattedDateComponent={() => null} + getFormattedComments={() => []} + /> + ); + + expect(wrapper.getByTestId('exceptionItemCardHeaderContainer')).toBeInTheDocument(); + // expect(wrapper.getByTestId('exceptionItemCardMetaInfo')).toBeInTheDocument(); + expect(wrapper.getByTestId('exceptionItemCardConditions')).toBeInTheDocument(); + // expect(wrapper.getByTestId('exceptionsViewerCommentAccordion')).toBeInTheDocument(); + }); + + it('it does not render edit or delete action buttons when "disableActions" is "true"', () => { + const exceptionItem = getExceptionListItemSchemaMock(); + + const wrapper = render( + null} + formattedDateComponent={() => null} + getFormattedComments={() => []} + /> + ); + expect(wrapper.queryByTestId('itemActionButton')).not.toBeInTheDocument(); + }); + + it('it invokes "onEditException" when edit button clicked', () => { + const mockOnEditException = jest.fn(); + const exceptionItem = getExceptionListItemSchemaMock(); + + const wrapper = render( + null} + formattedDateComponent={() => null} + getFormattedComments={() => []} + /> + ); + + fireEvent.click(wrapper.getByTestId('exceptionItemCardHeaderActionButton')); + fireEvent.click(wrapper.getByTestId('exceptionItemCardHeaderActionItemedit')); + expect(mockOnEditException).toHaveBeenCalledWith(getExceptionListItemSchemaMock()); + }); + + it('it invokes "onDeleteException" when delete button clicked', () => { + const mockOnDeleteException = jest.fn(); + const exceptionItem = getExceptionListItemSchemaMock(); + + const wrapper = render( + null} + formattedDateComponent={() => null} + getFormattedComments={() => []} + /> + ); + fireEvent.click(wrapper.getByTestId('exceptionItemCardHeaderActionButton')); + fireEvent.click(wrapper.getByTestId('exceptionItemCardHeaderActionItemdelete')); + + expect(mockOnDeleteException).toHaveBeenCalledWith({ + id: '1', + name: 'some name', + namespaceType: 'single', + }); + }); + + // TODO Fix this Test + // it('it renders comment accordion closed to begin with', () => { + // const exceptionItem = getExceptionListItemSchemaMock(); + // exceptionItem.comments = getCommentsArrayMock(); + // const wrapper = render( + // + // ); + + // expect(wrapper.queryByTestId('accordion-comment-list')).not.toBeVisible(); + // }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.ts b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.ts new file mode 100644 index 0000000000000..c0fd3fafc86d5 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './conditions/conditions'; +export * from './header/header'; +export * from './meta/meta'; +export * from './comments/comments'; +export * from './exception_item_card'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/details_info/details_info.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/details_info/details_info.tsx new file mode 100644 index 0000000000000..3d075f50096d0 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/details_info/details_info.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo } from 'react'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import * as i18n from '../../translations'; + +interface MetaInfoDetailsProps { + fieldName: string; + label: string; + lastUpdate: JSX.Element | string; + lastUpdateValue: string; + dataTestSubj: string; +} + +const euiBadgeFontFamily = css` + font-family: ${euiThemeVars.euiFontFamily}; +`; +export const MetaInfoDetails = memo( + ({ label, lastUpdate, lastUpdateValue, dataTestSubj }) => { + return ( + + + + {label} + + + + + {lastUpdate} + + + + + {i18n.EXCEPTION_ITEM_CARD_META_BY} + + + + + + + {lastUpdateValue} + + + + + + ); + } +); + +MetaInfoDetails.displayName = 'MetaInfoDetails'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx new file mode 100644 index 0000000000000..14bdef771d6b3 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; + +import { ExceptionItemCardMetaInfo } from './meta'; +import { RuleReference } from '../../types'; + +const ruleReferences = [ + { + exception_lists: [ + { + id: '123', + list_id: 'i_exist', + namespace_type: 'single', + type: 'detection', + }, + { + id: '456', + list_id: 'i_exist_2', + namespace_type: 'single', + type: 'detection', + }, + ], + id: '1a2b3c', + name: 'Simple Rule Query', + rule_id: 'rule-2', + }, +]; +describe('ExceptionItemCardMetaInfo', () => { + it('it should render creation info with sending custom formattedDateComponent', () => { + const wrapper = render( + null} + formattedDateComponent={({ fieldName, value }) => ( + <> +

    {new Date(value).toDateString()}

    + + )} + /> + ); + + expect(wrapper.getByTestId('exceptionItemMetaCreatedBylastUpdate')).toHaveTextContent( + 'Mon Apr 20 2020' + ); + expect(wrapper.getByTestId('exceptionItemMetaCreatedBylastUpdateValue')).toHaveTextContent( + 'some user' + ); + }); + + it('it should render udate info with sending custom formattedDateComponent', () => { + const wrapper = render( + null} + formattedDateComponent={({ fieldName, value }) => ( + <> +

    {new Date(value).toDateString()}

    + + )} + /> + ); + expect(wrapper.getByTestId('exceptionItemMetaUpdatedBylastUpdate')).toHaveTextContent( + 'Mon Apr 20 2020' + ); + expect(wrapper.getByTestId('exceptionItemMetaUpdatedBylastUpdateValue')).toHaveTextContent( + 'some user' + ); + }); + + it('it should render references info', () => { + const wrapper = render( + null} + formattedDateComponent={() => null} + /> + ); + + expect(wrapper.getByTestId('exceptionItemMetaAffectedRulesButton')).toHaveTextContent( + 'Affects 1 rule' + ); + }); + + it('it renders references info when multiple references exist', () => { + const wrapper = render( + null} + formattedDateComponent={() => null} + /> + ); + + expect(wrapper.getByTestId('exceptionItemMetaAffectedRulesButton')).toHaveTextContent( + 'Affects 2 rules' + ); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx new file mode 100644 index 0000000000000..91e0a9cdd19b8 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo, useMemo, useState } from 'react'; +import type { EuiContextMenuPanelProps } from '@elastic/eui'; +import { + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, + EuiButtonEmpty, + EuiPopover, +} from '@elastic/eui'; +import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; + +import { css } from '@emotion/react'; +import * as i18n from '../translations'; +import type { RuleReference } from '../../types'; +import { MetaInfoDetails } from './details_info/details_info'; + +const itemCss = css` + border-right: 1px solid #d3dae6; + padding: 4px 12px 4px 0; +`; + +export interface ExceptionItemCardMetaInfoProps { + item: ExceptionListItemSchema; + references: RuleReference[]; + dataTestSubj: string; + formattedDateComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common +} + +export const ExceptionItemCardMetaInfo = memo( + ({ item, references, dataTestSubj, securityLinkAnchorComponent, formattedDateComponent }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onAffectedRulesClick = () => setIsPopoverOpen((isOpen) => !isOpen); + const onClosePopover = () => setIsPopoverOpen(false); + + const FormattedDateComponent = formattedDateComponent; + const itemActions = useMemo((): EuiContextMenuPanelProps['items'] => { + if (references == null || securityLinkAnchorComponent === null) { + return []; + } + + const SecurityLinkAnchor = securityLinkAnchorComponent; + return references.map((reference) => ( + + + + + + )); + }, [references, securityLinkAnchorComponent, dataTestSubj]); + + return ( + + {FormattedDateComponent !== null && ( + <> + + + } + lastUpdateValue={item.created_by} + dataTestSubj={`${dataTestSubj}CreatedBy`} + /> + + + + + } + lastUpdateValue={item.updated_by} + dataTestSubj={`${dataTestSubj}UpdatedBy`} + /> + + + )} + + + {i18n.AFFECTED_RULES(references.length)} + + } + panelPaddingSize="none" + isOpen={isPopoverOpen} + closePopover={onClosePopover} + data-test-subj={`${dataTestSubj}Items`} + > + + + + + ); + } +); +ExceptionItemCardMetaInfo.displayName = 'ExceptionItemCardMetaInfo'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/translations.ts b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/translations.ts new file mode 100644 index 0000000000000..2fa7524291025 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/translations.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const exceptionItemCardEditButton = (listType: string) => + i18n.translate('exceptionList-components.exceptions.exceptionItem.card.editItemButton', { + values: { listType }, + defaultMessage: 'Edit {listType} exception', + }); + +export const exceptionItemCardDeleteButton = (listType: string) => + i18n.translate('exceptionList-components.exceptions.exceptionItem.card.deleteItemButton', { + values: { listType }, + defaultMessage: 'Delete {listType} exception', + }); + +export const EXCEPTION_ITEM_CARD_CREATED_LABEL = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.createdLabel', + { + defaultMessage: 'Created', + } +); + +export const EXCEPTION_ITEM_CARD_UPDATED_LABEL = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.updatedLabel', + { + defaultMessage: 'Updated', + } +); + +export const EXCEPTION_ITEM_CARD_META_BY = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.metaDetailsBy', + { + defaultMessage: 'by', + } +); + +export const exceptionItemCardCommentsAccordion = (comments: number) => + i18n.translate('exceptionList-components.exceptions.exceptionItem.card.showCommentsLabel', { + values: { comments }, + defaultMessage: 'Show {comments, plural, =1 {comment} other {comments}} ({comments})', + }); + +export const CONDITION_OPERATOR_TYPE_MATCH = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.matchOperator', + { + defaultMessage: 'IS', + } +); + +export const CONDITION_OPERATOR_TYPE_NOT_MATCH = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.matchOperator.not', + { + defaultMessage: 'IS NOT', + } +); + +export const CONDITION_OPERATOR_TYPE_WILDCARD_MATCHES = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.wildcardMatchesOperator', + { + defaultMessage: 'MATCHES', + } +); + +export const CONDITION_OPERATOR_TYPE_WILDCARD_DOES_NOT_MATCH = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.wildcardDoesNotMatchOperator', + { + defaultMessage: 'DOES NOT MATCH', + } +); + +export const CONDITION_OPERATOR_TYPE_NESTED = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.nestedOperator', + { + defaultMessage: 'has', + } +); + +export const CONDITION_OPERATOR_TYPE_MATCH_ANY = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.matchAnyOperator', + { + defaultMessage: 'is one of', + } +); + +export const CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.matchAnyOperator.not', + { + defaultMessage: 'is not one of', + } +); + +export const CONDITION_OPERATOR_TYPE_EXISTS = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.existsOperator', + { + defaultMessage: 'exists', + } +); + +export const CONDITION_OPERATOR_TYPE_DOES_NOT_EXIST = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.existsOperator.not', + { + defaultMessage: 'does not exist', + } +); + +export const CONDITION_OPERATOR_TYPE_LIST = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.listOperator', + { + defaultMessage: 'included in', + } +); + +export const CONDITION_OPERATOR_TYPE_NOT_IN_LIST = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.listOperator.not', + { + defaultMessage: 'is not included in', + } +); + +export const CONDITION_AND = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.and', + { + defaultMessage: 'AND', + } +); + +export const CONDITION_OS = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.os', + { + defaultMessage: 'OS', + } +); + +export const OS_WINDOWS = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.windows', + { + defaultMessage: 'Windows', + } +); + +export const OS_LINUX = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.linux', + { + defaultMessage: 'Linux', + } +); + +export const OS_MAC = i18n.translate( + 'exceptionList-components.exceptions.exceptionItem.card.conditions.macos', + { + defaultMessage: 'Mac', + } +); + +export const AFFECTED_RULES = (numRules: number) => + i18n.translate('exceptionList-components.exceptions.card.exceptionItem.affectedRules', { + values: { numRules }, + defaultMessage: 'Affects {numRules} {numRules, plural, =1 {rule} other {rules}}', + }); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.test.tsx new file mode 100644 index 0000000000000..3fe2d7eb6d0b3 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.test.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { ExceptionItems } from './exception_items'; + +import { ViewerStatus } from '../types'; +import { render } from '@testing-library/react'; + +const onCreateExceptionListItem = jest.fn(); +const onDeleteException = jest.fn(); +const onEditExceptionItem = jest.fn(); +const onPaginationChange = jest.fn(); + +const pagination = { pageIndex: 0, pageSize: 0, totalItemCount: 0 }; + +describe('ExceptionsViewerItems', () => { + describe('Viewing EmptyViewerState', () => { + it('it renders empty prompt if "viewerStatus" is "empty"', () => { + const wrapper = render( + null} + formattedDateComponent={() => null} + exceptionsUtilityComponent={() => null} + getFormattedComments={() => []} + /> + ); + // expect(wrapper).toMatchSnapshot(); + expect(wrapper.getByTestId('emptyViewerState')).toBeInTheDocument(); + expect(wrapper.queryByTestId('exceptionsContainer')).not.toBeInTheDocument(); + }); + + it('it renders no search results found prompt if "viewerStatus" is "empty_search"', () => { + const wrapper = render( + null} + formattedDateComponent={() => null} + exceptionsUtilityComponent={() => null} + getFormattedComments={() => []} + /> + ); + // expect(wrapper).toMatchSnapshot(); + expect(wrapper.getByTestId('emptySearchViewerState')).toBeInTheDocument(); + expect(wrapper.queryByTestId('exceptionsContainer')).not.toBeInTheDocument(); + }); + + it('it renders exceptions if "viewerStatus" and "null"', () => { + const wrapper = render( + null} + formattedDateComponent={() => null} + exceptionsUtilityComponent={() => null} + getFormattedComments={() => []} + /> + ); + // expect(wrapper).toMatchSnapshot(); + expect(wrapper.getByTestId('exceptionsContainer')).toBeTruthy(); + }); + }); + // TODO Add Exception Items and Pagination interactions +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.tsx new file mode 100644 index 0000000000000..80ab3d99f6eb8 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import type { FC } from 'react'; +import { EuiCommentProps, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import type { Pagination as PaginationType } from '@elastic/eui'; + +import type { + CommentsArray, + ExceptionListItemSchema, + ExceptionListTypeEnum, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { euiThemeVars } from '@kbn/ui-theme'; +import { EmptyViewerState, ExceptionItemCard, Pagination } from '../..'; + +import type { + RuleReferences, + ExceptionListItemIdentifiers, + ViewerStatus, + GetExceptionItemProps, +} from '../types'; + +const exceptionItemCss = css` + margin: ${euiThemeVars.euiSize} 0; + &div:first-child { + margin: ${euiThemeVars.euiSizeXS} 0 ${euiThemeVars.euiSize}; + } +`; + +interface ExceptionItemsProps { + lastUpdated: string | number | null; + viewerStatus: ViewerStatus; + isReadOnly: boolean; + emptyViewerTitle?: string; + emptyViewerBody?: string; + emptyViewerButtonText?: string; + exceptions: ExceptionListItemSchema[]; + listType: ExceptionListTypeEnum; + ruleReferences: RuleReferences; + pagination: PaginationType; + securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + formattedDateComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + exceptionsUtilityComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + getFormattedComments: (comments: CommentsArray) => EuiCommentProps[]; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common + onCreateExceptionListItem?: () => void; + onDeleteException: (arg: ExceptionListItemIdentifiers) => void; + onEditExceptionItem: (item: ExceptionListItemSchema) => void; + onPaginationChange: (arg: GetExceptionItemProps) => void; +} + +const ExceptionItemsComponent: FC = ({ + lastUpdated, + viewerStatus, + isReadOnly, + exceptions, + listType, + ruleReferences, + emptyViewerTitle, + emptyViewerBody, + emptyViewerButtonText, + pagination, + securityLinkAnchorComponent, + exceptionsUtilityComponent, + formattedDateComponent, + getFormattedComments, + onPaginationChange, + onDeleteException, + onEditExceptionItem, + onCreateExceptionListItem, +}) => { + const ExceptionsUtility = exceptionsUtilityComponent; + if (!exceptions.length || viewerStatus) + return ( + + ); + return ( + <> + + + + + {exceptions.map((exception) => ( + + + + ))} + + + + + + ); +}; + +ExceptionItemsComponent.displayName = 'ExceptionItemsComponent'; + +export const ExceptionItems = React.memo(ExceptionItemsComponent); + +ExceptionItems.displayName = 'ExceptionsItems'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.test.tsx new file mode 100644 index 0000000000000..4d97f198aa2b9 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; + +import { Pagination } from './pagination'; + +describe('Pagination', () => { + it('it invokes "onPaginationChange" when per page item is clicked', () => { + const mockOnPaginationChange = jest.fn(); + const wrapper = render( + + ); + + fireEvent.click(wrapper.getByTestId('tablePaginationPopoverButton')); + fireEvent.click(wrapper.getByTestId('tablePagination-50-rows')); + + expect(mockOnPaginationChange).toHaveBeenCalledWith({ + pagination: { pageIndex: 0, pageSize: 50, totalItemCount: 1 }, + }); + }); + + it('it invokes "onPaginationChange" when next clicked', () => { + const mockOnPaginationChange = jest.fn(); + const wrapper = render( + + ); + + fireEvent.click(wrapper.getByTestId('pagination-button-next')); + + expect(mockOnPaginationChange).toHaveBeenCalledWith({ + pagination: { pageIndex: 1, pageSize: 5, totalItemCount: 160 }, + }); + }); + + it('it invokes "onPaginationChange" when page clicked', () => { + const mockOnPaginationChange = jest.fn(); + const wrapper = render( + + ); + + fireEvent.click(wrapper.getByTestId('pagination-button-2')); + + expect(mockOnPaginationChange).toHaveBeenCalledWith({ + pagination: { pageIndex: 2, pageSize: 50, totalItemCount: 160 }, + }); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.tsx b/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.tsx new file mode 100644 index 0000000000000..30b029480e173 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { FC } from 'react'; +import { EuiTablePagination } from '@elastic/eui'; + +import type { PaginationProps } from '../types'; +import { usePagination } from './use_pagination'; + +const PaginationComponent: FC = ({ + dataTestSubj, + ariaLabel, + pagination, + onPaginationChange, +}) => { + const { + pageIndex, + pageCount, + pageSize, + pageSizeOptions, + + handleItemsPerPageChange, + handlePageIndexChange, + } = usePagination({ pagination, onPaginationChange }); + + return ( + + ); +}; + +PaginationComponent.displayName = 'PaginationComponent'; + +export const Pagination = React.memo(PaginationComponent); + +Pagination.displayName = 'Pagination'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts new file mode 100644 index 0000000000000..d190c88f10617 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { usePagination } from './use_pagination'; + +const onPaginationChange = jest.fn(); + +describe('usePagination', () => { + test('should return the correct EuiTablePagination props when all the pagination object properties are falsy', () => { + const pagination = { pageIndex: 0, pageSize: 0, totalItemCount: 0 }; + + const { result } = renderHook(() => usePagination({ pagination, onPaginationChange })); + const { pageCount, pageIndex, pageSize, pageSizeOptions } = result.current; + expect(pageCount).toEqual(0); + expect(pageIndex).toEqual(0); + expect(pageSize).toEqual(0); + expect(pageSizeOptions).toEqual(undefined); + }); + test('should return the correct pageCount when pagination properties are invalid', () => { + const pagination = { pageIndex: 0, pageSize: 10, totalItemCount: 0 }; + + const { result } = renderHook(() => usePagination({ pagination, onPaginationChange })); + const { pageCount } = result.current; + expect(pageCount).toEqual(0); + }); + test('should return the correct EuiTablePagination props when all the pagination object properties are turthy', () => { + const pagination = { pageIndex: 0, pageSize: 10, totalItemCount: 100 }; + + const { result } = renderHook(() => usePagination({ pagination, onPaginationChange })); + const { pageCount, pageIndex, pageSize } = result.current; + expect(pageCount).toEqual(10); + expect(pageIndex).toEqual(0); + expect(pageSize).toEqual(10); + }); + test('should call onPaginationChange with correct pageIndex when the Page changes', () => { + const pagination = { pageIndex: 0, pageSize: 10, totalItemCount: 100 }; + + const { result } = renderHook(() => usePagination({ pagination, onPaginationChange })); + const { handlePageIndexChange } = result.current; + + act(() => { + handlePageIndexChange(2); + }); + expect(onPaginationChange).toHaveBeenCalledWith({ + pagination: { pageIndex: 2, pageSize: 10, totalItemCount: 100 }, + }); + }); + test('should call onPaginationChange with correct pageSize when the number of items per change changes', () => { + const pagination = { pageIndex: 0, pageSize: 10, totalItemCount: 100 }; + + const { result } = renderHook(() => usePagination({ pagination, onPaginationChange })); + const { handleItemsPerPageChange } = result.current; + + act(() => { + handleItemsPerPageChange(100); + }); + expect(onPaginationChange).toHaveBeenCalledWith({ + pagination: { pageIndex: 0, pageSize: 100, totalItemCount: 100 }, + }); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.ts b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.ts new file mode 100644 index 0000000000000..d235658263687 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useCallback, useMemo } from 'react'; +import type { PaginationProps } from '../types'; + +export const usePagination = ({ pagination, onPaginationChange }: PaginationProps) => { + const { pageIndex, totalItemCount, pageSize, pageSizeOptions } = pagination; + + const pageCount = useMemo( + () => (isFinite(totalItemCount / pageSize) ? Math.ceil(totalItemCount / pageSize) : 0), + [pageSize, totalItemCount] + ); + + const handleItemsPerPageChange = useCallback( + (nextPageSize: number) => { + onPaginationChange({ + pagination: { + pageIndex, + pageSize: nextPageSize, + totalItemCount, + }, + }); + }, + [pageIndex, totalItemCount, onPaginationChange] + ); + + const handlePageIndexChange = useCallback( + (nextPageIndex: number) => { + onPaginationChange({ + pagination: { + pageIndex: nextPageIndex, + pageSize, + totalItemCount, + }, + }); + }, + [pageSize, totalItemCount, onPaginationChange] + ); + + return { + pageCount, + pageIndex, + pageSize, + pageSizeOptions, + + handleItemsPerPageChange, + handlePageIndexChange, + }; +}; diff --git a/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.test.tsx new file mode 100644 index 0000000000000..ac82bb3b6e850 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { SearchBar } from './search_bar'; + +describe('SearchBar', () => { + it('it does not display add exception button if user is read only', () => { + const wrapper = render( + + ); + + expect(wrapper.queryByTestId('searchBarButton')).not.toBeInTheDocument(); + }); + + it('it invokes "onAddExceptionClick" when user selects to add an exception item', () => { + const mockOnAddExceptionClick = jest.fn(); + const wrapper = render( + + ); + + const searchBtn = wrapper.getByTestId('searchBarButton'); + + fireEvent.click(searchBtn); + expect(searchBtn).toHaveTextContent('Add rule exception'); + expect(mockOnAddExceptionClick).toHaveBeenCalledWith('detection'); + }); + + it('it invokes "onAddExceptionClick" when user selects to add an endpoint exception item', () => { + const mockOnAddExceptionClick = jest.fn(); + const wrapper = render( + + ); + + const searchBtn = wrapper.getByTestId('searchBarButton'); + + fireEvent.click(searchBtn); + expect(searchBtn).toHaveTextContent('Add endpoint exception'); + expect(mockOnAddExceptionClick).toHaveBeenCalledWith('endpoint'); + }); + it('it invokes the "handlOnSearch" when the user add search query', () => { + const mockHandleOnSearch = jest.fn(); + const wrapper = render( + + ); + + const searchInput = wrapper.getByTestId('searchBar'); + fireEvent.change(searchInput, { target: { value: 'query' } }); + expect(mockHandleOnSearch).toBeCalledWith({ search: 'query' }); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.tsx b/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.tsx new file mode 100644 index 0000000000000..bb8dc6ee62559 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import type { FC } from 'react'; + +import type { SearchFilterConfig } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSearchBar } from '@elastic/eui'; +import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import type { GetExceptionItemProps } from '../types'; + +const ITEMS_SCHEMA = { + strict: true, + fields: { + created_by: { + type: 'string', + }, + description: { + type: 'string', + }, + id: { + type: 'string', + }, + item_id: { + type: 'string', + }, + list_id: { + type: 'string', + }, + name: { + type: 'string', + }, + os_types: { + type: 'string', + }, + tags: { + type: 'string', + }, + }, +}; +interface SearchBarProps { + addExceptionButtonText?: string; + placeholdertext?: string; + canAddException?: boolean; // TODO what is the default value + + // TODO: REFACTOR: not to send the listType and handle it in the Parent + // Exception list type used to determine what type of item is + // being created when "onAddExceptionClick" is invoked + listType: ExceptionListTypeEnum; + isSearching?: boolean; + dataTestSubj?: string; + filters?: SearchFilterConfig[]; // TODO about filters + onSearch: (arg: GetExceptionItemProps) => void; + onAddExceptionClick: (type: ExceptionListTypeEnum) => void; +} +const SearchBarComponent: FC = ({ + addExceptionButtonText, + placeholdertext, + canAddException, + listType, + isSearching, + dataTestSubj, + filters = [], + onSearch, + onAddExceptionClick, +}) => { + const handleOnSearch = useCallback( + ({ queryText }): void => { + onSearch({ search: queryText }); + }, + [onSearch] + ); + + const handleAddException = useCallback(() => { + // TODO: ASK YARA why we need to send the listType + onAddExceptionClick(listType); + }, [onAddExceptionClick, listType]); + + return ( + + + + + {!canAddException && ( + + + {addExceptionButtonText} + + + )} + + ); +}; + +SearchBarComponent.displayName = 'SearchBarComponent'; + +export const SearchBar = React.memo(SearchBarComponent); + +SearchBar.displayName = 'SearchBar'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/translations.ts b/packages/kbn-securitysolution-exception-list-components/src/translations.ts new file mode 100644 index 0000000000000..c919ef423c545 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/translations.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const EMPTY_VIEWER_STATE_EMPTY_TITLE = i18n.translate( + 'exceptionList-components.empty.viewer.state.empty.title', + { + defaultMessage: 'Add exceptions to this rule', + } +); + +export const EMPTY_VIEWER_STATE_EMPTY_BODY = i18n.translate( + 'exceptionList-components.empty.viewer.state.empty.body', + { + defaultMessage: 'There is no exception in your rule. Create your first rule exception.', + } +); +export const EMPTY_VIEWER_STATE_EMPTY_SEARCH_TITLE = i18n.translate( + 'exceptionList-components.empty.viewer.state.empty_search.search.title', + { + defaultMessage: 'No results match your search criteria', + } +); + +export const EMPTY_VIEWER_STATE_EMPTY_SEARCH_BODY = i18n.translate( + 'exceptionList-components.empty.viewer.state.empty_search.body', + { + defaultMessage: 'Try modifying your search', + } +); + +export const EMPTY_VIEWER_STATE_EMPTY_VIEWER_BUTTON = (exceptionType: string) => + i18n.translate('exceptionList-components.empty.viewer.state.empty.viewer_button', { + values: { exceptionType }, + defaultMessage: 'Create {exceptionType} exception', + }); + +export const EMPTY_VIEWER_STATE_ERROR_TITLE = i18n.translate( + 'exceptionList-components.empty.viewer.state.error_title', + { + defaultMessage: 'Unable to load exception items', + } +); + +export const EMPTY_VIEWER_STATE_ERROR_BODY = i18n.translate( + 'exceptionList-components.empty.viewer.state.error_body', + { + defaultMessage: + 'There was an error loading the exception items. Contact your administrator for help.', + } +); diff --git a/packages/kbn-securitysolution-exception-list-components/src/types/index.ts b/packages/kbn-securitysolution-exception-list-components/src/types/index.ts new file mode 100644 index 0000000000000..dbb402ca78451 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/types/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; + +import type { Pagination } from '@elastic/eui'; +import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; + +export interface GetExceptionItemProps { + pagination?: Pagination; + search?: string; + filters?: string; +} + +export interface PaginationProps { + dataTestSubj?: string; + ariaLabel?: string; + pagination: Pagination; + onPaginationChange: (arg: GetExceptionItemProps) => void; +} + +export enum ViewerStatus { + ERROR = 'error', + EMPTY = 'empty', + EMPTY_SEARCH = 'empty_search', + LOADING = 'loading', + SEARCHING = 'searching', + DELETING = 'deleting', +} + +export interface ExceptionListSummaryProps { + pagination: Pagination; + // Corresponds to last time exception items were fetched + lastUpdated: string | number | null; +} + +export type ViewerFlyoutName = 'addException' | 'editException' | null; + +export interface RuleReferences { + [key: string]: any[]; // TODO fix +} + +export interface ExceptionListItemIdentifiers { + id: string; + name: string; + namespaceType: NamespaceType; +} + +export enum ListTypeText { + ENDPOINT = 'endpoint', + DETECTION = 'empty', + RULE_DEFAULT = 'empty_search', +} + +export interface RuleReference { + name: string; + id: string; + ruleId: string; + exceptionLists: ExceptionListSchema[]; +} diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/index.ts b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/index.ts new file mode 100644 index 0000000000000..472a80575f27d --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ValueWithSpaceWarning } from './value_with_space_warning'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts new file mode 100644 index 0000000000000..8f6788d710a19 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useValueWithSpaceWarning } from './use_value_with_space_warning'; + +describe('useValueWithSpaceWarning', () => { + it('should return true when value is string and contains space', () => { + const { result } = renderHook(() => useValueWithSpaceWarning({ value: ' space before' })); + + const { showSpaceWarningIcon, warningText } = result.current; + expect(showSpaceWarningIcon).toBeTruthy(); + expect(warningText).toBeTruthy(); + }); + it('should return true when value is string and does not contain space', () => { + const { result } = renderHook(() => useValueWithSpaceWarning({ value: 'no space' })); + + const { showSpaceWarningIcon, warningText } = result.current; + expect(showSpaceWarningIcon).toBeFalsy(); + expect(warningText).toBeTruthy(); + }); + it('should return true when value is array and one of the elements contains space', () => { + const { result } = renderHook(() => + useValueWithSpaceWarning({ value: [' space before', 'no space'] }) + ); + + const { showSpaceWarningIcon, warningText } = result.current; + expect(showSpaceWarningIcon).toBeTruthy(); + expect(warningText).toBeTruthy(); + }); + it('should return true when value is array and none contains space', () => { + const { result } = renderHook(() => + useValueWithSpaceWarning({ value: ['no space', 'no space'] }) + ); + + const { showSpaceWarningIcon, warningText } = result.current; + expect(showSpaceWarningIcon).toBeFalsy(); + expect(warningText).toBeTruthy(); + }); + it('should return the tooltipIconText', () => { + const { result } = renderHook(() => + useValueWithSpaceWarning({ value: ' space before', tooltipIconText: 'Warning Text' }) + ); + + const { showSpaceWarningIcon, warningText } = result.current; + expect(showSpaceWarningIcon).toBeTruthy(); + expect(warningText).toEqual('Warning Text'); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.ts b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.ts new file mode 100644 index 0000000000000..bf407d2798c78 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { paramContainsSpace, autoCompletei18n } from '@kbn/securitysolution-autocomplete'; + +interface UseValueWithSpaceWarningResult { + showSpaceWarningIcon: boolean; + warningText: string; +} +interface UseValueWithSpaceWarningProps { + value: string | string[]; + tooltipIconText?: string; +} + +export const useValueWithSpaceWarning = ({ + value, + tooltipIconText, +}: UseValueWithSpaceWarningProps): UseValueWithSpaceWarningResult => { + const showSpaceWarningIcon = Array.isArray(value) + ? value.find(paramContainsSpace) + : paramContainsSpace(value); + + return { + showSpaceWarningIcon: !!showSpaceWarningIcon, + warningText: tooltipIconText || autoCompletei18n.FIELD_SPACE_WARNING, + }; +}; diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.test.tsx new file mode 100644 index 0000000000000..e19a54be48aa4 --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { ValueWithSpaceWarning } from '.'; + +import * as useValueWithSpaceWarningMock from './use_value_with_space_warning'; + +jest.mock('./use_value_with_space_warning'); + +describe('ValueWithSpaceWarning', () => { + beforeEach(() => { + // @ts-ignore + useValueWithSpaceWarningMock.useValueWithSpaceWarning = jest + .fn() + .mockReturnValue({ showSpaceWarningIcon: true, warningText: 'Warning Text' }); + }); + it('should not render if value is falsy', () => { + const container = render(); + expect(container.queryByTestId('valueWithSpaceWarningTooltip')).toBeFalsy(); + }); + it('should not render if showSpaceWarning is falsy', () => { + // @ts-ignore + useValueWithSpaceWarningMock.useValueWithSpaceWarning = jest + .fn() + .mockReturnValue({ showSpaceWarningIcon: false, warningText: '' }); + + const container = render(); + expect(container.queryByTestId('valueWithSpaceWarningTooltip')).toBeFalsy(); + }); + it('should render if showSpaceWarning is truthy', () => { + const container = render(); + expect(container.getByTestId('valueWithSpaceWarningTooltip')).toBeInTheDocument(); + }); + it('should show the tooltip when the icon is clicked', async () => { + const container = render(); + + fireEvent.mouseOver(container.getByTestId('valueWithSpaceWarningTooltip')); + expect(await container.findByText('Warning Text')).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx new file mode 100644 index 0000000000000..9cff0649efb9e --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { FC } from 'react'; +import { css } from '@emotion/css'; +import { euiThemeVars } from '@kbn/ui-theme'; + +import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { useValueWithSpaceWarning } from './use_value_with_space_warning'; + +interface ValueWithSpaceWarningProps { + value: string[] | string; + tooltipIconType?: string; + tooltipIconText?: string; +} +const containerCss = css` + display: inline; + margin-left: ${euiThemeVars.euiSizeXS}; +`; +export const ValueWithSpaceWarning: FC = ({ + value, + tooltipIconType = 'iInCircle', + tooltipIconText, +}) => { + const { showSpaceWarningIcon, warningText } = useValueWithSpaceWarning({ + value, + tooltipIconText, + }); + if (!showSpaceWarningIcon || !value) return null; + return ( +
    + + + +
    + ); +}; diff --git a/packages/kbn-securitysolution-exception-list-components/tsconfig.json b/packages/kbn-securitysolution-exception-list-components/tsconfig.json new file mode 100644 index 0000000000000..412652e0a8f9d --- /dev/null +++ b/packages/kbn-securitysolution-exception-list-components/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.d.ts" + ] +} diff --git a/x-pack/plugins/security_solution/public/jest.config.js b/x-pack/plugins/security_solution/public/jest.config.js index 5eb349b2c16e9..afa3e1b47efd4 100644 --- a/x-pack/plugins/security_solution/public/jest.config.js +++ b/x-pack/plugins/security_solution/public/jest.config.js @@ -14,7 +14,17 @@ module.exports = { coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/security_solution/public', coverageReporters: ['text', 'html'], - collectCoverageFrom: ['/x-pack/plugins/security_solution/public/**/*.{ts,tsx}'], + collectCoverageFrom: [ + '/x-pack/plugins/security_solution/public/**/*.{ts,tsx}', + '!/x-pack/plugins/security_solution/public/*.test.{ts,tsx}', + '!/x-pack/plugins/security_solution/public/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', + '!/x-pack/plugins/security_solution/public/*mock*.{ts,tsx}', + '!/x-pack/plugins/security_solution/public/*.test.{ts,tsx}', + '!/x-pack/plugins/security_solution/public/*.d.ts', + '!/x-pack/plugins/security_solution/public/*.config.ts', + '!/x-pack/plugins/security_solution/public/index.{js,ts,tsx}', + ], + // See: https://github.com/elastic/kibana/issues/117255, the moduleNameMapper creates mocks to avoid memory leaks from kibana core. moduleNameMapper: { 'core/server$': '/x-pack/plugins/security_solution/server/__mocks__/core.mock.ts', diff --git a/yarn.lock b/yarn.lock index 085f55146d4ed..4996c786772dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3566,6 +3566,10 @@ version "0.0.0" uid "" +"@kbn/securitysolution-exception-list-components@link:bazel-bin/packages/kbn-securitysolution-exception-list-components": + version "0.0.0" + uid "" + "@kbn/securitysolution-hook-utils@link:bazel-bin/packages/kbn-securitysolution-hook-utils": version "0.0.0" uid "" @@ -7683,6 +7687,10 @@ version "0.0.0" uid "" +"@types/kbn__securitysolution-exception-list-components@link:bazel-bin/packages/kbn-securitysolution-exception-list-components/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__securitysolution-hook-utils@link:bazel-bin/packages/kbn-securitysolution-hook-utils/npm_module_types": version "0.0.0" uid "" From 8c855d9fc286489921a058ce0999a5673ba47e04 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Wed, 28 Sep 2022 11:44:26 -0700 Subject: [PATCH 137/172] [8.5][Session View][Test] Added Unit test for Beta tag removal+ Price tier changes (#141874) * added test case for timeline-body-actions * added test for action button count * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../events_tab/events_query_tab_body.test.tsx | 44 +++++++++++ .../components/sessions_viewer/index.test.tsx | 57 ++++++++++++++ .../timeline/body/actions/index.test.tsx | 76 ++++++++++++++++++- 3 files changed, 176 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx index 2058cd4a7c614..0b13363a653a0 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -14,6 +14,12 @@ import type { EventsQueryTabBodyComponentProps } from './events_query_tab_body'; import { EventsQueryTabBody, ALERTS_EVENTS_HISTOGRAM_ID } from './events_query_tab_body'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; import * as tGridActions from '@kbn/timelines-plugin/public/store/t_grid/actions'; +import { licenseService } from '../../hooks/use_license'; + +const mockGetDefaultControlColumn = jest.fn(); +jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({ + getDefaultControlColumn: (props: number) => mockGetDefaultControlColumn(props), +})); jest.mock('../../lib/kibana', () => { const original = jest.requireActual('../../lib/kibana'); @@ -47,6 +53,19 @@ jest.mock('../../containers/use_full_screen', () => ({ }), })); +jest.mock('../../hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + isEnterprise: jest.fn(() => false), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); + describe('EventsQueryTabBody', () => { const commonProps: EventsQueryTabBodyComponentProps = { indexNames: ['test-index'], @@ -69,6 +88,7 @@ describe('EventsQueryTabBody', () => { ); expect(queryByText('MockedStatefulEventsViewer')).toBeInTheDocument(); + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4); }); it('renders the matrix histogram when globalFullScreen is false', () => { @@ -147,4 +167,28 @@ describe('EventsQueryTabBody', () => { expect(spy).toHaveBeenCalled(); }); + + it('only have 4 columns on Action bar for non-Enterprise user', () => { + render( + + + + ); + + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4); + }); + + it('shows 5 columns on Action bar for Enterprise user', () => { + const licenseServiceMock = licenseService as jest.Mocked; + + licenseServiceMock.isEnterprise.mockReturnValue(true); + + render( + + + + ); + + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx index a89a618972326..47a1bc4fa6514 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx @@ -14,6 +14,7 @@ import { TimelineId } from '@kbn/timelines-plugin/common'; import type { SessionsComponentsProps } from './types'; import type { TimelineModel } from '../../../timelines/store/timeline/model'; import { useGetUserCasesPermissions } from '../../lib/kibana'; +import { licenseService } from '../../hooks/use_license'; jest.mock('../../lib/kibana'); @@ -47,10 +48,28 @@ type Props = Partial & { entityType: EntityType; }; +const mockGetDefaultControlColumn = jest.fn(); +jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({ + getDefaultControlColumn: (props: number) => mockGetDefaultControlColumn(props), +})); + const TEST_PREFIX = 'security_solution:sessions_viewer:sessions_view'; const callFilters = jest.fn(); +jest.mock('../../hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + isEnterprise: jest.fn(() => false), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); + // creating a dummy component for testing TGrid to avoid mocking all the implementation details // but still test if the TGrid will render properly const SessionsViewerTGrid: React.FC = ({ columns, start, end, id, filters, entityType }) => { @@ -144,4 +163,42 @@ describe('SessionsView', () => { ]); }); }); + it('Action tab should have 4 columns for non Enterprise users', async () => { + render( + + + + ); + + await waitFor(() => { + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4); + }); + }); + + it('Action tab should have 5 columns for Enterprise or above users', async () => { + const licenseServiceMock = licenseService as jest.Mocked; + + licenseServiceMock.isEnterprise.mockReturnValue(true); + render( + + + + ); + + await waitFor(() => { + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5); + }); + }); + + it('Action tab should have 5 columns when accessed via K8S dahsboard', async () => { + render( + + + + ); + + await waitFor(() => { + expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 004850ede7d88..7bc64b9900640 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -7,12 +7,13 @@ import { mount } from 'enzyme'; import React from 'react'; - +import { TimelineId } from '../../../../../../common/types/timeline'; import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../../common/mock'; import { Actions, isAlert } from '.'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; +import { licenseService } from '../../../../../common/hooks/use_license'; jest.mock('../../../../../detections/components/user_info', () => ({ useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), @@ -63,6 +64,19 @@ jest.mock('../../../../../common/lib/kibana', () => { }; }); +jest.mock('../../../../../common/hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + isEnterprise: jest.fn(() => false), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); + const defaultProps = { ariaRowindex: 2, checked: false, @@ -122,6 +136,7 @@ describe('Actions', () => { expect(wrapper.find('[data-test-subj="select-event"]').exists()).toBe(false); }); + describe('Alert context menu enabled?', () => { test('it disables for eventType=raw', () => { const wrapper = mount( @@ -225,6 +240,65 @@ describe('Actions', () => { expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(false); }); + + test('it should not show session view button on action tabs for basic users', () => { + const ecsData = { + ...mockTimelineData[0].ecs, + event: { kind: ['alert'] }, + agent: { type: ['endpoint'] }, + process: { entry_leader: { entity_id: ['test_id'] } }, + }; + + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toEqual(false); + }); + + test('it should show session view button on action tabs when user access the session viewer via K8S dashboard', () => { + const ecsData = { + ...mockTimelineData[0].ecs, + event: { kind: ['alert'] }, + agent: { type: ['endpoint'] }, + process: { entry_leader: { entity_id: ['test_id'] } }, + }; + + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toEqual(true); + }); + + test('it should show session view button on action tabs for enterprise users', () => { + const licenseServiceMock = licenseService as jest.Mocked; + + licenseServiceMock.isEnterprise.mockReturnValue(true); + + const ecsData = { + ...mockTimelineData[0].ecs, + event: { kind: ['alert'] }, + agent: { type: ['endpoint'] }, + process: { entry_leader: { entity_id: ['test_id'] } }, + }; + + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toEqual(true); + }); }); describe('isAlert', () => { From aff59ee0161c5ade3033afdf69a5ac57c5ed4e4b Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Molins Date: Wed, 28 Sep 2022 20:50:55 +0200 Subject: [PATCH 138/172] Add infrastructure category (#142115) --- src/plugins/custom_integrations/common/index.ts | 1 + x-pack/plugins/fleet/common/types/models/package_spec.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index a3ab30a526ca6..e52a5deac59e5 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -23,6 +23,7 @@ export const INTEGRATION_CATEGORY_DISPLAY = { datastore: 'Datastore', elastic_stack: 'Elastic Stack', google_cloud: 'Google Cloud', + infrastructure: 'Infrastructure', kubernetes: 'Kubernetes', languages: 'Languages', message_queue: 'Message queue', diff --git a/x-pack/plugins/fleet/common/types/models/package_spec.ts b/x-pack/plugins/fleet/common/types/models/package_spec.ts index 4463bb81097e2..52f993499ea4e 100644 --- a/x-pack/plugins/fleet/common/types/models/package_spec.ts +++ b/x-pack/plugins/fleet/common/types/models/package_spec.ts @@ -42,6 +42,7 @@ export type PackageSpecCategory = | 'datastore' | 'elastic_stack' | 'google_cloud' + | 'infrastructure' | 'kubernetes' | 'languages' | 'message_queue' From 1717d61a76134b50df31dba437c4142310b70ab9 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:51:14 +0200 Subject: [PATCH 139/172] [Fleet] moved Agent activity and Add agent buttons to a new row above search bar (#142046) * moved Agent activity and Add agent buttons to a new row above search bar * moved show bulk action button visibility logic one level up --- .../components/bulk_actions.tsx | 59 ++++----- ...est.tsx => search_and_filter_bar.test.tsx} | 53 ++++++-- .../components/search_and_filter_bar.tsx | 121 +++++++++--------- 3 files changed, 132 insertions(+), 101 deletions(-) rename x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/{bulk_actions.test.tsx => search_and_filter_bar.test.tsx} (67%) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index 10ced1a5c0323..356753a0d0045 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -6,7 +6,6 @@ */ import React, { useMemo, useState } from 'react'; -import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, @@ -32,9 +31,6 @@ import { getCommonTags } from '../utils'; import type { SelectionMode } from './types'; import { TagsAddRemove } from './tags_add_remove'; -const FlexItem = styled(EuiFlexItem)` - height: ${(props) => props.theme.eui.euiSizeL}; -`; export interface Props { totalAgents: number; totalInactiveAgents: number; @@ -233,38 +229,31 @@ export const AgentBulkActions: React.FunctionComponent = ({ /> )} - {(selectionMode === 'manual' && selectedAgents.length) || - (selectionMode === 'query' && totalAgents > 0) ? ( - <> - - - - - } - isOpen={isMenuOpen} - closePopover={closeMenu} - panelPaddingSize="none" - anchorPosition="downLeft" + + - - - - - ) : ( - - )} + + + } + isOpen={isMenuOpen} + closePopover={closeMenu} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx similarity index 67% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx index 71e673fd30e19..33fd16419a1bd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx @@ -20,8 +20,7 @@ import { FleetStatusProvider, ConfigContext, KibanaVersionContext } from '../../ import { getMockTheme } from '../../../../../../mocks'; -import { AgentBulkActions } from './bulk_actions'; -import type { Props } from './bulk_actions'; +import { SearchAndFilterBar } from './search_and_filter_bar'; const mockTheme = getMockTheme({ eui: { @@ -29,13 +28,19 @@ const mockTheme = getMockTheme({ }, }); -const TestComponent = (props: Props) => ( +jest.mock('../../../../components', () => { + return { + SearchBar: () =>
    , + }; +}); + +const TestComponent = (props: any) => ( - + @@ -43,10 +48,10 @@ const TestComponent = (props: Props) => ( ); -describe('AgentBulkActions', () => { +describe('SearchAndFilterBar', () => { it('should show no Actions button when no agent is selected', async () => { const selectedAgents: Agent[] = []; - const props: Props = { + const props: any = { totalAgents: 10, totalInactiveAgents: 2, selectionMode: 'manual', @@ -54,8 +59,12 @@ describe('AgentBulkActions', () => { selectedAgents, refreshAgents: () => undefined, visibleAgents: [], - allTags: [], + tags: [], agentPolicies: [], + selectedStatus: [], + selectedTags: [], + selectedAgentPolicies: [], + showAgentActivityTour: {}, }; const testBed = registerTestBed(TestComponent)(props); const { exists } = testBed; @@ -76,7 +85,7 @@ describe('AgentBulkActions', () => { local_metadata: {}, }, ]; - const props: Props = { + const props: any = { totalAgents: 10, totalInactiveAgents: 2, selectionMode: 'manual', @@ -84,8 +93,34 @@ describe('AgentBulkActions', () => { selectedAgents, refreshAgents: () => undefined, visibleAgents: [], - allTags: [], + tags: [], + agentPolicies: [], + selectedStatus: [], + selectedTags: [], + selectedAgentPolicies: [], + showAgentActivityTour: {}, + }; + const testBed = registerTestBed(TestComponent)(props); + const { exists } = testBed; + + expect(exists('agentBulkActionsButton')).not.toBeNull(); + }); + + it('should show an Actions button when agents selected in query mode', async () => { + const props: any = { + totalAgents: 10, + totalInactiveAgents: 2, + selectionMode: 'query', + currentQuery: '', + selectedAgents: [], + refreshAgents: () => undefined, + visibleAgents: [], + tags: [], agentPolicies: [], + selectedStatus: [], + selectedTags: [], + selectedAgentPolicies: [], + showAgentActivityTour: {}, }; const testBed = registerTestBed(TestComponent)(props); const { exists } = testBed; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index e90e1ae713227..9b16136df2d96 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -69,6 +69,10 @@ const ClearAllTagsFilterItem = styled(EuiFilterSelectItem)` padding: ${(props) => props.theme.eui.euiSizeS}; `; +const FlexEndEuiFlexItem = styled(EuiFlexItem)` + align-self: flex-end; +`; + export const SearchAndFilterBar: React.FunctionComponent<{ agentPolicies: AgentPolicy[]; draftKuery: string; @@ -151,7 +155,51 @@ export const SearchAndFilterBar: React.FunctionComponent<{ return ( <> {/* Search and filter bar */} - + + + + + + + + + } + > + + + + + + + + } + > + + + + + + + @@ -328,63 +376,22 @@ export const SearchAndFilterBar: React.FunctionComponent<{ - {selectedAgents.length === 0 && ( - - - } - > - - - - + {(selectionMode === 'manual' && selectedAgents.length) || + (selectionMode === 'query' && totalAgents > 0) ? ( + + - )} - - - - {selectedAgents.length === 0 && ( - - - } - > - - - - - - )} - - - + ) : null} From 2d44c3597864288390951704f9beddd7212c199e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 20:09:50 +0100 Subject: [PATCH 140/172] skip flaky suite (#107034) --- test/plugin_functional/test_suites/telemetry/telemetry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/plugin_functional/test_suites/telemetry/telemetry.ts b/test/plugin_functional/test_suites/telemetry/telemetry.ts index 3b087c2705c10..1c68abd5426d3 100644 --- a/test/plugin_functional/test_suites/telemetry/telemetry.ts +++ b/test/plugin_functional/test_suites/telemetry/telemetry.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const browser = getService('browser'); const PageObjects = getPageObjects(['common']); - describe('Telemetry service', () => { + // FLAKY: https://github.com/elastic/kibana/issues/107034 + describe.skip('Telemetry service', () => { const checkCanSendTelemetry = (): Promise => { return browser.executeAsync((cb) => { (window as unknown as Record Promise>) From 0e6d079bfe058fa6e02eb29ec195957f1896e595 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Sep 2022 20:12:29 +0100 Subject: [PATCH 141/172] skip flaky suite (#140546) --- .../test/security_solution_endpoint/apps/endpoint/responder.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts index 9caf231089860..ddcbbc6251fdf 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts @@ -116,7 +116,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('from timeline', () => { + // FLAKY: https://github.com/elastic/kibana/issues/140546 + describe.skip('from timeline', () => { let timeline: TimelineResponse; before(async () => { From f5b6a2c43ba88232b9789b96d562b230a3ebc362 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Wed, 28 Sep 2022 15:16:51 -0400 Subject: [PATCH 142/172] [Portable Dashboards] Remove Saved Object Class (#138774) * Removed dashboard saved object class, replaced with dashboard saved object service --- .../public/services/plugin_services.stub.ts | 11 +- .../public/services/plugin_services.ts | 11 +- .../embeddable/embeddable_references.test.ts | 76 ----- .../embeddable/embeddable_references.ts | 69 ---- .../dashboard/common/embeddable/types.ts | 16 - src/plugins/dashboard/common/index.ts | 32 +- .../dashboard_panel_converters.test.ts} | 2 +- .../dashboard_panel_converters.ts} | 16 +- .../dashboard_container_references.test.ts} | 2 +- .../dashboard_container_references.ts} | 0 ...dashboard_saved_object_references.test.ts} | 4 +- .../dashboard_saved_object_references.ts} | 108 +++--- src/plugins/dashboard/common/types.ts | 138 ++++---- .../actions/add_to_library_action.test.tsx | 2 +- .../actions/add_to_library_action.tsx | 3 +- .../actions/clone_panel_action.test.tsx | 2 +- .../actions/clone_panel_action.tsx | 5 +- .../actions/copy_to_dashboard_action.tsx | 3 +- .../actions/copy_to_dashboard_modal.tsx | 4 +- .../actions/expand_panel_action.tsx | 4 +- .../filters_notification_badge.test.tsx | 21 +- .../public/application/actions/index.ts | 93 +++-- .../library_notification_action.test.tsx | 11 +- .../actions/library_notification_action.tsx | 6 +- .../actions/library_notification_popover.tsx | 4 +- .../actions/replace_panel_action.tsx | 3 +- .../unlink_from_library_action.test.tsx | 4 +- .../actions/unlink_from_library_action.tsx | 3 +- .../public/application/dashboard_app.tsx | 30 +- .../public/application/dashboard_router.tsx | 111 +++--- .../embeddable/dashboard_constants.ts | 13 - .../embeddable/dashboard_container.tsx | 2 +- .../dashboard_container_factory.tsx | 16 +- .../embeddable/grid/dashboard_grid.tsx | 6 +- .../public/application/embeddable/index.ts | 7 - .../panel/create_panel_state.test.ts | 2 +- .../embeddable/panel/create_panel_state.ts | 2 +- .../panel/dashboard_panel_placement.ts | 4 +- .../embeddable/placeholder/index.ts | 5 +- .../placeholder/placeholder_embeddable.tsx | 4 +- .../placeholder_embeddable_factory.ts | 3 +- .../hooks/use_dashboard_app_state.test.tsx | 126 +++---- .../hooks/use_dashboard_app_state.ts | 169 ++++------ .../lib/build_dashboard_container.ts | 8 +- .../lib/convert_dashboard_panels.ts | 32 -- .../lib/convert_dashboard_state.ts | 69 +--- .../lib/dashboard_control_group.ts | 32 +- ... => dashboard_session_restoration.test.ts} | 9 +- .../lib/dashboard_session_restoration.ts | 15 +- .../application/lib/dashboard_tagging.ts | 31 -- .../application/lib/diff_dashboard_state.ts | 71 +++- .../public/application/lib/filter_utils.ts | 23 +- .../dashboard/public/application/lib/index.ts | 32 +- .../lib/load_dashboard_by_title.ts | 31 -- .../load_dashboard_history_location_state.ts | 4 +- .../lib/load_saved_dashboard_state.ts | 74 ---- .../application/lib/migrate_app_state.test.ts | 164 --------- .../application/lib/migrate_app_state.ts | 87 ----- .../public/application/lib/save_dashboard.ts | 120 ------- .../lib/sync_dashboard_container_input.ts | 14 +- .../lib/sync_dashboard_filter_state.ts | 52 +-- .../lib/sync_dashboard_url_state.ts | 46 ++- .../public/application/lib/url.test.ts | 35 -- .../dashboard/public/application/lib/url.ts | 24 -- .../listing/dashboard_listing.test.tsx | 121 +++---- .../application/listing/dashboard_listing.tsx | 81 +++-- .../dashboard_unsaved_listing.test.tsx | 89 ++--- .../listing/dashboard_unsaved_listing.tsx | 49 ++- .../state/dashboard_state_slice.ts | 13 +- .../test_helpers/get_saved_dashboard_mock.ts | 36 -- .../public/application/test_helpers/index.ts | 2 - .../test_helpers/make_default_services.ts | 48 --- .../application/top_nav/dashboard_top_nav.tsx | 240 +++++++------ .../top_nav/show_share_modal.test.tsx | 8 +- .../application/top_nav/show_share_modal.tsx | 31 +- .../dashboard/public/dashboard_constants.ts | 35 ++ .../dashboard/public/dashboard_strings.ts | 16 +- src/plugins/dashboard/public/index.ts | 7 +- src/plugins/dashboard/public/locator.ts | 2 +- src/plugins/dashboard/public/plugin.tsx | 129 ++----- .../saved_dashboards/saved_dashboard.ts | 200 ----------- .../saved_dashboards/saved_dashboards.ts | 36 -- .../dashboard_saved_object.stub.ts | 77 +++++ .../dashboard_saved_object_service.ts | 62 ++++ .../check_for_duplicate_dashboard_title.ts | 62 ++++ .../lib/find_dashboard_saved_objects.ts | 93 +++++ .../load_dashboard_state_from_saved_object.ts | 201 +++++++++++ .../save_dashboard_state_to_saved_object.ts | 182 ++++++++++ .../services/dashboard_saved_object/types.ts | 63 ++++ .../services/embeddable/embeddable.stub.ts | 6 +- .../services/embeddable/embeddable_service.ts | 23 +- .../public/services/embeddable/types.ts | 17 +- .../public/services/plugin_services.stub.ts | 4 +- .../public/services/plugin_services.ts | 22 +- .../public/services/saved_object_loader.ts | 180 ---------- .../saved_objects/saved_objects.stub.ts | 21 -- .../saved_objects/saved_objects_service.ts | 26 -- .../public/services/saved_objects/types.ts | 13 - .../saved_objects_tagging.stub.ts | 5 +- .../saved_objects_tagging_service.ts | 12 +- .../services/saved_objects_tagging/types.ts | 5 +- .../dashboard/public/services/types.ts | 7 +- src/plugins/dashboard/public/types.ts | 30 +- .../dashboard_container_embeddable_factory.ts | 5 +- .../saved_objects/dashboard_migrations.ts | 318 ------------------ ...dashboard.ts => dashboard_saved_object.ts} | 2 +- .../dashboard/server/saved_objects/index.ts | 2 +- .../server/saved_objects/is_dashboard_doc.ts | 37 -- ...dashboard_saved_object_migrations.test.ts} | 14 +- .../dashboard_saved_object_migrations.ts | 48 +++ .../migrate_by_value_dashboard_panels.ts | 97 ++++++ .../migrate_extract_panel_references.ts | 56 +++ .../migrations/migrate_hidden_titles.ts | 65 ++++ .../migrate_index_pattern_reference.test.ts} | 2 +- .../migrate_index_pattern_reference.ts} | 0 .../migrate_match_all_query.test.ts | 0 .../migrate_match_all_query.ts | 2 +- .../migrations/migrate_to_730}/index.ts | 5 +- .../migrate_to_730_panels.test.ts | 7 +- .../migrate_to_730}/migrate_to_730_panels.ts | 21 +- .../migrate_to_730/migrations_700.ts | 90 +++++ .../migrate_to_730}/migrations_730.test.ts | 13 +- .../migrate_to_730}/migrations_730.ts | 35 +- .../move_filters_to_query.test.ts | 0 .../migrate_to_730}/move_filters_to_query.ts | 0 .../migrations/migrate_to_730/readme.md | 3 + .../migrations/migrate_to_730/types.ts | 182 ++++++++++ .../server/usage/dashboard_telemetry.test.ts | 12 +- .../server/usage/dashboard_telemetry.ts | 4 +- .../dashboard_telemetry_collection_task.ts | 13 +- .../usage/find_by_value_embeddables.test.ts | 8 +- .../server/usage/find_by_value_embeddables.ts | 4 +- .../apps/dashboard/group3/bwc_shared_urls.ts | 33 +- .../apps/dashboard/group3/dashboard_state.ts | 2 +- .../apps/dashboard/group4/dashboard_empty.ts | 2 +- .../functional/page_objects/dashboard_page.ts | 26 ++ .../add_swimlane_to_dashboard_controls.tsx | 4 +- .../use_dashboards_table.tsx | 4 +- .../application/services/dashboard_service.ts | 5 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../feature_controls/dashboard_security.ts | 155 +++------ .../feature_controls/dashboard_spaces.ts | 67 ++-- 144 files changed, 2575 insertions(+), 3151 deletions(-) delete mode 100644 src/plugins/dashboard/common/embeddable/embeddable_references.test.ts delete mode 100644 src/plugins/dashboard/common/embeddable/embeddable_references.ts delete mode 100644 src/plugins/dashboard/common/embeddable/types.ts rename src/plugins/dashboard/common/{embeddable/embeddable_saved_object_converters.test.ts => lib/dashboard_panel_converters.test.ts} (98%) rename src/plugins/dashboard/common/{embeddable/embeddable_saved_object_converters.ts => lib/dashboard_panel_converters.ts} (75%) rename src/plugins/dashboard/common/{embeddable/dashboard_container_persistable_state.test.ts => persistable_state/dashboard_container_references.test.ts} (99%) rename src/plugins/dashboard/common/{embeddable/dashboard_container_persistable_state.ts => persistable_state/dashboard_container_references.ts} (100%) rename src/plugins/dashboard/common/{saved_dashboard_references.test.ts => persistable_state/dashboard_saved_object_references.test.ts} (98%) rename src/plugins/dashboard/common/{saved_dashboard_references.ts => persistable_state/dashboard_saved_object_references.ts} (96%) delete mode 100644 src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts delete mode 100644 src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts rename src/plugins/dashboard/public/application/lib/{session_restoration.test.ts => dashboard_session_restoration.test.ts} (89%) delete mode 100644 src/plugins/dashboard/public/application/lib/dashboard_tagging.ts delete mode 100644 src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts delete mode 100644 src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts delete mode 100644 src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts delete mode 100644 src/plugins/dashboard/public/application/lib/migrate_app_state.ts delete mode 100644 src/plugins/dashboard/public/application/lib/save_dashboard.ts delete mode 100644 src/plugins/dashboard/public/application/lib/url.test.ts delete mode 100644 src/plugins/dashboard/public/application/lib/url.ts delete mode 100644 src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts delete mode 100644 src/plugins/dashboard/public/application/test_helpers/make_default_services.ts delete mode 100644 src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts delete mode 100644 src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_saved_object/types.ts delete mode 100644 src/plugins/dashboard/public/services/saved_object_loader.ts delete mode 100644 src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts delete mode 100644 src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts delete mode 100644 src/plugins/dashboard/public/services/saved_objects/types.ts delete mode 100644 src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts rename src/plugins/dashboard/server/saved_objects/{dashboard.ts => dashboard_saved_object.ts} (97%) delete mode 100644 src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts rename src/plugins/dashboard/server/saved_objects/{dashboard_migrations.test.ts => migrations/dashboard_saved_object_migrations.test.ts} (99%) create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.ts create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_by_value_dashboard_panels.ts create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_hidden_titles.ts rename src/plugins/dashboard/server/saved_objects/{replace_index_pattern_reference.test.ts => migrations/migrate_index_pattern_reference.test.ts} (94%) rename src/plugins/dashboard/server/saved_objects/{replace_index_pattern_reference.ts => migrations/migrate_index_pattern_reference.ts} (100%) rename src/plugins/dashboard/server/saved_objects/{ => migrations}/migrate_match_all_query.test.ts (100%) rename src/plugins/dashboard/server/saved_objects/{ => migrations}/migrate_match_all_query.ts (100%) rename src/plugins/dashboard/{public/saved_dashboards => server/saved_objects/migrations/migrate_to_730}/index.ts (73%) rename src/plugins/dashboard/{common => server/saved_objects/migrations/migrate_to_730}/migrate_to_730_panels.test.ts (99%) rename src/plugins/dashboard/{common => server/saved_objects/migrations/migrate_to_730}/migrate_to_730_panels.ts (98%) create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_700.ts rename src/plugins/dashboard/server/saved_objects/{ => migrations/migrate_to_730}/migrations_730.test.ts (96%) rename src/plugins/dashboard/server/saved_objects/{ => migrations/migrate_to_730}/migrations_730.ts (69%) rename src/plugins/dashboard/server/saved_objects/{ => migrations/migrate_to_730}/move_filters_to_query.test.ts (100%) rename src/plugins/dashboard/server/saved_objects/{ => migrations/migrate_to_730}/move_filters_to_query.ts (100%) create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/readme.md create mode 100644 src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/types.ts diff --git a/src/plugins/controls/public/services/plugin_services.stub.ts b/src/plugins/controls/public/services/plugin_services.stub.ts index 485891ff6ef94..08be260ce052c 100644 --- a/src/plugins/controls/public/services/plugin_services.stub.ts +++ b/src/plugins/controls/public/services/plugin_services.stub.ts @@ -27,16 +27,15 @@ import { themeServiceFactory } from './theme/theme.story'; import { registry as stubRegistry } from './plugin_services.story'; export const providers: PluginServiceProviders = { - http: new PluginServiceProvider(httpServiceFactory), + controls: new PluginServiceProvider(controlsServiceFactory), data: new PluginServiceProvider(dataServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), dataViews: new PluginServiceProvider(dataViewsServiceFactory), + http: new PluginServiceProvider(httpServiceFactory), + optionsList: new PluginServiceProvider(optionsListServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), settings: new PluginServiceProvider(settingsServiceFactory), - unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), theme: new PluginServiceProvider(themeServiceFactory), - - controls: new PluginServiceProvider(controlsServiceFactory), - optionsList: new PluginServiceProvider(optionsListServiceFactory), + unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), }; export const pluginServices = new PluginServices(); diff --git a/src/plugins/controls/public/services/plugin_services.ts b/src/plugins/controls/public/services/plugin_services.ts index 4debd0e8c9eba..f1811063e39a5 100644 --- a/src/plugins/controls/public/services/plugin_services.ts +++ b/src/plugins/controls/public/services/plugin_services.ts @@ -30,16 +30,15 @@ export const providers: PluginServiceProviders< ControlsServices, KibanaPluginServiceParams > = { - http: new PluginServiceProvider(httpServiceFactory), + controls: new PluginServiceProvider(controlsServiceFactory), data: new PluginServiceProvider(dataServiceFactory), - unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), dataViews: new PluginServiceProvider(dataViewsServiceFactory), + http: new PluginServiceProvider(httpServiceFactory), + optionsList: new PluginServiceProvider(optionsListServiceFactory, ['data', 'http']), + overlays: new PluginServiceProvider(overlaysServiceFactory), settings: new PluginServiceProvider(settingsServiceFactory), theme: new PluginServiceProvider(themeServiceFactory), - - optionsList: new PluginServiceProvider(optionsListServiceFactory, ['data', 'http']), - controls: new PluginServiceProvider(controlsServiceFactory), + unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), }; export const pluginServices = new PluginServices(); diff --git a/src/plugins/dashboard/common/embeddable/embeddable_references.test.ts b/src/plugins/dashboard/common/embeddable/embeddable_references.test.ts deleted file mode 100644 index 3a6475b60251c..0000000000000 --- a/src/plugins/dashboard/common/embeddable/embeddable_references.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - ExtractDeps, - extractPanelsReferences, - InjectDeps, - injectPanelsReferences, -} from './embeddable_references'; -import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; -import { SavedDashboardPanel } from '../types'; -import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; - -const embeddablePersistableStateService = createEmbeddablePersistableStateServiceMock(); -const deps: InjectDeps & ExtractDeps = { - embeddablePersistableStateService, -}; - -test('inject/extract panel references', () => { - embeddablePersistableStateService.extract.mockImplementationOnce((state) => { - const { HARDCODED_ID, ...restOfState } = state as unknown as Record; - return { - state: restOfState as EmbeddableStateWithType, - references: [{ id: HARDCODED_ID as string, name: 'refName', type: 'type' }], - }; - }); - - embeddablePersistableStateService.inject.mockImplementationOnce((state, references) => { - const ref = references.find((r) => r.name === 'refName'); - return { - ...state, - HARDCODED_ID: ref!.id, - }; - }); - - const savedDashboardPanel: SavedDashboardPanel = { - type: 'search', - embeddableConfig: { - HARDCODED_ID: 'IMPORTANT_HARDCODED_ID', - }, - id: 'savedObjectId', - panelIndex: '123', - gridData: { - x: 0, - y: 0, - h: 15, - w: 15, - i: '123', - }, - version: '7.0.0', - }; - - const [{ panel: extractedPanel, references }] = extractPanelsReferences( - [savedDashboardPanel], - deps - ); - expect(extractedPanel.embeddableConfig).toEqual({}); - expect(references).toMatchInlineSnapshot(` - Array [ - Object { - "id": "IMPORTANT_HARDCODED_ID", - "name": "refName", - "type": "type", - }, - ] - `); - - const [injectedPanel] = injectPanelsReferences([extractedPanel], references, deps); - - expect(injectedPanel).toEqual(savedDashboardPanel); -}); diff --git a/src/plugins/dashboard/common/embeddable/embeddable_references.ts b/src/plugins/dashboard/common/embeddable/embeddable_references.ts deleted file mode 100644 index 6664f70d3392a..0000000000000 --- a/src/plugins/dashboard/common/embeddable/embeddable_references.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { omit } from 'lodash'; -import { SavedObjectReference } from '@kbn/core/types'; -import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types'; -import { - convertSavedDashboardPanelToPanelState, - convertPanelStateToSavedDashboardPanel, -} from './embeddable_saved_object_converters'; -import { SavedDashboardPanel } from '../types'; - -export interface InjectDeps { - embeddablePersistableStateService: EmbeddablePersistableStateService; -} - -export function injectPanelsReferences( - panels: SavedDashboardPanel[], - references: SavedObjectReference[], - deps: InjectDeps -): SavedDashboardPanel[] { - const result: SavedDashboardPanel[] = []; - for (const panel of panels) { - const embeddableState = convertSavedDashboardPanelToPanelState(panel); - embeddableState.explicitInput = omit( - deps.embeddablePersistableStateService.inject( - { ...embeddableState.explicitInput, type: panel.type }, - references - ), - 'type' - ); - result.push(convertPanelStateToSavedDashboardPanel(embeddableState, panel.version)); - } - return result; -} - -export interface ExtractDeps { - embeddablePersistableStateService: EmbeddablePersistableStateService; -} - -export function extractPanelsReferences( - panels: SavedDashboardPanel[], - deps: ExtractDeps -): Array<{ panel: SavedDashboardPanel; references: SavedObjectReference[] }> { - const result: Array<{ panel: SavedDashboardPanel; references: SavedObjectReference[] }> = []; - - for (const panel of panels) { - const embeddable = convertSavedDashboardPanelToPanelState(panel); - const { state: embeddableInputWithExtractedReferences, references } = - deps.embeddablePersistableStateService.extract({ - ...embeddable.explicitInput, - type: embeddable.type, - }); - embeddable.explicitInput = omit(embeddableInputWithExtractedReferences, 'type'); - - const newPanel = convertPanelStateToSavedDashboardPanel(embeddable, panel.version); - result.push({ - panel: newPanel, - references, - }); - } - - return result; -} diff --git a/src/plugins/dashboard/common/embeddable/types.ts b/src/plugins/dashboard/common/embeddable/types.ts deleted file mode 100644 index d786078766f78..0000000000000 --- a/src/plugins/dashboard/common/embeddable/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type GridData = { - w: number; - h: number; - x: number; - y: number; - i: string; -}; diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index 73e01693977d9..81833f8a8f18e 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -6,24 +6,28 @@ * Side Public License, v 1. */ -export type { GridData } from './embeddable/types'; -export type { - RawSavedDashboardPanel730ToLatest, - DashboardDoc730ToLatest, - DashboardDoc700To720, - DashboardDocPre700, -} from './bwc/types'; export type { + GridData, + DashboardPanelMap, + SavedDashboardPanel, + DashboardAttributes, + DashboardPanelState, DashboardContainerStateWithType, - SavedDashboardPanelTo60, - SavedDashboardPanel610, - SavedDashboardPanel620, - SavedDashboardPanel630, - SavedDashboardPanel640To720, - SavedDashboardPanel730ToLatest, } from './types'; -export { migratePanelsTo730 } from './migrate_to_730_panels'; +export { + injectReferences, + extractReferences, +} from './persistable_state/dashboard_saved_object_references'; + +export { createInject, createExtract } from './persistable_state/dashboard_container_references'; + +export { + convertPanelStateToSavedDashboardPanel, + convertSavedDashboardPanelToPanelState, + convertSavedPanelsToPanelMap, + convertPanelMapToSavedPanels, +} from './lib/dashboard_panel_converters'; export const UI_SETTINGS = { ENABLE_LABS_UI: 'labs:dashboard:enable_ui', diff --git a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.test.ts b/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts similarity index 98% rename from src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.test.ts rename to src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts index 9ec93fa85fc54..2ebca116f3f12 100644 --- a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.test.ts +++ b/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts @@ -9,7 +9,7 @@ import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, -} from './embeddable_saved_object_converters'; +} from './dashboard_panel_converters'; import { SavedDashboardPanel, DashboardPanelState } from '../types'; import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types'; diff --git a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts similarity index 75% rename from src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts rename to src/plugins/dashboard/common/lib/dashboard_panel_converters.ts index aa9519a5a48b9..2652c7f9a40a7 100644 --- a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts +++ b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts @@ -8,7 +8,7 @@ import { omit } from 'lodash'; import { EmbeddableInput, SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; -import { DashboardPanelState, SavedDashboardPanel } from '../types'; +import { DashboardPanelMap, DashboardPanelState, SavedDashboardPanel } from '../types'; export function convertSavedDashboardPanelToPanelState< TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput @@ -42,3 +42,17 @@ export function convertPanelStateToSavedDashboardPanel( ...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }), }; } + +export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): DashboardPanelMap => { + const panelsMap: DashboardPanelMap = {}; + panels?.forEach((panel, idx) => { + panelsMap![panel.panelIndex ?? String(idx)] = convertSavedDashboardPanelToPanelState(panel); + }); + return panelsMap; +}; + +export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap, version: string) => { + return Object.values(panels).map((panel) => + convertPanelStateToSavedDashboardPanel(panel, version) + ); +}; diff --git a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.test.ts b/src/plugins/dashboard/common/persistable_state/dashboard_container_references.test.ts similarity index 99% rename from src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.test.ts rename to src/plugins/dashboard/common/persistable_state/dashboard_container_references.test.ts index ee13926486f8b..47215e5e32008 100644 --- a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.test.ts +++ b/src/plugins/dashboard/common/persistable_state/dashboard_container_references.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createExtract, createInject } from './dashboard_container_persistable_state'; +import { createExtract, createInject } from './dashboard_container_references'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; import { DashboardContainerStateWithType } from '../types'; diff --git a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts b/src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts similarity index 100% rename from src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts rename to src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts diff --git a/src/plugins/dashboard/common/saved_dashboard_references.test.ts b/src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.test.ts similarity index 98% rename from src/plugins/dashboard/common/saved_dashboard_references.test.ts rename to src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.test.ts index 84bfe6ea48d3a..e28a429d6d004 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.test.ts +++ b/src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.test.ts @@ -11,9 +11,9 @@ import { injectReferences, InjectDeps, ExtractDeps, -} from './saved_dashboard_references'; +} from './dashboard_saved_object_references'; -import { createExtract, createInject } from './embeddable/dashboard_container_persistable_state'; +import { createExtract, createInject } from './dashboard_container_references'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock(); diff --git a/src/plugins/dashboard/common/saved_dashboard_references.ts b/src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts similarity index 96% rename from src/plugins/dashboard/common/saved_dashboard_references.ts rename to src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts index e3a3193dd85a1..a3126a381d944 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.ts +++ b/src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts @@ -6,23 +6,33 @@ * Side Public License, v 1. */ import semverGt from 'semver/functions/gt'; -import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types'; -import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types'; + import { - PersistableControlGroupInput, RawControlGroupAttributes, + PersistableControlGroupInput, } from '@kbn/controls-plugin/common'; -import { DashboardContainerStateWithType, DashboardPanelState } from './types'; +import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types'; +import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types'; + +import { + SavedDashboardPanel, + DashboardPanelState, + DashboardContainerStateWithType, +} from '../types'; import { convertPanelStateToSavedDashboardPanel, convertSavedDashboardPanelToPanelState, -} from './embeddable/embeddable_saved_object_converters'; -import { SavedDashboardPanel } from './types'; +} from '../lib/dashboard_panel_converters'; export interface ExtractDeps { embeddablePersistableStateService: EmbeddablePersistableStateService; } -export interface SavedObjectAttributesAndReferences { + +export interface InjectDeps { + embeddablePersistableStateService: EmbeddablePersistableStateService; +} + +interface SavedObjectAttributesAndReferences { attributes: SavedObjectAttributes; references: SavedObjectReference[]; } @@ -79,7 +89,7 @@ function panelStatesToPanels( let originalPanel = originalPanels.find((p) => p.panelIndex === id); if (!originalPanel) { - // Maybe original panel doesn't have a panel index and it's just straight up based on it's index + // Maybe original panel doesn't have a panel index and it's just straight up based on its index const numericId = parseInt(id, 10); originalPanel = isNaN(numericId) ? originalPanel : originalPanels[numericId]; } @@ -91,6 +101,45 @@ function panelStatesToPanels( }); } +export function injectReferences( + { attributes, references = [] }: SavedObjectAttributesAndReferences, + deps: InjectDeps +): SavedObjectAttributes { + // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when + // importing objects without panelsJSON. At development time of this, there is no guarantee each saved + // object has panelsJSON in all previous versions of kibana. + if (typeof attributes.panelsJSON !== 'string') { + return attributes; + } + const parsedPanels = JSON.parse(attributes.panelsJSON); + // Same here, prevent failing saved object import if ever panels aren't an array. + if (!Array.isArray(parsedPanels)) { + return attributes; + } + + const { panels, state } = dashboardAttributesToState(attributes); + + const injectedState = deps.embeddablePersistableStateService.inject( + state, + references + ) as DashboardContainerStateWithType; + const injectedPanels = panelStatesToPanels(injectedState.panels, panels); + + const newAttributes = { + ...attributes, + panelsJSON: JSON.stringify(injectedPanels), + } as SavedObjectAttributes; + + if (injectedState.controlGroupInput) { + newAttributes.controlGroupInput = { + ...(attributes.controlGroupInput as SavedObjectAttributes), + panelsJSON: JSON.stringify(injectedState.controlGroupInput.panels), + }; + } + + return newAttributes; +} + export function extractReferences( { attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ExtractDeps @@ -137,49 +186,6 @@ export function extractReferences( }; } -export interface InjectDeps { - embeddablePersistableStateService: EmbeddablePersistableStateService; -} - -export function injectReferences( - { attributes, references = [] }: SavedObjectAttributesAndReferences, - deps: InjectDeps -): SavedObjectAttributes { - // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when - // importing objects without panelsJSON. At development time of this, there is no guarantee each saved - // object has panelsJSON in all previous versions of kibana. - if (typeof attributes.panelsJSON !== 'string') { - return attributes; - } - const parsedPanels = JSON.parse(attributes.panelsJSON); - // Same here, prevent failing saved object import if ever panels aren't an array. - if (!Array.isArray(parsedPanels)) { - return attributes; - } - - const { panels, state } = dashboardAttributesToState(attributes); - - const injectedState = deps.embeddablePersistableStateService.inject( - state, - references - ) as DashboardContainerStateWithType; - const injectedPanels = panelStatesToPanels(injectedState.panels, panels); - - const newAttributes = { - ...attributes, - panelsJSON: JSON.stringify(injectedPanels), - } as SavedObjectAttributes; - - if (injectedState.controlGroupInput) { - newAttributes.controlGroupInput = { - ...(attributes.controlGroupInput as SavedObjectAttributes), - panelsJSON: JSON.stringify(injectedState.controlGroupInput.panels), - }; - } - - return newAttributes; -} - function pre730ExtractReferences( { attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ExtractDeps diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts index 941f9437e54e6..ff5a1cbc17552 100644 --- a/src/plugins/dashboard/common/types.ts +++ b/src/plugins/dashboard/common/types.ts @@ -11,28 +11,13 @@ import { EmbeddableStateWithType, PanelState, } from '@kbn/embeddable-plugin/common/types'; -import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common/lib/saved_object_embeddable'; -import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; +import { Serializable } from '@kbn/utility-types'; import { - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, -} from './bwc/types'; - -import { GridData } from './embeddable/types'; - -export type PanelId = string; -export type SavedObjectId = string; - -export interface DashboardPanelState< - TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput -> extends PanelState { - readonly gridData: GridData; - panelRefName?: string; -} + PersistableControlGroupInput, + RawControlGroupAttributes, +} from '@kbn/controls-plugin/common'; +import { RefreshInterval } from '@kbn/data-plugin/common'; +import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common/lib/saved_object_embeddable'; export interface DashboardCapabilities { showWriteControls: boolean; @@ -43,62 +28,77 @@ export interface DashboardCapabilities { } /** - * This should always represent the latest dashboard panel shape, after all possible migrations. + * The attributes of the dashboard saved object. This interface should be the + * source of truth for the latest dashboard attributes shape after all migrations. */ -export type SavedDashboardPanel = SavedDashboardPanel730ToLatest; - -export type SavedDashboardPanel640To720 = Pick< - RawSavedDashboardPanel640To720, - Exclude -> & { - readonly id: string; - readonly type: string; -}; +export interface DashboardAttributes { + controlGroupInput?: RawControlGroupAttributes; + refreshInterval?: RefreshInterval; + timeRestore: boolean; + optionsJSON?: string; + useMargins?: boolean; + description: string; + panelsJSON: string; + timeFrom?: string; + version: number; + timeTo?: string; + title: string; + kibanaSavedObjectMeta: { + searchSourceJSON: string; + }; +} -export type SavedDashboardPanel630 = Pick< - RawSavedDashboardPanel630, - Exclude -> & { - readonly id: string; - readonly type: string; -}; +/** -------------------------------------------------------------------- + * Dashboard panel types + -----------------------------------------------------------------------*/ -export type SavedDashboardPanel620 = Pick< - RawSavedDashboardPanel620, - Exclude -> & { - readonly id: string; - readonly type: string; -}; +/** + * The dashboard panel format expected by the embeddable container. + */ +export interface DashboardPanelState< + TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput +> extends PanelState { + readonly gridData: GridData; + panelRefName?: string; +} -export type SavedDashboardPanel610 = Pick< - RawSavedDashboardPanel610, - Exclude -> & { - readonly id: string; - readonly type: string; -}; +/** + * A saved dashboard panel parsed directly from the Dashboard Attributes panels JSON + */ +export interface SavedDashboardPanel { + embeddableConfig: { [key: string]: Serializable }; // parsed into the panel's explicitInput + id?: string; // the saved object id for by reference panels + type: string; // the embeddable type + panelRefName?: string; + gridData: GridData; + panelIndex: string; + version: string; + title?: string; +} -export type SavedDashboardPanelTo60 = Pick< - RawSavedDashboardPanelTo60, - Exclude -> & { - readonly id: string; - readonly type: string; -}; +/** + * Grid type for React Grid Layout + */ +export interface GridData { + w: number; + h: number; + x: number; + y: number; + i: string; +} -// id becomes optional starting in 7.3.0 -export type SavedDashboardPanel730ToLatest = Pick< - RawSavedDashboardPanel730ToLatest, - Exclude -> & { - readonly id?: string; - readonly type: string; -}; +export interface DashboardPanelMap { + [key: string]: DashboardPanelState; +} -// Making this interface because so much of the Container type from embeddable is tied up in public -// Once that is all available from common, we should be able to move the dashboard_container type to our common as well +/** -------------------------------------------------------------------- + * Dashboard container types + -----------------------------------------------------------------------*/ +/** + * Types below this line are copied here because so many important types are tied up in public. These types should be + * moved from public into common. + */ export interface DashboardContainerStateWithType extends EmbeddableStateWithType { panels: { [panelId: string]: DashboardPanelState; diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx index ac467e35729f8..aa3419e37890c 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { AddToLibraryAction } from '.'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { getSampleDashboardInput } from '../test_helpers'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; @@ -27,6 +26,7 @@ import { CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; import { pluginServices } from '../../services/plugin_services'; +import { AddToLibraryAction } from './add_to_library_action'; const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); pluginServices.getServices().embeddable.getEmbeddableFactory = jest diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx index 8c6577012161d..0510d35519ff2 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx @@ -18,8 +18,9 @@ import { import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { dashboardAddToLibraryAction } from '../../dashboard_strings'; -import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; +import { type DashboardPanelState, type DashboardContainer } from '..'; import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; export const ACTION_ADD_TO_LIBRARY = 'saveToLibrary'; diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index 5bb331e58fe38..0fb63049ebe32 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -12,7 +12,7 @@ import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helper import { coreMock } from '@kbn/core/public/mocks'; import { CoreStart } from '@kbn/core/public'; -import { ClonePanelAction } from '.'; +import { ClonePanelAction } from './clone_panel_action'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { ContactCardEmbeddable, diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx index 02862e7c75e86..11a96733337f0 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx @@ -27,9 +27,10 @@ import { placePanelBeside, IPanelPlacementBesideArgs, } from '../embeddable/panel/dashboard_panel_placement'; -import { dashboardClonePanelAction } from '../../dashboard_strings'; -import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; import { pluginServices } from '../../services/plugin_services'; +import { dashboardClonePanelAction } from '../../dashboard_strings'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; +import { type DashboardPanelState, type DashboardContainer } from '..'; export const ACTION_CLONE_PANEL = 'clonePanel'; diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx index 8f602db5e4529..cdd8d726e9fa6 100644 --- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx +++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx @@ -14,9 +14,10 @@ import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { dashboardCopyToDashboardAction } from '../../dashboard_strings'; -import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; +import { DashboardContainer } from '../embeddable'; import { CopyToDashboardModal } from './copy_to_dashboard_modal'; import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; export const ACTION_COPY_TO_DASHBOARD = 'copyToDashboard'; diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx index 7f9a99ed27231..af91631d20b39 100644 --- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx +++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx @@ -25,8 +25,8 @@ import { import { IEmbeddable, PanelNotFoundError } from '@kbn/embeddable-plugin/public'; import { LazyDashboardPicker, withSuspense } from '@kbn/presentation-util-plugin/public'; import { dashboardCopyToDashboardAction } from '../../dashboard_strings'; -import { createDashboardEditUrl, DashboardConstants, DashboardContainer } from '../..'; -import { DashboardPanelState } from '..'; +import { createDashboardEditUrl, DashboardConstants } from '../..'; +import { type DashboardContainer, DashboardPanelState } from '..'; import { pluginServices } from '../../services/plugin_services'; interface CopyToDashboardModalProps { diff --git a/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx index c5da566b90a6a..79ab109ddce51 100644 --- a/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx @@ -9,9 +9,9 @@ import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; -import type { DashboardContainerInput } from '../..'; +import { DashboardContainerInput, DASHBOARD_CONTAINER_TYPE } from '../..'; import { dashboardExpandPanelAction } from '../../dashboard_strings'; -import { DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '../embeddable'; +import { type DashboardContainer } from '../embeddable'; export const ACTION_EXPAND_PANEL = 'togglePanel'; diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx index 275a5625e5e0b..3b3fb5dde0497 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx @@ -6,27 +6,26 @@ * Side Public License, v 1. */ -import { getSampleDashboardInput } from '../test_helpers'; -import { DashboardContainer } from '../embeddable/dashboard_container'; - -import { FiltersNotificationBadge } from '.'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { type Query, type AggregateQuery, Filter } from '@kbn/es-query'; import { - ErrorEmbeddable, - FilterableEmbeddable, IContainer, + ErrorEmbeddable, isErrorEmbeddable, + FilterableEmbeddable, } from '@kbn/embeddable-plugin/public'; - import { ContactCardEmbeddable, - ContactCardEmbeddableFactory, + CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, - CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, } from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { type Query, type AggregateQuery, Filter } from '@kbn/es-query'; +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; + +import { getSampleDashboardInput } from '../test_helpers'; import { pluginServices } from '../../services/plugin_services'; +import { DashboardContainer } from '../embeddable/dashboard_container'; +import { FiltersNotificationBadge } from './filters_notification_badge'; const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); pluginServices.getServices().embeddable.getEmbeddableFactory = jest diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index 5c95e3c42cccd..a238ce05e1017 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -6,23 +6,76 @@ * Side Public License, v 1. */ -export type { ExpandPanelActionContext } from './expand_panel_action'; -export { ExpandPanelAction, ACTION_EXPAND_PANEL } from './expand_panel_action'; -export type { ReplacePanelActionContext } from './replace_panel_action'; -export { ReplacePanelAction, ACTION_REPLACE_PANEL } from './replace_panel_action'; -export type { ClonePanelActionContext } from './clone_panel_action'; -export { ClonePanelAction, ACTION_CLONE_PANEL } from './clone_panel_action'; -export type { AddToLibraryActionContext } from './add_to_library_action'; -export { AddToLibraryAction, ACTION_ADD_TO_LIBRARY } from './add_to_library_action'; -export type { UnlinkFromLibraryActionContext } from './unlink_from_library_action'; -export { UnlinkFromLibraryAction, ACTION_UNLINK_FROM_LIBRARY } from './unlink_from_library_action'; -export type { CopyToDashboardActionContext } from './copy_to_dashboard_action'; -export { CopyToDashboardAction, ACTION_COPY_TO_DASHBOARD } from './copy_to_dashboard_action'; -export type { LibraryNotificationActionContext } from './library_notification_action'; -export { - LibraryNotificationAction, - ACTION_LIBRARY_NOTIFICATION, -} from './library_notification_action'; -export { FiltersNotificationBadge, BADGE_FILTERS_NOTIFICATION } from './filters_notification_badge'; -export type { ExportContext } from './export_csv_action'; -export { ExportCSVAction, ACTION_EXPORT_CSV } from './export_csv_action'; +import { + CONTEXT_MENU_TRIGGER, + PANEL_BADGE_TRIGGER, + PANEL_NOTIFICATION_TRIGGER, +} from '@kbn/embeddable-plugin/public'; +import { CoreStart } from '@kbn/core/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; + +import { ExportCSVAction } from './export_csv_action'; +import { ClonePanelAction } from './clone_panel_action'; +import { DashboardStartDependencies } from '../../plugin'; +import { ExpandPanelAction } from './expand_panel_action'; +import { ReplacePanelAction } from './replace_panel_action'; +import { AddToLibraryAction } from './add_to_library_action'; +import { CopyToDashboardAction } from './copy_to_dashboard_action'; +import { UnlinkFromLibraryAction } from './unlink_from_library_action'; +import { FiltersNotificationBadge } from './filters_notification_badge'; +import { LibraryNotificationAction } from './library_notification_action'; + +interface BuildAllDashboardActionsProps { + core: CoreStart; + allowByValueEmbeddables?: boolean; + plugins: DashboardStartDependencies; +} + +export const buildAllDashboardActions = async ({ + core, + plugins, + allowByValueEmbeddables, +}: BuildAllDashboardActionsProps) => { + const { uiSettings } = core; + const { uiActions, share, presentationUtil } = plugins; + + const clonePanelAction = new ClonePanelAction(core.savedObjects); + uiActions.registerAction(clonePanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); + + const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, uiSettings); + const changeViewAction = new ReplacePanelAction(SavedObjectFinder); + uiActions.registerAction(changeViewAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); + + const panelLevelFiltersNotification = new FiltersNotificationBadge(); + uiActions.registerAction(panelLevelFiltersNotification); + uiActions.attachAction(PANEL_BADGE_TRIGGER, panelLevelFiltersNotification.id); + + const expandPanelAction = new ExpandPanelAction(); + uiActions.registerAction(expandPanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); + + if (share) { + const ExportCSVPlugin = new ExportCSVAction(); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, ExportCSVPlugin); + } + + if (allowByValueEmbeddables) { + const addToLibraryAction = new AddToLibraryAction(); + uiActions.registerAction(addToLibraryAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id); + + const unlinkFromLibraryAction = new UnlinkFromLibraryAction(); + uiActions.registerAction(unlinkFromLibraryAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); + + const libraryNotificationAction = new LibraryNotificationAction(unlinkFromLibraryAction); + uiActions.registerAction(libraryNotificationAction); + uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id); + + const copyToDashboardAction = new CopyToDashboardAction(presentationUtil.ContextProvider); + uiActions.registerAction(copyToDashboardAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id); + } +}; diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx index f1202de4ac1b6..f30cba538b8d1 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx @@ -6,11 +6,6 @@ * Side Public License, v 1. */ -import { getSampleDashboardInput } from '../test_helpers'; -import { DashboardContainer } from '../embeddable/dashboard_container'; - -import { LibraryNotificationAction, UnlinkFromLibraryAction } from '.'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { ErrorEmbeddable, IContainer, @@ -25,7 +20,13 @@ import { ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; + +import { getSampleDashboardInput } from '../test_helpers'; import { pluginServices } from '../../services/plugin_services'; +import { DashboardContainer } from '../embeddable/dashboard_container'; +import { UnlinkFromLibraryAction } from './unlink_from_library_action'; +import { LibraryNotificationAction } from './library_notification_action'; const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); pluginServices.getServices().embeddable.getEmbeddableFactory = jest diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx index a5abe8161e9ad..a05b78994b31d 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx @@ -17,10 +17,10 @@ import { import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; -import { UnlinkFromLibraryAction } from '.'; -import { LibraryNotificationPopover } from './library_notification_popover'; -import { dashboardLibraryNotification } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; +import { UnlinkFromLibraryAction } from './unlink_from_library_action'; +import { dashboardLibraryNotification } from '../../dashboard_strings'; +import { LibraryNotificationPopover } from './library_notification_popover'; export const ACTION_LIBRARY_NOTIFICATION = 'ACTION_LIBRARY_NOTIFICATION'; diff --git a/src/plugins/dashboard/public/application/actions/library_notification_popover.tsx b/src/plugins/dashboard/public/application/actions/library_notification_popover.tsx index 81d2f2a0557ec..38c8452eadde5 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_popover.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_popover.tsx @@ -17,8 +17,10 @@ import { EuiPopoverTitle, EuiText, } from '@elastic/eui'; -import { LibraryNotificationActionContext, UnlinkFromLibraryAction } from '.'; + import { dashboardLibraryNotification } from '../../dashboard_strings'; +import { UnlinkFromLibraryAction } from './unlink_from_library_action'; +import { LibraryNotificationActionContext } from './library_notification_action'; export interface LibraryNotificationProps { context: LibraryNotificationActionContext; diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx index f39988842e3fc..52f6a345a181e 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx @@ -8,9 +8,10 @@ import { type IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; -import { DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '../embeddable'; +import type { DashboardContainer } from '../embeddable'; import { openReplacePanelFlyout } from './open_replace_panel_flyout'; import { dashboardReplacePanelAction } from '../../dashboard_strings'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; export const ACTION_REPLACE_PANEL = 'replacePanel'; diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx index 854383edd4e14..080c358a86fdf 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx @@ -23,10 +23,10 @@ import { CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; -import { UnlinkFromLibraryAction } from '.'; import { getSampleDashboardInput } from '../test_helpers'; -import { DashboardContainer } from '../embeddable/dashboard_container'; import { pluginServices } from '../../services/plugin_services'; +import { UnlinkFromLibraryAction } from './unlink_from_library_action'; +import { DashboardContainer } from '../embeddable/dashboard_container'; let container: DashboardContainer; let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx index e399411e77fee..b7c53a78becc2 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx @@ -17,8 +17,9 @@ import { } from '@kbn/embeddable-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { dashboardUnlinkFromLibraryAction } from '../../dashboard_strings'; -import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; +import { type DashboardPanelState, type DashboardContainer } from '..'; import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary'; diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 302cb43794229..b05944c99292b 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -9,24 +9,23 @@ import { History } from 'history'; import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; -import { useDashboardSelector } from './state'; -import { useDashboardAppState } from './hooks'; import { dashboardFeatureCatalog, getDashboardBreadcrumb, getDashboardTitle, leaveConfirmStrings, } from '../dashboard_strings'; -import { createDashboardEditUrl } from '../dashboard_constants'; -import { DashboardTopNav, isCompleteDashboardAppState } from './top_nav/dashboard_top_nav'; -import { DashboardEmbedSettings, DashboardRedirect } from '../types'; -import { DashboardAppNoDataPage } from './dashboard_app_no_data'; +import { useDashboardAppState } from './hooks'; +import { useDashboardSelector } from './state'; import { pluginServices } from '../services/plugin_services'; +import { DashboardAppNoDataPage } from './dashboard_app_no_data'; +import { DashboardEmbedSettings, DashboardRedirect } from '../types'; import { useDashboardMountContext } from './hooks/dashboard_mount_context'; +import { DashboardTopNav, isCompleteDashboardAppState } from './top_nav/dashboard_top_nav'; export interface DashboardAppProps { history: History; savedDashboardId?: string; @@ -43,13 +42,12 @@ export function DashboardApp({ const { onAppLeave } = useDashboardMountContext(); const { chrome: { setBreadcrumbs, setIsVisible }, + screenshotMode: { isScreenshotMode }, coreContext: { executionContext }, - data: { search }, embeddable: { getStateTransfer }, notifications: { toasts }, - screenshotMode: { isScreenshotMode }, settings: { uiSettings }, - spaces: { getLegacyUrlConflict }, + data: { search }, } = pluginServices.getServices(); const [showNoDataPage, setShowNoDataPage] = useState(false); @@ -160,17 +158,7 @@ export function DashboardApp({ dashboardAppState={dashboardAppState} /> - {dashboardAppState.savedDashboard.outcome === 'conflict' && - dashboardAppState.savedDashboard.id && - dashboardAppState.savedDashboard.aliasId - ? getLegacyUrlConflict?.({ - currentObjectId: dashboardAppState.savedDashboard.id, - otherObjectId: dashboardAppState.savedDashboard.aliasId, - otherObjectPath: `#${createDashboardEditUrl( - dashboardAppState.savedDashboard.aliasId - )}${history.location.search}`, - }) - : null} + {dashboardAppState.createConflictWarning?.()}
    createKbnUrlStateStorage({ history, @@ -172,51 +162,48 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da }); const app = ( - // TODO: Remove KibanaContextProvider as part of https://github.com/elastic/kibana/pull/138774 - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts b/src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts deleted file mode 100644 index 2f7854e81ad95..0000000000000 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const DASHBOARD_GRID_COLUMN_COUNT = 48; -export const DASHBOARD_GRID_HEIGHT = 20; -export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; -export const DEFAULT_PANEL_HEIGHT = 15; -export const DASHBOARD_CONTAINER_TYPE = 'dashboard'; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 4f7483cf06f35..036c77fc6257c 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -32,7 +32,7 @@ import type { Query } from '@kbn/es-query'; import type { RefreshInterval } from '@kbn/data-plugin/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; import { createPanelState } from './panel'; import { DashboardPanelState } from './types'; import { DashboardViewport } from './viewport/dashboard_viewport'; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 27670ee104367..58a2c63492c09 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -7,16 +7,14 @@ */ import { i18n } from '@kbn/i18n'; -import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; - import { identity, pickBy } from 'lodash'; + import { ControlGroupContainer, ControlGroupInput, ControlGroupOutput, CONTROL_GROUP_TYPE, } from '@kbn/controls-plugin/public'; -import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common'; import { Container, ErrorEmbeddable, @@ -25,14 +23,13 @@ import { EmbeddableFactoryDefinition, } from '@kbn/embeddable-plugin/public'; +import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common'; +import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; + import { DashboardContainerInput } from '../..'; -import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; +import { createExtract, createInject } from '../../../common'; import type { DashboardContainer } from './dashboard_container'; -import { - createExtract, - createInject, -} from '../../../common/embeddable/dashboard_container_persistable_state'; -import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -80,6 +77,7 @@ export class DashboardContainerFactoryDefinition initialInput: DashboardContainerInput, parent?: Container ): Promise => { + const { pluginServices } = await import('../../services/plugin_services'); const { embeddable: { getEmbeddableFactory }, } = pluginServices.getServices(); diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index 64afdcdb2e609..7fda6eb1a3f35 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -22,9 +22,9 @@ import { DashboardContainer, DashboardLoadedInfo } from '../dashboard_container' import { GridData } from '../../../../common'; import { DashboardGridItem } from './dashboard_grid_item'; import { DashboardLoadedEventStatus, DashboardPanelState } from '../types'; -import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; +import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../../../dashboard_constants'; import { pluginServices } from '../../../services/plugin_services'; -import { dashboardLoadingErrorStrings } from '../../../dashboard_strings'; +import { dashboardSavedObjectErrorStrings } from '../../../dashboard_strings'; let lastValidGridSize = 0; @@ -153,7 +153,7 @@ class DashboardGridUi extends React.Component { } catch (error) { console.error(error); // eslint-disable-line no-console isLayoutInvalid = true; - toasts.addDanger(dashboardLoadingErrorStrings.getDashboardGridError(error.message)); + toasts.addDanger(dashboardSavedObjectErrorStrings.getDashboardGridError(error.message)); } this.setState({ layout, diff --git a/src/plugins/dashboard/public/application/embeddable/index.ts b/src/plugins/dashboard/public/application/embeddable/index.ts index ce8bb5b7169ac..1979ae5ad7bf6 100644 --- a/src/plugins/dashboard/public/application/embeddable/index.ts +++ b/src/plugins/dashboard/public/application/embeddable/index.ts @@ -13,11 +13,4 @@ export { createPanelState } from './panel'; export * from './types'; -export { - DASHBOARD_GRID_COLUMN_COUNT, - DEFAULT_PANEL_HEIGHT, - DEFAULT_PANEL_WIDTH, - DASHBOARD_CONTAINER_TYPE, -} from './dashboard_constants'; - export { createDashboardContainerByValueRenderer } from './dashboard_container_by_value_renderer'; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts index 10c3044ea912a..4c926675e1e94 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts @@ -8,7 +8,7 @@ import { EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_samples'; -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; +import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; import { DashboardPanelState } from '../types'; import { createPanelState } from './create_panel_state'; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts index 5aa9066ea1eba..e5d4f69c914ce 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts @@ -7,7 +7,7 @@ */ import { PanelState, EmbeddableInput } from '@kbn/embeddable-plugin/public'; -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; +import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; import { DashboardPanelState } from '../types'; import { IPanelPlacementArgs, diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index 9d90b711a6843..77b51874319ba 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -8,8 +8,8 @@ import _ from 'lodash'; import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; -import { GridData } from '../../../../common'; -import { DashboardPanelState, DASHBOARD_GRID_COLUMN_COUNT } from '..'; +import { DashboardPanelState, GridData } from '../../../../common'; +import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; export type PanelPlacementMethod = ( args: PlacementArgs diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/index.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/index.ts index af4327f0fcd98..1d1aba84e7c3a 100644 --- a/src/plugins/dashboard/public/application/embeddable/placeholder/index.ts +++ b/src/plugins/dashboard/public/application/embeddable/placeholder/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export * from './placeholder_embeddable'; -export * from './placeholder_embeddable_factory'; +export { PlaceholderEmbeddableFactory } from './placeholder_embeddable_factory'; + +export const PLACEHOLDER_EMBEDDABLE = 'placeholder'; diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx index a7fa1e793ebf0..de468d86c89fe 100644 --- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx +++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx @@ -13,9 +13,9 @@ import classNames from 'classnames'; import { EuiLoadingChart } from '@elastic/eui'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { Embeddable, type EmbeddableInput, type IContainer } from '@kbn/embeddable-plugin/public'; -import { pluginServices } from '../../../services/plugin_services'; -export const PLACEHOLDER_EMBEDDABLE = 'placeholder'; +import { PLACEHOLDER_EMBEDDABLE } from '.'; +import { pluginServices } from '../../../services/plugin_services'; export class PlaceholderEmbeddable extends Embeddable { public readonly type = PLACEHOLDER_EMBEDDABLE; diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts index 74ce8bf96edbd..26cdddbf17d85 100644 --- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts +++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts @@ -13,7 +13,7 @@ import { EmbeddableInput, IContainer, } from '@kbn/embeddable-plugin/public'; -import { PlaceholderEmbeddable, PLACEHOLDER_EMBEDDABLE } from './placeholder_embeddable'; +import { PLACEHOLDER_EMBEDDABLE } from '.'; export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition { public readonly type = PLACEHOLDER_EMBEDDABLE; @@ -29,6 +29,7 @@ export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition } public async create(initialInput: EmbeddableInput, parent?: IContainer) { + const { PlaceholderEmbeddable } = await import('./placeholder_embeddable'); return new PlaceholderEmbeddable(initialInput, parent); } diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx index c462df50ef27f..76a3ae7a053a4 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx @@ -9,27 +9,20 @@ import React from 'react'; import { Provider } from 'react-redux'; import { createBrowserHistory } from 'history'; + +import type { Filter } from '@kbn/es-query'; +import { DataView } from '@kbn/data-views-plugin/public'; +import { EmbeddableFactory, ViewMode } from '@kbn/embeddable-plugin/public'; import { renderHook, act, RenderHookResult } from '@testing-library/react-hooks'; import { createKbnUrlStateStorage, defer } from '@kbn/kibana-utils-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; +import { DashboardAppState } from '../../types'; +import { getSampleDashboardInput } from '../test_helpers'; import { DashboardConstants } from '../../dashboard_constants'; -import { SavedObjectLoader } from '../../services/saved_object_loader'; -import { DashboardAppServices, DashboardAppState } from '../../types'; +import { pluginServices } from '../../services/plugin_services'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dashboardStateStore, setDescription, setViewMode } from '../state'; import { useDashboardAppState, UseDashboardStateProps } from './use_dashboard_app_state'; -import { - getSampleDashboardInput, - getSavedDashboardMock, - makeDefaultServices, -} from '../test_helpers'; - -import type { Filter } from '@kbn/es-query'; -import { pluginServices } from '../../services/plugin_services'; -import { EmbeddableFactory, ViewMode } from '@kbn/embeddable-plugin/public'; -import { DashboardServices } from '../../services/types'; interface SetupEmbeddableFactoryReturn { finalizeEmbeddableCreation: () => void; @@ -40,7 +33,6 @@ interface SetupEmbeddableFactoryReturn { interface RenderDashboardStateHookReturn { embeddableFactoryResult: SetupEmbeddableFactoryReturn; renderHookResult: RenderHookResult, DashboardAppState>; - services: DashboardAppServices; props: UseDashboardStateProps; } @@ -55,28 +47,7 @@ const createDashboardAppStateProps = (): UseDashboardStateProps => ({ setShowNoDataPage: () => {}, }); -const createDashboardAppStateServices = () => { - const defaults = makeDefaultServices(); - - const defaultDataView = { id: 'foo', fields: [{ name: 'bar' }] } as DataView; - - (pluginServices.getServices().data.dataViews.getDefaultDataView as jest.Mock).mockResolvedValue( - defaultDataView - ); - (pluginServices.getServices().data.dataViews.getDefaultId as jest.Mock).mockResolvedValue( - defaultDataView.id - ); - (pluginServices.getServices().data.query.filterManager.getFilters as jest.Mock).mockReturnValue( - [] - ); - - return defaults; -}; - -const setupEmbeddableFactory = ( - services: DashboardAppServices, - id: string -): SetupEmbeddableFactoryReturn => { +const setupEmbeddableFactory = (id: string): SetupEmbeddableFactoryReturn => { const dashboardContainer = new DashboardContainer({ ...getSampleDashboardInput(), id }); const deferEmbeddableCreate = defer(); pluginServices.getServices().embeddable.getEmbeddableFactory = jest.fn().mockImplementation( @@ -100,15 +71,22 @@ const setupEmbeddableFactory = ( const renderDashboardAppStateHook = ({ partialProps, - partialServices, }: { partialProps?: Partial; - partialServices?: Partial; }): RenderDashboardStateHookReturn => { + const defaultDataView = { id: 'foo', fields: [{ name: 'bar' }] } as DataView; + (pluginServices.getServices().data.dataViews.getDefaultDataView as jest.Mock).mockResolvedValue( + defaultDataView + ); + (pluginServices.getServices().data.dataViews.getDefaultId as jest.Mock).mockResolvedValue( + defaultDataView.id + ); + (pluginServices.getServices().data.query.filterManager.getFilters as jest.Mock).mockReturnValue( + [] + ); + const props = { ...createDashboardAppStateProps(), ...(partialProps ?? {}) }; - const services = { ...createDashboardAppStateServices(), ...(partialServices ?? {}) }; - const embeddableFactoryResult = setupEmbeddableFactory(services, originalDashboardEmbeddableId); - const DashboardServicesProvider = pluginServices.getContextProvider(); + const embeddableFactoryResult = setupEmbeddableFactory(originalDashboardEmbeddableId); const renderHookResult = renderHook( (replaceProps: Partial) => { @@ -116,18 +94,11 @@ const renderDashboardAppStateHook = ({ }, { wrapper: ({ children }) => { - return ( - - {/* Can't get rid of KibanaContextProvider here yet because of saved dashboard tests below */} - - {children} - - - ); + return {children}; }, } ); - return { embeddableFactoryResult, renderHookResult, services, props }; + return { embeddableFactoryResult, renderHookResult, props }; }; describe('Dashboard container lifecycle', () => { @@ -146,7 +117,7 @@ describe('Dashboard container lifecycle', () => { }); test('Old dashboard container is destroyed when new dashboardId is given', async () => { - const { renderHookResult, embeddableFactoryResult, services } = renderDashboardAppStateHook({}); + const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({}); const getResult = () => renderHookResult.result.current; // on initial render dashboard container is undefined @@ -158,7 +129,7 @@ describe('Dashboard container lifecycle', () => { expect(embeddableFactoryResult.dashboardDestroySpy).not.toBeCalled(); const newDashboardId = 'wow_a_new_dashboard_id'; - const embeddableFactoryNew = setupEmbeddableFactory(services, newDashboardId); + const embeddableFactoryNew = setupEmbeddableFactory(newDashboardId); renderHookResult.rerender({ savedDashboardId: newDashboardId }); embeddableFactoryNew.finalizeEmbeddableCreation(); @@ -170,7 +141,7 @@ describe('Dashboard container lifecycle', () => { }); test('Dashboard container is destroyed if dashboard id is changed before container is resolved', async () => { - const { renderHookResult, embeddableFactoryResult, services } = renderDashboardAppStateHook({}); + const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({}); const getResult = () => renderHookResult.result.current; // on initial render dashboard container is undefined @@ -178,7 +149,7 @@ describe('Dashboard container lifecycle', () => { await act(() => Promise.resolve()); // wait for the original savedDashboard to be loaded... const newDashboardId = 'wow_a_new_dashboard_id'; - const embeddableFactoryNew = setupEmbeddableFactory(services, newDashboardId); + const embeddableFactoryNew = setupEmbeddableFactory(newDashboardId); renderHookResult.rerender({ savedDashboardId: newDashboardId }); await act(() => Promise.resolve()); // wait for the new savedDashboard to be loaded... @@ -199,38 +170,33 @@ describe('Dashboard container lifecycle', () => { // FLAKY: https://github.com/elastic/kibana/issues/105018 describe.skip('Dashboard initial state', () => { it('Extracts state from Dashboard Saved Object', async () => { + const savedTitle = 'testDash1'; + ( + pluginServices.getServices().dashboardSavedObject + .loadDashboardStateFromSavedObject as jest.Mock + ).mockResolvedValue({ title: savedTitle }); + const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({}); const getResult = () => renderHookResult.result.current; - // saved dashboard isn't applied until after the dashboard embeddable has been created. - expect(getResult().savedDashboard).toBeUndefined(); - embeddableFactoryResult.finalizeEmbeddableCreation(); await renderHookResult.waitForNextUpdate(); - expect(getResult().savedDashboard).toBeDefined(); - expect(getResult().savedDashboard?.title).toEqual( - getResult().getLatestDashboardState?.().title - ); + expect(savedTitle).toEqual(getResult().getLatestDashboardState?.().title); }); it('Sets initial time range and filters from saved dashboard', async () => { - const savedDashboards = {} as SavedObjectLoader; - savedDashboards.get = jest.fn().mockImplementation((id?: string) => - Promise.resolve( - getSavedDashboardMock({ - getFilters: () => [{ meta: { test: 'filterMeTimbers' } } as unknown as Filter], - timeRestore: true, - timeFrom: 'now-13d', - timeTo: 'now', - id, - }) - ) - ); - const partialServices: Partial = { savedDashboards }; - const { renderHookResult, embeddableFactoryResult, services } = renderDashboardAppStateHook({ - partialServices, + ( + pluginServices.getServices().dashboardSavedObject + .loadDashboardStateFromSavedObject as jest.Mock + ).mockResolvedValue({ + filters: [{ meta: { test: 'filterMeTimbers' } } as unknown as Filter], + timeRestore: true, + timeFrom: 'now-13d', + timeTo: 'now', }); + + const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({}); const getResult = () => renderHookResult.result.current; embeddableFactoryResult.finalizeEmbeddableCreation(); @@ -238,15 +204,13 @@ describe.skip('Dashboard initial state', () => { expect(getResult().getLatestDashboardState?.().timeRestore).toEqual(true); expect( - (services as DashboardAppServices & { data: DashboardServices['data'] }).data.query.timefilter - .timefilter.setTime + pluginServices.getServices().data.query.timefilter.timefilter.setTime ).toHaveBeenCalledWith({ from: 'now-13d', to: 'now', }); expect( - (services as DashboardAppServices & { data: DashboardServices['data'] }).data.query - .filterManager.setAppFilters + pluginServices.getServices().data.query.filterManager.setAppFilters ).toHaveBeenCalledWith([{ meta: { test: 'filterMeTimbers' } } as unknown as Filter]); }); diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index 932bfdd016b38..850c6f575904c 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -6,50 +6,45 @@ * Side Public License, v 1. */ +import { omit } from 'lodash'; import { History } from 'history'; import { debounceTime, switchMap } from 'rxjs/operators'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import { DashboardConstants } from '../..'; -import { getNewDashboardTitle } from '../../dashboard_strings'; -import { setDashboardState, useDashboardDispatch, useDashboardSelector } from '../state'; -import type { - DashboardBuildContext, - DashboardAppServices, - DashboardAppState, - DashboardState, -} from '../../types'; -import { DashboardAppLocatorParams } from '../../locator'; import { - loadDashboardHistoryLocationState, - tryDestroyDashboardContainer, - syncDashboardContainerInput, - savedObjectToDashboardState, + diffDashboardState, + syncDashboardUrlState, syncDashboardDataViews, - syncDashboardFilterState, - loadSavedDashboardState, buildDashboardContainer, - syncDashboardUrlState, - diffDashboardState, - areTimeRangesEqual, - areRefreshIntervalsEqual, + syncDashboardFilterState, + syncDashboardContainerInput, + tryDestroyDashboardContainer, + loadDashboardHistoryLocationState, } from '../lib'; -import { isDashboardAppInNoDataState } from '../dashboard_app_no_data'; +import { + dashboardStateLoadWasSuccessful, + LoadDashboardFromSavedObjectReturn, +} from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object'; +import { DashboardConstants } from '../..'; +import { DashboardAppLocatorParams } from '../../locator'; +import { dashboardSavedObjectErrorStrings, getNewDashboardTitle } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; import { useDashboardMountContext } from './dashboard_mount_context'; +import { isDashboardAppInNoDataState } from '../dashboard_app_no_data'; +import { setDashboardState, useDashboardDispatch, useDashboardSelector } from '../state'; +import type { DashboardBuildContext, DashboardAppState, DashboardState } from '../../types'; export interface UseDashboardStateProps { history: History; showNoDataPage: boolean; savedDashboardId?: string; isEmbeddedExternally: boolean; - setShowNoDataPage: (showNoData: boolean) => void; kbnUrlStateStorage: IKbnUrlStateStorage; + setShowNoDataPage: (showNoData: boolean) => void; } export const useDashboardAppState = ({ @@ -79,25 +74,23 @@ export const useDashboardAppState = ({ const [lastSavedState, setLastSavedState] = useState(); const $onLastSavedStateChange = useMemo(() => new Subject(), []); - const { - services: { savedDashboards }, - } = useKibana(); - /** * Unpack services and context */ const { scopedHistory } = useDashboardMountContext(); const { + embeddable, + notifications: { toasts }, chrome: { docTitle }, dashboardCapabilities, dashboardSessionStorage, + spaces: { redirectLegacyUrl }, data: { query, search, dataViews }, - embeddable, initializerContext: { kibanaVersion }, screenshotMode: { isScreenshotMode, getScreenshotContext }, - spaces: { redirectLegacyUrl }, - notifications, + dashboardSavedObject: { loadDashboardStateFromSavedObject }, } = pluginServices.getServices(); + const { getStateTransfer } = embeddable; /** @@ -120,7 +113,6 @@ export const useDashboardAppState = ({ */ const dashboardBuildContext: DashboardBuildContext = { history, - savedDashboards, kbnUrlStateStorage, isEmbeddedExternally, dispatchDashboardStateChange, @@ -149,33 +141,25 @@ export const useDashboardAppState = ({ /** * Load and unpack state from dashboard saved object. */ - const loadSavedDashboardResult = await loadSavedDashboardState({ - ...dashboardBuildContext, - savedDashboardId, - }); - if (canceled || !loadSavedDashboardResult) return; - const { savedDashboard, savedDashboardState } = loadSavedDashboardResult; - - // If the saved dashboard is an alias match, then we will redirect - if (savedDashboard.outcome === 'aliasMatch' && savedDashboard.id && savedDashboard.aliasId) { - // We want to keep the "query" params on our redirect. - // But, these aren't true query params, they are technically part of the hash - // So, to get the new path, we will just replace the current id in the hash - // with the alias id - const path = scopedHistory().location.hash.replace( - savedDashboard.id, - savedDashboard.aliasId - ); - const aliasPurpose = savedDashboard.aliasPurpose; - if (isScreenshotMode()) { - scopedHistory().replace(path); - } else { - await redirectLegacyUrl?.({ path, aliasPurpose }); - } - // Return so we don't run any more of the hook and let it rerun after the redirect that just happened + let loadSavedDashboardResult: LoadDashboardFromSavedObjectReturn; + try { + loadSavedDashboardResult = await loadDashboardStateFromSavedObject({ + getScopedHistory: scopedHistory, + id: savedDashboardId, + }); + } catch (error) { + // redirect back to landing page if dashboard could not be loaded. + toasts.addDanger(dashboardSavedObjectErrorStrings.getDashboardLoadError(error.message)); + history.push(DashboardConstants.LANDING_PAGE_PATH); + return; + } + if (canceled || !dashboardStateLoadWasSuccessful(loadSavedDashboardResult)) { return; } + const { dashboardState: savedDashboardState, createConflictWarning } = + loadSavedDashboardResult; + /** * Combine initial state from the saved object, session storage, and URL, then dispatch it to Redux. */ @@ -187,12 +171,11 @@ export const useDashboardAppState = ({ const { initialDashboardStateFromUrl, stopWatchingAppStateInUrl } = syncDashboardUrlState({ ...dashboardBuildContext, - savedDashboard, }); const printLayoutDetected = isScreenshotMode() && getScreenshotContext('layout') === 'print'; - const initialDashboardState = { + const initialDashboardState: DashboardState = { ...savedDashboardState, ...dashboardSessionStorageState, ...initialDashboardStateFromUrl, @@ -208,10 +191,9 @@ export const useDashboardAppState = ({ /** * Start syncing dashboard state with the Query, Filters and Timepicker from the Query Service. */ - const { applyFilters, stopSyncingDashboardFilterState } = syncDashboardFilterState({ + const { stopSyncingDashboardFilterState } = syncDashboardFilterState({ ...dashboardBuildContext, initialDashboardState, - savedDashboard, }); /** @@ -222,10 +204,9 @@ export const useDashboardAppState = ({ ...dashboardBuildContext, initialDashboardState, incomingEmbeddable, - savedDashboard, executionContext: { type: 'dashboard', - description: savedDashboard.title, + description: initialDashboardState.title, }, }); @@ -256,15 +237,13 @@ export const useDashboardAppState = ({ const stopSyncingContainerInput = syncDashboardContainerInput({ ...dashboardBuildContext, dashboardContainer, - savedDashboard, - applyFilters, }); /** * Any time the redux state, or the last saved state changes, compare them, set the unsaved * changes state, and and push the unsaved changes to session storage. */ - const { timefilter } = query.timefilter; + const lastSavedSubscription = combineLatest([ $onLastSavedStateChange, dashboardAppState.$onDashboardStateChange, @@ -281,31 +260,24 @@ export const useDashboardAppState = ({ newState: current, }).then((unsavedChanges) => { if (observer.closed) return; - const savedTimeChanged = - lastSaved.timeRestore && - (!areTimeRangesEqual( - { - from: savedDashboard?.timeFrom, - to: savedDashboard?.timeTo, - }, - timefilter.getTime() - ) || - !areRefreshIntervalsEqual( - savedDashboard?.refreshInterval, - timefilter.getRefreshInterval() - )); - /** * changes to the dashboard should only be considered 'unsaved changes' when * editing the dashboard */ const hasUnsavedChanges = - current.viewMode === ViewMode.EDIT && - (Object.keys(unsavedChanges).length > 0 || savedTimeChanged); + current.viewMode === ViewMode.EDIT && Object.keys(unsavedChanges).length > 0; setDashboardAppState((s) => ({ ...s, hasUnsavedChanges })); unsavedChanges.viewMode = current.viewMode; // always push view mode into session store. - dashboardSessionStorage.setState(savedDashboardId, unsavedChanges); + + /** + * Current behaviour expects time range not to be backed up. + * TODO: Revisit this. It seems like we should treat all state the same. + */ + dashboardSessionStorage.setState( + savedDashboardId, + omit(unsavedChanges, ['timeRange', 'refreshInterval']) + ); }); }); }) @@ -319,11 +291,7 @@ export const useDashboardAppState = ({ setLastSavedState(savedDashboardState); dashboardBuildContext.$checkForUnsavedChanges.next(undefined); const updateLastSavedState = () => { - setLastSavedState( - savedObjectToDashboardState({ - savedDashboard, - }) - ); + setLastSavedState(dashboardBuildContext.getLatestDashboardState()); }; /** @@ -332,10 +300,9 @@ export const useDashboardAppState = ({ docTitle.change(savedDashboardState.title || getNewDashboardTitle()); setDashboardAppState((s) => ({ ...s, - applyFilters, - savedDashboard, dashboardContainer, updateLastSavedState, + createConflictWarning, getLatestDashboardState: dashboardBuildContext.getLatestDashboardState, })); @@ -359,47 +326,43 @@ export const useDashboardAppState = ({ }, [ dashboardAppState.$triggerDashboardRefresh, dashboardAppState.$onDashboardStateChange, + loadDashboardStateFromSavedObject, dispatchDashboardStateChange, $onLastSavedStateChange, dashboardSessionStorage, dashboardCapabilities, isEmbeddedExternally, + getScreenshotContext, kbnUrlStateStorage, + setShowNoDataPage, + redirectLegacyUrl, savedDashboardId, + isScreenshotMode, getStateTransfer, - savedDashboards, + showNoDataPage, scopedHistory, - notifications, - dataViews, kibanaVersion, + dataViews, embeddable, docTitle, history, + toasts, search, query, - showNoDataPage, - setShowNoDataPage, - redirectLegacyUrl, - getScreenshotContext, - isScreenshotMode, ]); /** * rebuild reset to last saved state callback whenever last saved state changes */ const resetToLastSavedState = useCallback(() => { - if ( - !lastSavedState || - !dashboardAppState.savedDashboard || - !dashboardAppState.getLatestDashboardState - ) { + if (!lastSavedState || !dashboardAppState.getLatestDashboardState) { return; } if (dashboardAppState.getLatestDashboardState().timeRestore) { const { timefilter } = query.timefilter; - const { timeFrom: from, timeTo: to, refreshInterval } = dashboardAppState.savedDashboard; - if (from && to) timefilter.setTime({ from, to }); + const { timeRange, refreshInterval } = lastSavedState; + if (timeRange) timefilter.setTime(timeRange); if (refreshInterval) timefilter.setRefreshInterval(refreshInterval); } dispatchDashboardStateChange( diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index 8ad2b7ddc52e2..2d5304e002d54 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -16,8 +16,7 @@ import { isErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; -import { DashboardSavedObject } from '../../saved_dashboards'; -import { DashboardContainer, DASHBOARD_CONTAINER_TYPE } from '../embeddable'; +import { DashboardContainer } from '../embeddable'; import { DashboardBuildContext, DashboardState, DashboardContainerInput } from '../../types'; import { enableDashboardSearchSessions, @@ -25,9 +24,9 @@ import { stateToDashboardContainerInput, } from '.'; import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_CONTAINER_TYPE } from '../../dashboard_constants'; type BuildDashboardContainerProps = DashboardBuildContext & { - savedDashboard: DashboardSavedObject; initialDashboardState: DashboardState; incomingEmbeddable?: EmbeddablePackageState; executionContext?: KibanaExecutionContext; @@ -41,7 +40,6 @@ export const buildDashboardContainer = async ({ initialDashboardState, isEmbeddedExternally, incomingEmbeddable, - savedDashboard, history, executionContext, }: BuildDashboardContainerProps) => { @@ -55,7 +53,6 @@ export const buildDashboardContainer = async ({ // set up search session enableDashboardSearchSessions({ - savedDashboard, initialDashboardState, getLatestDashboardState, canStoreSearchSession, @@ -95,7 +92,6 @@ export const buildDashboardContainer = async ({ dashboardState: initialDashboardState, incomingEmbeddable, searchSessionId, - savedDashboard, executionContext, }); diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts deleted file mode 100644 index 8e74245137f8e..0000000000000 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - convertSavedDashboardPanelToPanelState, - convertPanelStateToSavedDashboardPanel, -} from '../../../common/embeddable/embeddable_saved_object_converters'; -import { pluginServices } from '../../services/plugin_services'; -import type { SavedDashboardPanel, DashboardPanelMap } from '../../types'; - -export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): DashboardPanelMap => { - const panelsMap: DashboardPanelMap = {}; - panels?.forEach((panel, idx) => { - panelsMap![panel.panelIndex ?? String(idx)] = convertSavedDashboardPanelToPanelState(panel); - }); - return panelsMap; -}; - -export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap) => { - const { - initializerContext: { kibanaVersion }, - } = pluginServices.getServices(); - - return Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) - ); -}; diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts index c9e954a081ca2..14e0f4ac4c171 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts @@ -6,37 +6,21 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { cloneDeep, omit } from 'lodash'; import type { KibanaExecutionContext } from '@kbn/core/public'; -import type { ControlGroupInput } from '@kbn/controls-plugin/public'; -import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; -import { - compareFilters, - COMPARE_ALL_OPTIONS, - Filter, - isFilterPinned, - TimeRange, -} from '@kbn/es-query'; import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; +import { type EmbeddablePackageState } from '@kbn/embeddable-plugin/public'; +import { Filter, isFilterPinned, compareFilters, COMPARE_ALL_OPTIONS } from '@kbn/es-query'; -import type { DashboardSavedObject } from '../../saved_dashboards'; -import { getTagsFromSavedDashboard, migrateAppState } from '.'; -import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; -import type { DashboardState, RawDashboardState, DashboardContainerInput } from '../../types'; -import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; -import { deserializeControlGroupFromDashboardSavedObject } from './dashboard_control_group'; import { pluginServices } from '../../services/plugin_services'; - -interface SavedObjectToDashboardStateProps { - savedDashboard: DashboardSavedObject; -} +import { convertPanelStateToSavedDashboardPanel } from '../../../common'; +import type { DashboardState, RawDashboardState, DashboardContainerInput } from '../../types'; interface StateToDashboardContainerInputProps { searchSessionId?: string; isEmbeddedExternally?: boolean; dashboardState: DashboardState; - savedDashboard: DashboardSavedObject; incomingEmbeddable?: EmbeddablePackageState; executionContext?: KibanaExecutionContext; } @@ -44,40 +28,6 @@ interface StateToDashboardContainerInputProps { interface StateToRawDashboardStateProps { state: DashboardState; } -/** - * Converts a dashboard saved object to a dashboard state by extracting raw state from the given Dashboard - * Saved Object migrating the panel states to the latest version, then converting each panel from a saved - * dashboard panel to a panel state. - */ -export const savedObjectToDashboardState = ({ - savedDashboard, -}: SavedObjectToDashboardStateProps): DashboardState => { - const { - dashboardCapabilities: { showWriteControls }, - } = pluginServices.getServices(); - - const rawState = migrateAppState({ - fullScreenMode: false, - title: savedDashboard.title, - query: savedDashboard.getQuery(), - filters: savedDashboard.getFilters(), - timeRestore: savedDashboard.timeRestore, - description: savedDashboard.description || '', - tags: getTagsFromSavedDashboard(savedDashboard), - panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [], - viewMode: savedDashboard.id || showWriteControls ? ViewMode.EDIT : ViewMode.VIEW, - options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {}, - }); - - if (rawState.timeRestore) { - rawState.timeRange = { from: savedDashboard.timeFrom, to: savedDashboard.timeTo } as TimeRange; - } - - rawState.controlGroupInput = deserializeControlGroupFromDashboardSavedObject( - savedDashboard - ) as ControlGroupInput; - return { ...rawState, panels: convertSavedPanelsToPanelMap(rawState.panels) }; -}; /** * Converts a dashboard state object to dashboard container input @@ -85,7 +35,6 @@ export const savedObjectToDashboardState = ({ export const stateToDashboardContainerInput = ({ isEmbeddedExternally, searchSessionId, - savedDashboard, dashboardState, executionContext, }: StateToDashboardContainerInputProps): DashboardContainerInput => { @@ -111,7 +60,7 @@ export const stateToDashboardContainerInput = ({ filters: dashboardFilters, } = dashboardState; - const migratedDashboardFilters = mapAndFlattenFilters(_.cloneDeep(dashboardFilters)); + const migratedDashboardFilters = mapAndFlattenFilters(cloneDeep(dashboardFilters)); return { refreshConfig: timefilter.getRefreshInterval(), filters: filterManager @@ -124,7 +73,7 @@ export const stateToDashboardContainerInput = ({ ) ), isFullScreenMode: fullScreenMode, - id: savedDashboard.id || '', + id: dashboardState.savedObjectId ?? '', isEmbeddedExternally, ...(options || {}), controlGroupInput, @@ -136,7 +85,7 @@ export const stateToDashboardContainerInput = ({ query, title, timeRange: { - ..._.cloneDeep(timefilter.getTime()), + ...cloneDeep(timefilter.getTime()), }, timeslice, timeRestore, @@ -161,5 +110,5 @@ export const stateToRawDashboardState = ({ const savedDashboardPanels = Object.values(state.panels).map((panel) => convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) ); - return { ..._.omit(state, 'panels'), panels: savedDashboardPanels }; + return { ...omit(state, 'panels'), panels: savedDashboardPanels }; }; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts index a9f474ed85dd0..4f44d0cf250d1 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts @@ -14,16 +14,15 @@ import { debounceTime, distinctUntilChanged, distinctUntilKeyChanged } from 'rxj import { ControlGroupInput, - controlGroupInputToRawControlGroupAttributes, getDefaultControlGroupInput, persistableControlGroupInputIsEqual, - rawControlGroupAttributesToControlGroupInput, + controlGroupInputToRawControlGroupAttributes, } from '@kbn/controls-plugin/common'; import { ControlGroupContainer } from '@kbn/controls-plugin/public'; import { DashboardContainer } from '..'; import { DashboardState } from '../../types'; -import { DashboardContainerInput, DashboardSavedObject } from '../..'; +import { DashboardContainerInput } from '../..'; interface DiffChecks { [key: string]: (a?: unknown, b?: unknown) => boolean; @@ -169,32 +168,17 @@ export const syncDashboardControlGroup = async ({ }; }; -export const serializeControlGroupToDashboardSavedObject = ( - dashboardSavedObject: DashboardSavedObject, - dashboardState: DashboardState +export const serializeControlGroupInput = ( + controlGroupInput: DashboardState['controlGroupInput'] ) => { // only save to saved object if control group is not default if ( - persistableControlGroupInputIsEqual( - dashboardState.controlGroupInput, - getDefaultControlGroupInput() - ) + !controlGroupInput || + persistableControlGroupInputIsEqual(controlGroupInput, getDefaultControlGroupInput()) ) { - dashboardSavedObject.controlGroupInput = undefined; - return; + return undefined; } - if (dashboardState.controlGroupInput) { - dashboardSavedObject.controlGroupInput = controlGroupInputToRawControlGroupAttributes( - dashboardState.controlGroupInput - ); - } -}; - -export const deserializeControlGroupFromDashboardSavedObject = ( - dashboardSavedObject: DashboardSavedObject -): Omit | undefined => { - if (!dashboardSavedObject.controlGroupInput) return; - return rawControlGroupAttributesToControlGroupInput(dashboardSavedObject.controlGroupInput); + return controlGroupInputToRawControlGroupAttributes(controlGroupInput); }; export const combineDashboardFiltersWithControlGroupFilters = ( diff --git a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.test.ts similarity index 89% rename from src/plugins/dashboard/public/application/lib/session_restoration.test.ts rename to src/plugins/dashboard/public/application/lib/dashboard_session_restoration.test.ts index aeb83dd8a6e4c..56ee2ac55f445 100644 --- a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.test.ts @@ -6,16 +6,13 @@ * Side Public License, v 1. */ -import { getSavedDashboardMock } from '../test_helpers'; -import { createSessionRestorationDataProvider, savedObjectToDashboardState } from '.'; +import { DashboardState } from '../../types'; +import { createSessionRestorationDataProvider } from '.'; import { pluginServices } from '../../services/plugin_services'; describe('createSessionRestorationDataProvider', () => { const searchSessionInfoProvider = createSessionRestorationDataProvider({ - getAppState: () => - savedObjectToDashboardState({ - savedDashboard: getSavedDashboardMock(), - }), + getAppState: () => ({ panels: {} } as unknown as DashboardState), getDashboardTitle: () => 'Dashboard', getDashboardId: () => 'Id', }); diff --git a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts index b2cedeee4ee04..113c39d0717ba 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts @@ -17,12 +17,11 @@ import { import { getQueryParams } from '@kbn/kibana-utils-plugin/public'; import type { DashboardState } from '../../types'; -import type { DashboardSavedObject } from '../../saved_dashboards'; -import { DashboardAppLocatorParams, DashboardConstants } from '../..'; -import { getDashboardTitle } from '../../dashboard_strings'; -import { stateToRawDashboardState } from './convert_dashboard_state'; import { DASHBOARD_APP_LOCATOR } from '../../locator'; +import { getDashboardTitle } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; +import { DashboardAppLocatorParams, DashboardConstants } from '../..'; +import { stateToRawDashboardState } from './convert_dashboard_state'; export const getSearchSessionIdFromURL = (history: History): string | undefined => getQueryParams(history.location)[DashboardConstants.SEARCH_SESSION_ID] as string | undefined; @@ -52,10 +51,8 @@ export function enableDashboardSearchSessions({ canStoreSearchSession, initialDashboardState, getLatestDashboardState, - savedDashboard, }: { canStoreSearchSession: boolean; - savedDashboard: DashboardSavedObject; initialDashboardState: DashboardState; getLatestDashboardState: () => DashboardState; }) { @@ -63,13 +60,13 @@ export function enableDashboardSearchSessions({ const dashboardTitle = getDashboardTitle( initialDashboardState.title, initialDashboardState.viewMode, - !savedDashboard.id + !getLatestDashboardState().savedObjectId ); data.search.session.enableStorage( createSessionRestorationDataProvider({ getDashboardTitle: () => dashboardTitle, - getDashboardId: () => savedDashboard?.id || '', + getDashboardId: () => getLatestDashboardState().savedObjectId ?? '', getAppState: getLatestDashboardState, }), { @@ -106,7 +103,7 @@ function getLocatorParams({ return { timeRange: shouldRestoreSearchSession ? timefilter.getAbsoluteTime() : timefilter.getTime(), searchSessionId: shouldRestoreSearchSession ? data.search.session.getSessionId() : undefined, - panels: getDashboardId() ? undefined : appState.panels, + panels: getDashboardId() ? undefined : (appState.panels as DashboardAppLocatorParams['panels']), query: queryString.formatQuery(appState.query) as Query, filters: filterManager.getFilters(), savedQuery: appState.savedQuery, diff --git a/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts b/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts deleted file mode 100644 index 0a8ec17aeb2f1..0000000000000 --- a/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { TagDecoratedSavedObject } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import type { SavedObject } from '@kbn/saved-objects-plugin/public'; - -import { DashboardSavedObject } from '../..'; -import { pluginServices } from '../../services/plugin_services'; - -// TS is picky with type guards, we can't just inline `() => false` -function defaultTaggingGuard(_obj: SavedObject): _obj is TagDecoratedSavedObject { - return false; -} - -export const getTagsFromSavedDashboard = (savedDashboard: DashboardSavedObject) => { - const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(); - return hasTaggingCapabilities(savedDashboard) ? savedDashboard.getTags() : []; -}; - -export const getHasTaggingCapabilitiesGuard = () => { - const { - savedObjectsTagging: { hasTagDecoration }, - } = pluginServices.getServices(); - - return hasTagDecoration || defaultTaggingGuard; -}; diff --git a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts index ca913199a3ba2..1c57d1bd2afa9 100644 --- a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts @@ -6,14 +6,25 @@ * Side Public License, v 1. */ -import { xor, omit, isEmpty } from 'lodash'; import fastIsEqual from 'fast-deep-equal'; -import { compareFilters, COMPARE_ALL_OPTIONS, type Filter, isFilterPinned } from '@kbn/es-query'; +import { xor, omit, isEmpty, pick } from 'lodash'; + +import { + compareFilters, + COMPARE_ALL_OPTIONS, + type Filter, + isFilterPinned, + TimeRange, +} from '@kbn/es-query'; +import { RefreshInterval } from '@kbn/data-plugin/common'; import { IEmbeddable } from '@kbn/embeddable-plugin/public'; - import { persistableControlGroupInputIsEqual } from '@kbn/controls-plugin/common'; + import { DashboardContainerInput } from '../..'; -import { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types'; +import { areTimesEqual } from './filter_utils'; +import { DashboardPanelMap } from '../embeddable'; +import { DashboardOptions, DashboardState } from '../../types'; +import { pluginServices } from '../../services/plugin_services'; const stateKeystoIgnore = [ 'expandedPanelId', @@ -96,15 +107,67 @@ export const diffDashboardState = async ({ newState.controlGroupInput ); + const timeStatediff = getTimeSettingsAreEqual({ + currentTimeRestore: newState.timeRestore, + lastSaved: { ...pick(originalState, ['timeRange', 'timeRestore', 'refreshInterval']) }, + }) + ? {} + : pick(newState, ['timeRange', 'timeRestore', 'refreshInterval']); + return { ...commonStateDiff, ...(panelsAreEqual ? {} : { panels: newState.panels }), ...(filtersAreEqual ? {} : { filters: newState.filters }), ...(optionsAreEqual ? {} : { options: newState.options }), ...(controlGroupIsEqual ? {} : { controlGroupInput: newState.controlGroupInput }), + ...timeStatediff, }; }; +interface TimeStateDiffArg { + timeRange?: TimeRange; + timeRestore?: boolean; + refreshInterval?: RefreshInterval; +} + +export const getTimeSettingsAreEqual = ({ + lastSaved, + currentTimeRestore, +}: { + lastSaved?: TimeStateDiffArg; + currentTimeRestore?: boolean; +}) => { + const { + data: { + query: { + timefilter: { timefilter }, + }, + }, + } = pluginServices.getServices(); + + if (currentTimeRestore !== lastSaved?.timeRestore) return false; + if (!currentTimeRestore) return true; + + const currentRange = timefilter.getTime(); + const lastRange = lastSaved?.timeRange ?? timefilter.getTimeDefaults(); + if ( + !areTimesEqual(currentRange.from, lastRange.from) || + !areTimesEqual(currentRange.to, lastRange.to) + ) { + return false; + } + + const currentInterval = timefilter.getRefreshInterval(); + const lastInterval = lastSaved?.refreshInterval ?? timefilter.getRefreshIntervalDefaults(); + if ( + currentInterval.pause !== lastInterval.pause || + currentInterval.value !== lastInterval.value + ) { + return false; + } + return true; +}; + const getFiltersAreEqual = ( filtersA: Filter[], filtersB: Filter[], diff --git a/src/plugins/dashboard/public/application/lib/filter_utils.ts b/src/plugins/dashboard/public/application/lib/filter_utils.ts index 9b9a1270fd3ba..fb2762c7dc587 100644 --- a/src/plugins/dashboard/public/application/lib/filter_utils.ts +++ b/src/plugins/dashboard/public/application/lib/filter_utils.ts @@ -8,12 +8,7 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; -import type { Optional } from '@kbn/utility-types'; -import type { RefreshInterval } from '@kbn/data-plugin/public'; -import type { Filter, TimeRange } from '@kbn/es-query'; - -type TimeRangeCompare = Optional; -type RefreshIntervalCompare = Optional; +import type { Filter } from '@kbn/es-query'; /** * Converts the time to a utc formatted string. If the time is not valid (e.g. it might be in a relative format like @@ -32,22 +27,6 @@ export const convertTimeToUTCString = (time?: string | Moment): undefined | stri } }; -export const areTimeRangesEqual = (rangeA: TimeRangeCompare, rangeB: TimeRangeCompare): boolean => - areTimesEqual(rangeA.from, rangeB.from) && areTimesEqual(rangeA.to, rangeB.to); - -export const areRefreshIntervalsEqual = ( - refreshA?: RefreshIntervalCompare, - refreshB?: RefreshIntervalCompare -): boolean => refreshA?.pause === refreshB?.pause && refreshA?.value === refreshB?.value; - -/** - * Compares the two times, making sure they are in both compared in string format. Absolute times - * are sometimes stored as moment objects, but converted to strings when reloaded. Relative times are - * strings that are not convertible to moment objects. - * @param timeA {string|Moment} - * @param timeB {string|Moment} - * @returns {boolean} - */ export const areTimesEqual = (timeA?: string | Moment, timeB?: string | Moment) => { return convertTimeToUTCString(timeA) === convertTimeToUTCString(timeB); }; diff --git a/src/plugins/dashboard/public/application/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts index 1b4ab12d2bc1b..0f364a31061d3 100644 --- a/src/plugins/dashboard/public/application/lib/index.ts +++ b/src/plugins/dashboard/public/application/lib/index.ts @@ -6,28 +6,24 @@ * Side Public License, v 1. */ -export * from './filter_utils'; -export { getDashboardIdFromUrl } from './url'; -export { saveDashboard } from './save_dashboard'; -export { migrateAppState } from './migrate_app_state'; -export { addHelpMenuToAppChrome } from './help_menu_util'; -export { diffDashboardState } from './diff_dashboard_state'; -export { getTagsFromSavedDashboard } from './dashboard_tagging'; -export { syncDashboardUrlState } from './sync_dashboard_url_state'; -export { loadSavedDashboardState } from './load_saved_dashboard_state'; -export { attemptLoadDashboardByTitle } from './load_dashboard_by_title'; -export { syncDashboardFilterState } from './sync_dashboard_filter_state'; -export { syncDashboardDataViews } from './sync_dashboard_data_views'; -export { syncDashboardContainerInput } from './sync_dashboard_container_input'; -export { loadDashboardHistoryLocationState } from './load_dashboard_history_location_state'; -export { buildDashboardContainer, tryDestroyDashboardContainer } from './build_dashboard_container'; export { - stateToDashboardContainerInput, - savedObjectToDashboardState, -} from './convert_dashboard_state'; + areTimesEqual, + convertTimeToUTCString, + cleanFiltersForSerialize, + cleanFiltersForComparison, +} from './filter_utils'; export { createSessionRestorationDataProvider, enableDashboardSearchSessions, getSearchSessionIdFromURL, getSessionURLObservable, } from './dashboard_session_restoration'; +export { addHelpMenuToAppChrome } from './help_menu_util'; +export { diffDashboardState } from './diff_dashboard_state'; +export { syncDashboardUrlState } from './sync_dashboard_url_state'; +export { syncDashboardDataViews } from './sync_dashboard_data_views'; +export { syncDashboardFilterState } from './sync_dashboard_filter_state'; +export { stateToDashboardContainerInput } from './convert_dashboard_state'; +export { syncDashboardContainerInput } from './sync_dashboard_container_input'; +export { loadDashboardHistoryLocationState } from './load_dashboard_history_location_state'; +export { buildDashboardContainer, tryDestroyDashboardContainer } from './build_dashboard_container'; diff --git a/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts b/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts deleted file mode 100644 index bff9f8600c0ed..0000000000000 --- a/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DashboardSavedObject } from '../..'; -import { pluginServices } from '../../services/plugin_services'; - -export async function attemptLoadDashboardByTitle( - title: string -): Promise<{ id: string } | undefined> { - const { - savedObjects: { client }, - } = pluginServices.getServices(); - - const results = await client.find({ - search: `"${title}"`, - searchFields: ['title'], - type: 'dashboard', - }); - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - (dashboard) => dashboard.attributes.title.toLowerCase() === title.toLowerCase() - ); - if (matchingDashboards.length === 1) { - return { id: matchingDashboards[0].id }; - } -} diff --git a/src/plugins/dashboard/public/application/lib/load_dashboard_history_location_state.ts b/src/plugins/dashboard/public/application/lib/load_dashboard_history_location_state.ts index ce06ef443d69f..9a7d1791c6c94 100644 --- a/src/plugins/dashboard/public/application/lib/load_dashboard_history_location_state.ts +++ b/src/plugins/dashboard/public/application/lib/load_dashboard_history_location_state.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { ForwardedDashboardState } from '../../locator'; import { DashboardState } from '../../types'; -import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; +import { ForwardedDashboardState } from '../../locator'; +import { convertSavedPanelsToPanelMap } from '../../../common'; export const loadDashboardHistoryLocationState = ( state?: ForwardedDashboardState diff --git a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts deleted file mode 100644 index 6a7eba0884abe..0000000000000 --- a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { getDashboard60Warning, dashboardLoadingErrorStrings } from '../../dashboard_strings'; -import { savedObjectToDashboardState } from './convert_dashboard_state'; -import { DashboardState, DashboardBuildContext } from '../../types'; -import { DashboardConstants, DashboardSavedObject } from '../..'; -import { migrateLegacyQuery } from './migrate_legacy_query'; -import { cleanFiltersForSerialize } from './filter_utils'; -import { pluginServices } from '../../services/plugin_services'; - -interface LoadSavedDashboardStateReturn { - savedDashboardState: DashboardState; - savedDashboard: DashboardSavedObject; -} - -/** - * Loads, migrates, and returns state from a dashboard saved object. - */ -export const loadSavedDashboardState = async ({ - history, - savedDashboards, - savedDashboardId, -}: DashboardBuildContext & { savedDashboardId?: string }): Promise< - LoadSavedDashboardStateReturn | undefined -> => { - const { - dashboardCapabilities: { showWriteControls }, - data: { - query: { queryString }, - }, - notifications: { toasts }, - } = pluginServices.getServices(); - - // BWC - remove for 8.0 - if (savedDashboardId === 'create') { - history.replace({ - ...history.location, // preserve query, - pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, - }); - - toasts.addWarning(getDashboard60Warning()); - return; - } - try { - const savedDashboard = (await savedDashboards.get({ - id: savedDashboardId, - useResolve: true, - })) as DashboardSavedObject; - const savedDashboardState = savedObjectToDashboardState({ - savedDashboard, - }); - - const isViewMode = !showWriteControls || Boolean(savedDashboard.id); - savedDashboardState.viewMode = isViewMode ? ViewMode.VIEW : ViewMode.EDIT; - savedDashboardState.filters = cleanFiltersForSerialize(savedDashboardState.filters); - savedDashboardState.query = migrateLegacyQuery( - savedDashboardState.query || queryString.getDefaultQuery() - ); - - return { savedDashboardState, savedDashboard }; - } catch (error) { - // E.g. a corrupt or deleted dashboard - toasts.addDanger(dashboardLoadingErrorStrings.getDashboardLoadError(error.message)); - history.push(DashboardConstants.LANDING_PAGE_PATH); - return; - } -}; diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts deleted file mode 100644 index 578439070e970..0000000000000 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { pluginServices } from '../../services/plugin_services'; -import { SavedDashboardPanel } from '../../types'; -import { migrateAppState } from './migrate_app_state'; - -pluginServices.getServices().initializerContext.kibanaVersion = '8.0'; - -test('migrate app state from 6.0', async () => { - const appState = { - uiState: { - 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, - }, - panels: [ - { - col: 1, - id: 'Visualization-MetricChart', - panelIndex: 1, - row: 1, - size_x: 6, - size_y: 3, - type: 'visualization', - }, - ], - }; - migrateAppState(appState as any); - expect(appState.uiState).toBeUndefined(); - - const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; - - expect(newPanel.gridData.w).toBe(24); - expect(newPanel.gridData.h).toBe(15); - expect(newPanel.gridData.x).toBe(0); - expect(newPanel.gridData.y).toBe(0); - - expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); -}); - -test('migrate sort from 6.1', async () => { - const appState = { - uiState: { - 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, - }, - panels: [ - { - col: 1, - id: 'Visualization-MetricChart', - panelIndex: 1, - row: 1, - size_x: 6, - size_y: 3, - type: 'visualization', - sort: 'sort', - }, - ], - useMargins: false, - }; - migrateAppState(appState as any); - expect(appState.uiState).toBeUndefined(); - - const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; - expect(newPanel.gridData.w).toBe(24); - expect(newPanel.gridData.h).toBe(15); - expect((newPanel as any).sort).toBeUndefined(); - - expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); -}); - -test('migrates 6.0 even when uiState does not exist', async () => { - const appState = { - panels: [ - { - col: 1, - id: 'Visualization-MetricChart', - panelIndex: 1, - row: 1, - size_x: 6, - size_y: 3, - type: 'visualization', - sort: 'sort', - }, - ], - }; - migrateAppState(appState as any); - expect((appState as any).uiState).toBeUndefined(); - - const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; - expect(newPanel.gridData.w).toBe(24); - expect(newPanel.gridData.h).toBe(15); - expect((newPanel as any).sort).toBeUndefined(); - - expect((newPanel.embeddableConfig as any).sort).toBe('sort'); -}); - -test('6.2 migration adjusts w & h without margins', async () => { - const appState = { - panels: [ - { - id: 'Visualization-MetricChart', - panelIndex: 1, - gridData: { - h: 3, - w: 7, - x: 2, - y: 5, - }, - type: 'visualization', - sort: 'sort', - version: '6.2.0', - }, - ], - useMargins: false, - }; - migrateAppState(appState as any); - expect((appState as any).uiState).toBeUndefined(); - - const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; - expect(newPanel.gridData.w).toBe(28); - expect(newPanel.gridData.h).toBe(15); - expect(newPanel.gridData.x).toBe(8); - expect(newPanel.gridData.y).toBe(25); - expect((newPanel as any).sort).toBeUndefined(); - - expect((newPanel.embeddableConfig as any).sort).toBe('sort'); -}); - -test('6.2 migration adjusts w & h with margins', async () => { - const appState = { - panels: [ - { - id: 'Visualization-MetricChart', - panelIndex: 1, - gridData: { - h: 3, - w: 7, - x: 2, - y: 5, - }, - type: 'visualization', - sort: 'sort', - version: '6.2.0', - }, - ], - useMargins: true, - }; - migrateAppState(appState as any); - expect((appState as any).uiState).toBeUndefined(); - - const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; - expect(newPanel.gridData.w).toBe(28); - expect(newPanel.gridData.h).toBe(12); - expect(newPanel.gridData.x).toBe(8); - expect(newPanel.gridData.y).toBe(20); - expect((newPanel as any).sort).toBeUndefined(); - - expect((newPanel.embeddableConfig as any).sort).toBe('sort'); -}); diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts deleted file mode 100644 index e077aab89ea8b..0000000000000 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import semverSatisfies from 'semver/functions/satisfies'; - -import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE } from '@kbn/analytics'; -import type { SerializableRecord } from '@kbn/utility-types'; - -import { RawDashboardState, SavedDashboardPanel } from '../../types'; -import type { - SavedDashboardPanelTo60, - SavedDashboardPanel730ToLatest, - SavedDashboardPanel610, - SavedDashboardPanel630, - SavedDashboardPanel640To720, - SavedDashboardPanel620, -} from '../../../common'; -import { migratePanelsTo730 } from '../../../common'; -import { pluginServices } from '../../services/plugin_services'; - -/** - * Attempts to migrate the state stored in the URL into the latest version of it. - * - * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. - */ -export function migrateAppState( - appState: { [key: string]: any } & RawDashboardState -): RawDashboardState { - if (!appState.panels) { - throw new Error( - i18n.translate('dashboard.panel.invalidData', { - defaultMessage: 'Invalid data in url', - }) - ); - } - - const { - usageCollection: { reportUiCounter }, - initializerContext: { kibanaVersion }, - } = pluginServices.getServices(); - - const panelNeedsMigration = ( - appState.panels as Array< - | SavedDashboardPanelTo60 - | SavedDashboardPanel610 - | SavedDashboardPanel620 - | SavedDashboardPanel630 - | SavedDashboardPanel640To720 - | SavedDashboardPanel730ToLatest - > - ).some((panel) => { - if ((panel as { version?: string }).version === undefined) return true; - - const version = (panel as SavedDashboardPanel730ToLatest).version; - - if (reportUiCounter) { - // This will help us figure out when to remove support for older style URLs. - reportUiCounter('DashboardPanelVersionInUrl', METRIC_TYPE.LOADED, `${version}`); - } - - return semverSatisfies(version, '<7.3'); - }); - - if (panelNeedsMigration) { - appState.panels = migratePanelsTo730( - appState.panels as Array< - | SavedDashboardPanelTo60 - | SavedDashboardPanel610 - | SavedDashboardPanel620 - | SavedDashboardPanel630 - | SavedDashboardPanel640To720 - >, - kibanaVersion, - appState.useMargins as boolean, - appState.uiState as { [key: string]: SerializableRecord } - ) as SavedDashboardPanel[]; - delete appState.uiState; - } - - return appState; -} diff --git a/src/plugins/dashboard/public/application/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts deleted file mode 100644 index f3347ca3f2041..0000000000000 --- a/src/plugins/dashboard/public/application/lib/save_dashboard.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; - -import { isFilterPinned } from '@kbn/es-query'; -import type { RefreshInterval } from '@kbn/data-plugin/public'; -import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; - -import { convertTimeToUTCString } from '.'; -import type { DashboardSavedObject } from '../../saved_dashboards'; -import { dashboardSaveToastStrings } from '../../dashboard_strings'; -import { getHasTaggingCapabilitiesGuard } from './dashboard_tagging'; -import type { DashboardRedirect, DashboardState } from '../../types'; -import { serializeControlGroupToDashboardSavedObject } from './dashboard_control_group'; -import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; -import { pluginServices } from '../../services/plugin_services'; - -export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { stayInEditMode?: boolean }; - -interface SaveDashboardProps { - redirectTo: DashboardRedirect; - currentState: DashboardState; - saveOptions: SavedDashboardSaveOpts; - savedDashboard: DashboardSavedObject; -} - -export const saveDashboard = async ({ - redirectTo, - saveOptions, - currentState, - savedDashboard, -}: SaveDashboardProps): Promise<{ id?: string; redirected?: boolean; error?: any }> => { - const { - data: { - query: { - timefilter: { timefilter }, - }, - }, - dashboardSessionStorage, - initializerContext: { kibanaVersion }, - notifications, - } = pluginServices.getServices(); - - const lastDashboardId = savedDashboard.id; - const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(); - - const { panels, title, tags, description, timeRestore, options } = currentState; - - const savedDashboardPanels = Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) - ); - - savedDashboard.title = title; - savedDashboard.description = description; - savedDashboard.timeRestore = timeRestore; - savedDashboard.optionsJSON = JSON.stringify(options); - savedDashboard.panelsJSON = JSON.stringify(savedDashboardPanels); - - // control group input - serializeControlGroupToDashboardSavedObject(savedDashboard, currentState); - - if (hasTaggingCapabilities(savedDashboard)) { - savedDashboard.setTags(tags); - } - - const { from, to } = timefilter.getTime(); - savedDashboard.timeFrom = savedDashboard.timeRestore ? convertTimeToUTCString(from) : undefined; - savedDashboard.timeTo = savedDashboard.timeRestore ? convertTimeToUTCString(to) : undefined; - - const timeRestoreObj: RefreshInterval = _.pick(timefilter.getRefreshInterval(), [ - 'display', - 'pause', - 'section', - 'value', - ]) as RefreshInterval; - savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined; - - // only save unpinned filters - const unpinnedFilters = savedDashboard.getFilters().filter((filter) => !isFilterPinned(filter)); - savedDashboard.searchSource.setField('filter', unpinnedFilters); - - try { - const newId = await savedDashboard.save(saveOptions); - if (newId) { - notifications.toasts.addSuccess({ - title: dashboardSaveToastStrings.getSuccessString(currentState.title), - 'data-test-subj': 'saveDashboardSuccess', - }); - - /** - * If the dashboard id has been changed, redirect to the new ID to keep the url param in sync. - */ - if (newId !== lastDashboardId) { - dashboardSessionStorage.clearState(lastDashboardId); - redirectTo({ - id: newId, - editMode: true, - useReplace: true, - destination: 'dashboard', - }); - return { redirected: true, id: newId }; - } - } - return { id: newId }; - } catch (error) { - notifications.toasts.addDanger( - dashboardSaveToastStrings.getFailureString(currentState.title, error.message), - { - 'data-test-subj': 'saveDashboardFailure', - } - ); - return { error }; - } -}; diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts index 67e098186f9e9..9e97beaad276f 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts @@ -10,12 +10,11 @@ import _ from 'lodash'; import { Subscription } from 'rxjs'; import { debounceTime, tap } from 'rxjs/operators'; -import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; +import { compareFilters, COMPARE_ALL_OPTIONS } from '@kbn/es-query'; import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; -import type { Query } from '@kbn/es-query'; import type { DashboardContainer } from '../embeddable'; -import { DashboardConstants, type DashboardSavedObject } from '../..'; +import { DashboardConstants } from '../..'; import { setControlGroupState, setExpandedPanelId, @@ -36,16 +35,13 @@ import { pluginServices } from '../../services/plugin_services'; type SyncDashboardContainerCommon = DashboardBuildContext & { dashboardContainer: DashboardContainer; - savedDashboard: DashboardSavedObject; }; type ApplyStateChangesToContainerProps = SyncDashboardContainerCommon & { force: boolean; }; -type ApplyContainerChangesToStateProps = SyncDashboardContainerCommon & { - applyFilters: (query: Query, filters: Filter[]) => void; -}; +type ApplyContainerChangesToStateProps = SyncDashboardContainerCommon; type SyncDashboardContainerProps = SyncDashboardContainerCommon & ApplyContainerChangesToStateProps; @@ -94,7 +90,6 @@ export const syncDashboardContainerInput = ( }; export const applyContainerChangesToState = ({ - applyFilters, dashboardContainer, getLatestDashboardState, dispatchDashboardStateChange, @@ -112,7 +107,6 @@ export const applyContainerChangesToState = ({ if (!compareFilters(input.filters, filterManager.getFilters(), COMPARE_ALL_OPTIONS)) { // Add filters modifies the object passed to it, hence the clone deep. filterManager.addFilters(_.cloneDeep(input.filters)); - applyFilters(latestState.query, input.filters); } if (!_.isEqual(input.panels, latestState.panels)) { @@ -144,7 +138,6 @@ export const applyContainerChangesToState = ({ export const applyStateChangesToContainer = ({ force, history, - savedDashboard, dashboardContainer, kbnUrlStateStorage, isEmbeddedExternally, @@ -161,7 +154,6 @@ export const applyStateChangesToContainer = ({ const currentDashboardStateAsInput = stateToDashboardContainerInput({ dashboardState: latestState, isEmbeddedExternally, - savedDashboard, }); const differences = diffDashboardContainerInput( dashboardContainer.getInput(), diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts index d02e674936aef..0bce899e22fcd 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts @@ -8,25 +8,22 @@ import _ from 'lodash'; import { merge } from 'rxjs'; -import { debounceTime, finalize, map, switchMap, tap } from 'rxjs/operators'; +import { finalize, map, switchMap, tap } from 'rxjs/operators'; import { connectToQueryState, GlobalQueryStateFromUrl, - syncQueryStateWithUrl, + syncGlobalQueryStateWithUrl, waitUntilNextSessionCompletes$, } from '@kbn/data-plugin/public'; import type { Filter, Query } from '@kbn/es-query'; import { cleanFiltersForSerialize } from '.'; -import { setQuery } from '../state'; import type { DashboardBuildContext, DashboardState } from '../../types'; -import type { DashboardSavedObject } from '../../saved_dashboards'; import { setFiltersAndQuery } from '../state/dashboard_state_slice'; import { pluginServices } from '../../services/plugin_services'; type SyncDashboardFilterStateProps = DashboardBuildContext & { initialDashboardState: DashboardState; - savedDashboard: DashboardSavedObject; }; /** @@ -35,7 +32,6 @@ type SyncDashboardFilterStateProps = DashboardBuildContext & { * and the dashboard Redux store. */ export const syncDashboardFilterState = ({ - savedDashboard, kbnUrlStateStorage, initialDashboardState, $checkForUnsavedChanges, @@ -46,25 +42,17 @@ export const syncDashboardFilterState = ({ const { data: { query: queryService, search }, } = pluginServices.getServices(); - const { filterManager, queryString, timefilter } = queryService; + const { queryString, timefilter } = queryService; const { timefilter: timefilterService } = timefilter; // apply initial dashboard filter state. applyDashboardFilterState({ currentDashboardState: initialDashboardState, kbnUrlStateStorage, - savedDashboard, }); - // this callback will be used any time new filters and query need to be applied. - const applyFilters = (query: Query, filters: Filter[]) => { - savedDashboard.searchSource.setField('query', query); - savedDashboard.searchSource.setField('filter', filters); - dispatchDashboardStateChange(setQuery(query)); - }; - // starts syncing `_g` portion of url with query services - const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( + const { stop: stopSyncingQueryServiceStateWithUrl } = syncGlobalQueryStateWithUrl( queryService, kbnUrlStateStorage ); @@ -81,7 +69,6 @@ export const syncDashboardFilterState = ({ set: ({ filters, query }) => { intermediateFilterState.filters = cleanFiltersForSerialize(filters ?? []) || []; intermediateFilterState.query = query || queryString.getDefaultQuery(); - applyFilters(intermediateFilterState.query, intermediateFilterState.filters); dispatchDashboardStateChange(setFiltersAndQuery(intermediateFilterState)); }, state$: $onDashboardStateChange.pipe( @@ -97,11 +84,6 @@ export const syncDashboardFilterState = ({ } ); - // apply filters when the filter manager changes - const filterManagerSubscription = merge(filterManager.getUpdates$(), queryString.getUpdates$()) - .pipe(debounceTime(100)) - .subscribe(() => applyFilters(queryString.getQuery() as Query, filterManager.getFilters())); - const timeRefreshSubscription = merge( timefilterService.getRefreshIntervalUpdate$(), timefilterService.getTimeUpdate$() @@ -127,26 +109,23 @@ export const syncDashboardFilterState = ({ .subscribe(); const stopSyncingDashboardFilterState = () => { - filterManagerSubscription.unsubscribe(); forceRefreshSubscription.unsubscribe(); timeRefreshSubscription.unsubscribe(); stopSyncingQueryServiceStateWithUrl(); stopSyncingAppFilters(); }; - return { applyFilters, stopSyncingDashboardFilterState }; + return { stopSyncingDashboardFilterState }; }; interface ApplyDashboardFilterStateProps { kbnUrlStateStorage: DashboardBuildContext['kbnUrlStateStorage']; currentDashboardState: DashboardState; - savedDashboard: DashboardSavedObject; } export const applyDashboardFilterState = ({ currentDashboardState, kbnUrlStateStorage, - savedDashboard, }: ApplyDashboardFilterStateProps) => { const { data: { @@ -155,13 +134,9 @@ export const applyDashboardFilterState = ({ } = pluginServices.getServices(); const { timefilter: timefilterService } = timefilter; - // apply filters to the query service and to the saved dashboard + // apply filters and query to the query service filterManager.setAppFilters(_.cloneDeep(currentDashboardState.filters)); - savedDashboard.searchSource.setField('filter', currentDashboardState.filters); - - // apply query to the query service and to the saved dashboard queryString.setQuery(currentDashboardState.query); - savedDashboard.searchSource.setField('query', currentDashboardState.query); /** * If a global time range is not set explicitly and the time range was saved with the dashboard, apply @@ -169,18 +144,11 @@ export const applyDashboardFilterState = ({ */ if (currentDashboardState.timeRestore) { const globalQueryState = kbnUrlStateStorage.get('_g'); - if (!globalQueryState?.time) { - if (savedDashboard.timeFrom && savedDashboard.timeTo) { - timefilterService.setTime({ - from: savedDashboard.timeFrom, - to: savedDashboard.timeTo, - }); - } + if (!globalQueryState?.time && currentDashboardState.timeRange) { + timefilterService.setTime(currentDashboardState.timeRange); } - if (!globalQueryState?.refreshInterval) { - if (savedDashboard.refreshInterval) { - timefilterService.setRefreshInterval(savedDashboard.refreshInterval); - } + if (!globalQueryState?.refreshInterval && currentDashboardState.refreshInterval) { + timefilterService.setRefreshInterval(currentDashboardState.refreshInterval); } } }; diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts index 947e3f5d69de7..31101ae3679f0 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts @@ -8,41 +8,52 @@ import _ from 'lodash'; import { debounceTime } from 'rxjs/operators'; +import semverSatisfies from 'semver/functions/satisfies'; import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; -import { migrateAppState } from '.'; -import { DashboardSavedObject } from '../..'; + import { setDashboardState } from '../state'; import { migrateLegacyQuery } from './migrate_legacy_query'; -import { applyDashboardFilterState } from './sync_dashboard_filter_state'; +import { pluginServices } from '../../services/plugin_services'; import { DASHBOARD_STATE_STORAGE_KEY } from '../../dashboard_constants'; -import type { - DashboardBuildContext, - DashboardPanelMap, - DashboardState, - RawDashboardState, -} from '../../types'; -import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; +import { applyDashboardFilterState } from './sync_dashboard_filter_state'; +import { dashboardSavedObjectErrorStrings } from '../../dashboard_strings'; +import { convertSavedPanelsToPanelMap, DashboardPanelMap } from '../../../common'; +import type { DashboardBuildContext, DashboardState, RawDashboardState } from '../../types'; -type SyncDashboardUrlStateProps = DashboardBuildContext & { savedDashboard: DashboardSavedObject }; +/** + * We no longer support loading panels from a version older than 7.3 in the URL. + * @returns whether or not there is a panel in the URL state saved with a version before 7.3 + */ +export const isPanelVersionTooOld = (panels: RawDashboardState['panels']) => { + for (const panel of panels) { + if (!panel.version || semverSatisfies(panel.version, '<7.3')) return true; + } + return false; +}; export const syncDashboardUrlState = ({ dispatchDashboardStateChange, getLatestDashboardState, kbnUrlStateStorage, - savedDashboard, -}: SyncDashboardUrlStateProps) => { +}: DashboardBuildContext) => { /** * Loads any dashboard state from the URL, and removes the state from the URL. */ const loadAndRemoveDashboardState = (): Partial => { + const { + notifications: { toasts }, + } = pluginServices.getServices(); const rawAppStateInUrl = kbnUrlStateStorage.get(DASHBOARD_STATE_STORAGE_KEY); if (!rawAppStateInUrl) return {}; - let panelsMap: DashboardPanelMap = {}; + let panelsMap: DashboardPanelMap | undefined; if (rawAppStateInUrl.panels && rawAppStateInUrl.panels.length > 0) { - const rawState = migrateAppState(rawAppStateInUrl); - panelsMap = convertSavedPanelsToPanelMap(rawState.panels); + if (isPanelVersionTooOld(rawAppStateInUrl.panels)) { + toasts.addWarning(dashboardSavedObjectErrorStrings.getPanelTooOldError()); + } else { + panelsMap = convertSavedPanelsToPanelMap(rawAppStateInUrl.panels); + } } const migratedQuery = rawAppStateInUrl.query @@ -58,7 +69,7 @@ export const syncDashboardUrlState = ({ return { ..._.omit(rawAppStateInUrl, ['panels', 'query']), ...(migratedQuery ? { query: migratedQuery } : {}), - ...(rawAppStateInUrl.panels ? { panels: panelsMap } : {}), + ...(panelsMap ? { panels: panelsMap } : {}), }; }; @@ -75,7 +86,6 @@ export const syncDashboardUrlState = ({ applyDashboardFilterState({ currentDashboardState: updatedDashboardState, kbnUrlStateStorage, - savedDashboard, }); if (Object.keys(stateFromUrl).length === 0) return; diff --git a/src/plugins/dashboard/public/application/lib/url.test.ts b/src/plugins/dashboard/public/application/lib/url.test.ts deleted file mode 100644 index fc7e51b8c2e3e..0000000000000 --- a/src/plugins/dashboard/public/application/lib/url.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getDashboardIdFromUrl } from './url'; - -test('getDashboardIdFromUrl', () => { - let url = - "http://localhost:5601/wev/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()"; - expect(getDashboardIdFromUrl(url)).toEqual(undefined); - - url = - "http://localhost:5601/wev/app/dashboards#/view/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))"; - expect(getDashboardIdFromUrl(url)).toEqual('625357282'); - - url = 'http://myserver.mydomain.com:5601/wev/app/dashboards#/view/777182'; - expect(getDashboardIdFromUrl(url)).toEqual('777182'); - - url = - "http://localhost:5601/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()"; - expect(getDashboardIdFromUrl(url)).toEqual(undefined); - - url = '/view/test/?_g=(refreshInterval:'; - expect(getDashboardIdFromUrl(url)).toEqual('test'); - - url = 'view/test/?_g=(refreshInterval:'; - expect(getDashboardIdFromUrl(url)).toEqual('test'); - - url = '/other-app/test/'; - expect(getDashboardIdFromUrl(url)).toEqual(undefined); -}); diff --git a/src/plugins/dashboard/public/application/lib/url.ts b/src/plugins/dashboard/public/application/lib/url.ts deleted file mode 100644 index 0ff2809a92667..0000000000000 --- a/src/plugins/dashboard/public/application/lib/url.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Returns dashboard id from URL - * literally looks from id after `dashboard/` string and before `/`, `?` and end of string - * @param url to extract dashboardId from - * input: http://localhost:5601/lib/app/kibana#/dashboard?param1=x¶m2=y¶m3=z - * output: undefined - * input: http://localhost:5601/lib/app/kibana#/dashboard/39292992?param1=x¶m2=y¶m3=z - * output: 39292992 - */ -export function getDashboardIdFromUrl(url: string): string | undefined { - const [, dashboardId] = url.match(/view\/(.*?)(\/|\?|$)/) ?? [ - undefined, // full match - undefined, // group with dashboardId - ]; - return dashboardId ?? undefined; -} diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx index 5712b4c6ee2f8..4a77249aeb39c 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx @@ -9,19 +9,15 @@ import React from 'react'; import { mount } from 'enzyme'; -import { I18nProvider, FormattedRelative } from '@kbn/i18n-react'; -import { SimpleSavedObject } from '@kbn/core/public'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { TableListViewKibanaDependencies, TableListViewKibanaProvider, } from '@kbn/content-management-table-list'; +import { I18nProvider, FormattedRelative } from '@kbn/i18n-react'; +import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import { DashboardAppServices } from '../../types'; -import { DashboardListing, DashboardListingProps } from './dashboard_listing'; -import { makeDefaultServices } from '../test_helpers'; import { pluginServices } from '../../services/plugin_services'; +import { DashboardListing, DashboardListingProps } from './dashboard_listing'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; function makeDefaultProps(): DashboardListingProps { @@ -31,14 +27,7 @@ function makeDefaultProps(): DashboardListingProps { }; } -function mountWith({ - props: incomingProps, - services: incomingServices, -}: { - props?: DashboardListingProps; - services?: DashboardAppServices; -}) { - const services = incomingServices ?? makeDefaultServices(); +function mountWith({ props: incomingProps }: { props?: DashboardListingProps }) { const props = incomingProps ?? makeDefaultProps(); const wrappingComponent: React.FC<{ children: React.ReactNode; @@ -47,35 +36,32 @@ function mountWith({ return ( - {/* Can't get rid of KibanaContextProvider here yet because of 'call to action when no dashboards exist' tests below */} - - null, - }, + null, }, - } as unknown as TableListViewKibanaDependencies['savedObjectsTagging'] - } - FormattedRelative={FormattedRelative} - toMountPoint={() => () => () => undefined} - > - {children} - - + }, + } as unknown as TableListViewKibanaDependencies['savedObjectsTagging'] + } + FormattedRelative={FormattedRelative} + toMountPoint={() => () => () => undefined} + > + {children} + ); }; const component = mount(, { wrappingComponent }); - return { component, props, services }; + return { component, props }; } describe('after fetch', () => { @@ -89,14 +75,14 @@ describe('after fetch', () => { }); test('renders call to action when no dashboards exist', async () => { - const services = makeDefaultServices(); - services.savedDashboards.find = () => { - return Promise.resolve({ - total: 0, - hits: [], - }); - }; - const { component } = mountWith({ services }); + ( + pluginServices.getServices().dashboardSavedObject.findDashboards.findSavedObjects as jest.Mock + ).mockResolvedValue({ + total: 0, + hits: [], + }); + + const { component } = mountWith({}); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected @@ -105,18 +91,18 @@ describe('after fetch', () => { }); test('renders call to action with continue when no dashboards exist but one is in progress', async () => { - const services = makeDefaultServices(); - services.savedDashboards.find = () => { - return Promise.resolve({ - total: 0, - hits: [], - }); - }; pluginServices.getServices().dashboardSessionStorage.getDashboardIdsWithUnsavedChanges = jest .fn() .mockReturnValueOnce([DASHBOARD_PANELS_UNSAVED_ID]) .mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']); - const { component } = mountWith({ services }); + ( + pluginServices.getServices().dashboardSavedObject.findDashboards.findSavedObjects as jest.Mock + ).mockResolvedValue({ + total: 0, + hits: [], + }); + + const { component } = mountWith({}); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected @@ -139,17 +125,9 @@ describe('after fetch', () => { const title = 'search by title'; const props = makeDefaultProps(); props.title = title; - pluginServices.getServices().savedObjects.client.find = () => { - return Promise.resolve({ - perPage: 10, - total: 2, - page: 0, - savedObjects: [ - { attributes: { title: `${title}_number1` }, id: 'hello there' } as SimpleSavedObject, - { attributes: { title: `${title}_number2` }, id: 'goodbye' } as SimpleSavedObject, - ], - }); - }; + ( + pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock + ).mockResolvedValue(undefined); const { component } = mountWith({ props }); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -163,14 +141,9 @@ describe('after fetch', () => { const title = 'search by title'; const props = makeDefaultProps(); props.title = title; - pluginServices.getServices().savedObjects.client.find = () => { - return Promise.resolve({ - perPage: 10, - total: 1, - page: 0, - savedObjects: [{ attributes: { title }, id: 'you_found_me' } as SimpleSavedObject], - }); - }; + ( + pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock + ).mockResolvedValue({ id: 'you_found_me' }); const { component } = mountWith({ props }); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index 40753c556a56a..1e78b94303478 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -6,7 +6,10 @@ * Side Public License, v 1. */ +import useMount from 'react-use/lib/useMount'; import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + import { EuiLink, EuiButton, @@ -15,30 +18,29 @@ import { EuiFlexItem, EuiButtonEmpty, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; -import useMount from 'react-use/lib/useMount'; -import type { SavedObjectReference } from '@kbn/core/types'; -import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public'; +import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; +import type { SavedObjectsFindOptionsReference, SimpleSavedObject } from '@kbn/core/public'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { TableListView, type UserContentCommonSchema } from '@kbn/content-management-table-list'; -import { attemptLoadDashboardByTitle } from '../lib'; -import { DashboardAppServices, DashboardRedirect } from '../../types'; import { getDashboardBreadcrumb, - dashboardListingTable, + dashboardListingTableStrings, noItemsStrings, dashboardUnsavedListingStrings, getNewDashboardTitle, + dashboardSavedObjectErrorStrings, } from '../../dashboard_strings'; +import { DashboardConstants } from '../..'; +import { DashboardRedirect } from '../../types'; +import { pluginServices } from '../../services/plugin_services'; import { DashboardUnsavedListing } from './dashboard_unsaved_listing'; -import { confirmCreateWithUnsaved, confirmDiscardUnsavedChanges } from './confirm_overlays'; import { getDashboardListItemLink } from './get_dashboard_list_item_link'; +import { confirmCreateWithUnsaved, confirmDiscardUnsavedChanges } from './confirm_overlays'; import { DashboardAppNoDataPage, isDashboardAppInNoDataState } from '../dashboard_app_no_data'; -import { pluginServices } from '../../services/plugin_services'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; +import { DashboardAttributes } from '../embeddable'; const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; @@ -52,17 +54,18 @@ interface DashboardSavedObjectUserContent extends UserContentCommonSchema { } const toTableListViewSavedObject = ( - savedObject: Record + savedObject: SimpleSavedObject ): DashboardSavedObjectUserContent => { + const { title, description, timeRestore } = savedObject.attributes; return { - id: savedObject.id as string, - updatedAt: savedObject.updatedAt! as string, - references: savedObject.references as SavedObjectReference[], type: 'dashboard', + id: savedObject.id, + updatedAt: savedObject.updatedAt!, + references: savedObject.references, attributes: { - title: (savedObject.title as string) ?? '', - description: savedObject.description as string, - timeRestore: savedObject.timeRestore as boolean, + title, + description, + timeRestore, }, }; }; @@ -80,19 +83,16 @@ export const DashboardListing = ({ initialFilter, kbnUrlStateStorage, }: DashboardListingProps) => { - const { - services: { savedDashboards }, - } = useKibana(); - const { application, + data: { query }, + dashboardSessionStorage, + settings: { uiSettings }, + notifications: { toasts }, chrome: { setBreadcrumbs }, coreContext: { executionContext }, dashboardCapabilities: { showWriteControls }, - dashboardSessionStorage, - data: { query }, - savedObjects: { client }, - settings: { uiSettings }, + dashboardSavedObject: { findDashboards, savedObjectsClient }, } = pluginServices.getServices(); const [showNoDataPage, setShowNoDataPage] = useState(false); @@ -125,7 +125,7 @@ export const DashboardListing = ({ kbnUrlStateStorage ); if (title) { - attemptLoadDashboardByTitle(title).then((result) => { + findDashboards.findByTitle(title).then((result) => { if (!result) return; redirectTo({ destination: 'dashboard', @@ -138,7 +138,7 @@ export const DashboardListing = ({ return () => { stopSyncingQueryServiceStateWithUrl(); }; - }, [title, client, redirectTo, query, kbnUrlStateStorage]); + }, [title, redirectTo, query, kbnUrlStateStorage, findDashboards]); const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); @@ -262,10 +262,11 @@ export const DashboardListing = ({ const fetchItems = useCallback( (searchTerm: string, references?: SavedObjectsFindOptionsReference[]) => { - return savedDashboards - .find(searchTerm, { - hasReference: references, + return findDashboards + .findSavedObjects({ + search: searchTerm, size: listingLimit, + hasReference: references, }) .then(({ total, hits }) => { return { @@ -274,16 +275,24 @@ export const DashboardListing = ({ }; }); }, - [listingLimit, savedDashboards] + [findDashboards, listingLimit] ); const deleteItems = useCallback( - (dashboards: Array<{ id: string }>) => { - dashboards.map((d) => dashboardSessionStorage.clearState(d.id)); + async (dashboardsToDelete: Array<{ id: string }>) => { + await Promise.all( + dashboardsToDelete.map(({ id }) => { + dashboardSessionStorage.clearState(id); + return savedObjectsClient.delete(DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, id); + }) + ).catch((error) => { + toasts.addError(error, { + title: dashboardSavedObjectErrorStrings.getErrorDeletingDashboardToast(), + }); + }); setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); - return savedDashboards.delete(dashboards.map((d) => d.id)); }, - [savedDashboards, dashboardSessionStorage] + [savedObjectsClient, dashboardSessionStorage, toasts] ); const editItem = useCallback( @@ -292,7 +301,7 @@ export const DashboardListing = ({ [redirectTo] ); - const { getEntityName, getTableListTitle, getEntityNamePlural } = dashboardListingTable; + const { getEntityName, getTableListTitle, getEntityNamePlural } = dashboardListingTableStrings; return ( <> {showNoDataPage && ( diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx index 1feec9bbdc42f..34a78c5181b92 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx @@ -11,87 +11,46 @@ import { mount } from 'enzyme'; import { I18nProvider } from '@kbn/i18n-react'; import { waitFor } from '@testing-library/react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { DashboardSavedObject } from '../..'; -import { DashboardAppServices } from '../../types'; -import { makeDefaultServices } from '../test_helpers'; -import { SavedObjectLoader } from '../../services/saved_object_loader'; import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; import { pluginServices } from '../../services/plugin_services'; -const mockedDashboards: { [key: string]: DashboardSavedObject } = { - dashboardUnsavedOne: { - id: `dashboardUnsavedOne`, - title: `Dashboard Unsaved One`, - } as DashboardSavedObject, - dashboardUnsavedTwo: { - id: `dashboardUnsavedTwo`, - title: `Dashboard Unsaved Two`, - } as DashboardSavedObject, - dashboardUnsavedThree: { - id: `dashboardUnsavedThree`, - title: `Dashboard Unsaved Three`, - } as DashboardSavedObject, -}; - -function makeServices(): DashboardAppServices { - const services = makeDefaultServices(); - const savedDashboards = {} as SavedObjectLoader; - savedDashboards.get = jest - .fn() - .mockImplementation((id: string) => Promise.resolve(mockedDashboards[id])); - return { - ...services, - savedDashboards, - }; -} - const makeDefaultProps = (): DashboardUnsavedListingProps => ({ redirectTo: jest.fn(), unsavedDashboardIds: ['dashboardUnsavedOne', 'dashboardUnsavedTwo', 'dashboardUnsavedThree'], refreshUnsavedDashboards: jest.fn(), }); -function mountWith({ - services: incomingServices, - props: incomingProps, -}: { - services?: DashboardAppServices; - props?: DashboardUnsavedListingProps; -}) { - const services = incomingServices ?? makeServices(); +function mountWith({ props: incomingProps }: { props?: DashboardUnsavedListingProps }) { const props = incomingProps ?? makeDefaultProps(); const wrappingComponent: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - return ( - - {/* Only the old savedObjects service is used for `DashboardUnsavedListing`, so will need to wrap this in - `DashboardServicesProvider` instead once that is removed as part of https://github.com/elastic/kibana/pull/138774*/} - {children} - - ); + return {children}; }; const component = mount(, { wrappingComponent }); - return { component, props, services }; + return { component, props }; } describe('Unsaved listing', () => { it('Gets information for each unsaved dashboard', async () => { - const { services } = mountWith({}); + mountWith({}); await waitFor(() => { - expect(services.savedDashboards.get).toHaveBeenCalledTimes(3); + expect( + pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds + ).toHaveBeenCalledTimes(1); }); }); - it('Does not attempt to get unsaved dashboard id', async () => { + it('Does not attempt to get newly created dashboard', async () => { const props = makeDefaultProps(); props.unsavedDashboardIds = ['dashboardUnsavedOne', DASHBOARD_PANELS_UNSAVED_ID]; - const { services } = mountWith({ props }); + mountWith({ props }); await waitFor(() => { - expect(services.savedDashboards.get).toHaveBeenCalledTimes(1); + expect( + pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds + ).toHaveBeenCalledWith(['dashboardUnsavedOne']); }); }); @@ -146,14 +105,22 @@ describe('Unsaved listing', () => { }); it('removes unsaved changes from any dashboard which errors on fetch', async () => { - const services = makeServices(); + ( + pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds as jest.Mock + ).mockResolvedValue([ + { + id: 'failCase1', + status: 'error', + error: { error: 'oh no', message: 'bwah', statusCode: 100 }, + }, + { + id: 'failCase2', + status: 'error', + error: { error: 'oh no', message: 'bwah', statusCode: 100 }, + }, + ]); + const props = makeDefaultProps(); - services.savedDashboards.get = jest.fn().mockImplementation((id: string) => { - if (id === 'failCase1' || id === 'failCase2') { - return Promise.reject(new Error()); - } - return Promise.resolve(mockedDashboards[id]); - }); props.unsavedDashboardIds = [ 'dashboardUnsavedOne', @@ -162,7 +129,7 @@ describe('Unsaved listing', () => { 'failCase1', 'failCase2', ]; - const { component } = mountWith({ services, props }); + const { component } = mountWith({ props }); waitFor(() => { component.update(); expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx index a5c5b1b224a32..3aa862fe30264 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -import React, { useCallback, useEffect, useState } from 'react'; - import { EuiButtonEmpty, EuiCallOut, @@ -17,14 +15,14 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React, { useCallback, useEffect, useState } from 'react'; -import type { DashboardSavedObject } from '../..'; -import { dashboardUnsavedListingStrings, getNewDashboardTitle } from '../../dashboard_strings'; -import type { DashboardAppServices, DashboardRedirect } from '../../types'; -import { confirmDiscardUnsavedChanges } from './confirm_overlays'; +import type { DashboardRedirect } from '../../types'; import { pluginServices } from '../../services/plugin_services'; +import { confirmDiscardUnsavedChanges } from './confirm_overlays'; +import { dashboardUnsavedListingStrings, getNewDashboardTitle } from '../../dashboard_strings'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; +import { DashboardAttributes } from '../embeddable'; const DashboardUnsavedItem = ({ id, @@ -102,7 +100,7 @@ const DashboardUnsavedItem = ({ }; interface UnsavedItemMap { - [key: string]: DashboardSavedObject; + [key: string]: DashboardAttributes; } export interface DashboardUnsavedListingProps { @@ -117,10 +115,9 @@ export const DashboardUnsavedListing = ({ refreshUnsavedDashboards, }: DashboardUnsavedListingProps) => { const { - services: { savedDashboards }, - } = useKibana(); - - const { dashboardSessionStorage } = pluginServices.getServices(); + dashboardSessionStorage, + dashboardSavedObject: { savedObjectsClient, findDashboards }, + } = pluginServices.getServices(); const [items, setItems] = useState({}); @@ -146,28 +143,24 @@ export const DashboardUnsavedListing = ({ return; } let canceled = false; - const dashPromises = unsavedDashboardIds - .filter((id) => id !== DASHBOARD_PANELS_UNSAVED_ID) - .map((dashboardId) => { - return (savedDashboards.get(dashboardId) as Promise).catch( - () => dashboardId - ); - }); - Promise.all(dashPromises).then((dashboards: Array) => { + const existingDashboardsWithUnsavedChanges = unsavedDashboardIds.filter( + (id) => id !== DASHBOARD_PANELS_UNSAVED_ID + ); + findDashboards.findByIds(existingDashboardsWithUnsavedChanges).then((results) => { const dashboardMap = {}; if (canceled) { return; } let hasError = false; - const newItems = dashboards.reduce((map, dashboard) => { - if (typeof dashboard === 'string') { + const newItems = results.reduce((map, result) => { + if (result.status === 'error') { hasError = true; - dashboardSessionStorage.clearState(dashboard); + dashboardSessionStorage.clearState(result.id); return map; } return { ...map, - [dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard, + [result.id || DASHBOARD_PANELS_UNSAVED_ID]: result.attributes, }; }, dashboardMap); if (hasError) { @@ -179,7 +172,13 @@ export const DashboardUnsavedListing = ({ return () => { canceled = true; }; - }, [savedDashboards, dashboardSessionStorage, refreshUnsavedDashboards, unsavedDashboardIds]); + }, [ + refreshUnsavedDashboards, + dashboardSessionStorage, + unsavedDashboardIds, + savedObjectsClient, + findDashboards, + ]); return unsavedDashboardIds.length === 0 ? null : ( <> diff --git a/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts b/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts index f28d095c9b9b4..669bde5c3eb65 100644 --- a/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts +++ b/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts @@ -7,11 +7,14 @@ */ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { RefreshInterval } from '@kbn/data-plugin/common'; import type { Filter, Query, TimeRange } from '@kbn/es-query'; -import type { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types'; +import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; + +import { DashboardPanelMap } from '../../../common'; +import type { DashboardOptions, DashboardState } from '../../types'; export const dashboardStateSlice = createSlice({ name: 'dashboardState', @@ -33,11 +36,15 @@ export const dashboardStateSlice = createSlice({ description: string; tags?: string[]; timeRestore: boolean; + timeRange?: TimeRange; + refreshInterval?: RefreshInterval; }> ) => { state.title = action.payload.title; state.description = action.payload.description; state.timeRestore = action.payload.timeRestore; + state.timeRange = action.payload.timeRange; + state.refreshInterval = action.payload.refreshInterval; if (action.payload.tags) { state.tags = action.payload.tags; } diff --git a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts deleted file mode 100644 index 69da1dbbe56a2..0000000000000 --- a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { DashboardSavedObject } from '../../saved_dashboards'; - -export function getSavedDashboardMock( - config?: Partial -): DashboardSavedObject { - const searchSource = dataPluginMock.createStartContract(); - - return { - id: '123', - title: 'my dashboard', - panelsJSON: '[]', - searchSource: searchSource.search.searchSource.create(), - copyOnSave: false, - timeRestore: false, - timeTo: 'now', - timeFrom: 'now-15m', - optionsJSON: '', - lastSavedTitle: '', - destroy: () => {}, - save: () => { - return Promise.resolve('123'); - }, - getQuery: () => ({ query: '', language: 'kuery' }), - getFilters: () => [], - ...config, - } as DashboardSavedObject; -} diff --git a/src/plugins/dashboard/public/application/test_helpers/index.ts b/src/plugins/dashboard/public/application/test_helpers/index.ts index 7c8ae86074a46..c4d149e8c10b2 100644 --- a/src/plugins/dashboard/public/application/test_helpers/index.ts +++ b/src/plugins/dashboard/public/application/test_helpers/index.ts @@ -7,6 +7,4 @@ */ export { getSampleDashboardInput, getSampleDashboardPanel } from './get_sample_dashboard_input'; -export { getSavedDashboardMock } from './get_saved_dashboard_mock'; -export { makeDefaultServices } from './make_default_services'; export { setupIntersectionObserverMock } from './intersection_observer_mock'; diff --git a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts deleted file mode 100644 index 3ffe9dc3b70e9..0000000000000 --- a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - SavedObjectLoader, - type SavedObjectLoaderFindOptions, -} from '../../services/saved_object_loader'; -import { DashboardAppServices } from '../../types'; -import { getSavedDashboardMock } from './get_saved_dashboard_mock'; - -// TODO: Remove as part of https://github.com/elastic/kibana/pull/138774 -export function makeDefaultServices(): DashboardAppServices { - const savedDashboards = {} as SavedObjectLoader; - savedDashboards.find = (search: string, sizeOrOptions: number | SavedObjectLoaderFindOptions) => { - const size = typeof sizeOrOptions === 'number' ? sizeOrOptions : sizeOrOptions.size ?? 10; - const hits = []; - for (let i = 0; i < size; i++) { - hits.push({ - id: `dashboard${i}`, - title: `dashboard${i} - ${search} - title`, - description: `dashboard${i} desc`, - references: [], - timeRestore: true, - type: '', - url: '', - updatedAt: '', - panelsJSON: '', - lastSavedTitle: '', - }); - } - return Promise.resolve({ - total: size, - hits, - }); - }; - savedDashboards.get = jest - .fn() - .mockImplementation((id?: string) => Promise.resolve(getSavedDashboardMock({ id }))); - - return { - savedDashboards, - }; -} diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 86fd56c2ec6a0..97630c48dc784 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -6,46 +6,32 @@ * Side Public License, v 1. */ -import { METRIC_TYPE } from '@kbn/analytics'; -import { Required } from '@kbn/utility-types'; -import { EuiHorizontalRule } from '@elastic/eui'; import UseUnmount from 'react-use/lib/useUnmount'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { OverlayRef } from '@kbn/core/public'; -import type { TopNavMenuProps } from '@kbn/navigation-plugin/public'; -import type { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; import { - AddFromLibraryButton, + withSuspense, LazyLabsFlyout, - PrimaryActionButton, + SolutionToolbar, QuickButtonGroup, QuickButtonProps, - SolutionToolbar, - withSuspense, + PrimaryActionButton, + AddFromLibraryButton, } from '@kbn/presentation-util-plugin/public'; -import type { SavedQuery } from '@kbn/data-plugin/common'; -import { isErrorEmbeddable, openAddPanelFlyout, ViewMode } from '@kbn/embeddable-plugin/public'; import { - getSavedObjectFinder, - type SaveResult, showSaveModal, + type SaveResult, + getSavedObjectFinder, } from '@kbn/saved-objects-plugin/public'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { Required } from '@kbn/utility-types'; +import { EuiHorizontalRule } from '@elastic/eui'; +import type { OverlayRef } from '@kbn/core/public'; +import type { SavedQuery } from '@kbn/data-plugin/common'; +import type { TopNavMenuProps } from '@kbn/navigation-plugin/public'; +import type { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; +import { isErrorEmbeddable, openAddPanelFlyout, ViewMode } from '@kbn/embeddable-plugin/public'; -import { saveDashboard } from '../lib'; -import { TopNavIds } from './top_nav_ids'; -import { EditorMenu } from './editor_menu'; -import { UI_SETTINGS } from '../../../common'; -import { DashboardSaveModal } from './save_modal'; -import { showCloneModal } from './show_clone_modal'; -import { ShowShareModal } from './show_share_modal'; -import { getTopNavConfig } from './get_top_nav_config'; -import { showOptionsPopover } from './show_options_popover'; -import { DashboardConstants } from '../../dashboard_constants'; -import { confirmDiscardUnsavedChanges } from '../listing/confirm_overlays'; -import type { DashboardAppState, DashboardSaveOptions, NavAction } from '../../types'; -import type { DashboardEmbedSettings, DashboardRedirect } from '../../types'; -import { getCreateVisualizationButtonTitle, unsavedChangesBadge } from '../../dashboard_strings'; import { setFullScreenMode, setHidePanelTitles, @@ -58,8 +44,21 @@ import { useDashboardDispatch, useDashboardSelector, } from '../state'; +import { TopNavIds } from './top_nav_ids'; +import { EditorMenu } from './editor_menu'; +import { UI_SETTINGS } from '../../../common'; +import { DashboardSaveModal } from './save_modal'; +import { showCloneModal } from './show_clone_modal'; +import { ShowShareModal } from './show_share_modal'; +import { getTopNavConfig } from './get_top_nav_config'; +import { showOptionsPopover } from './show_options_popover'; import { pluginServices } from '../../services/plugin_services'; +import { DashboardEmbedSettings, DashboardRedirect, DashboardState } from '../../types'; +import { confirmDiscardUnsavedChanges } from '../listing/confirm_overlays'; import { useDashboardMountContext } from '../hooks/dashboard_mount_context'; +import { DashboardConstants, getFullEditPath } from '../../dashboard_constants'; +import { DashboardAppState, DashboardSaveOptions, NavAction } from '../../types'; +import { getCreateVisualizationButtonTitle, unsavedChangesBadge } from '../../dashboard_strings'; export interface DashboardTopNavState { chromeIsVisible: boolean; @@ -70,18 +69,13 @@ export interface DashboardTopNavState { type CompleteDashboardAppState = Required< DashboardAppState, - 'getLatestDashboardState' | 'dashboardContainer' | 'savedDashboard' | 'applyFilters' + 'getLatestDashboardState' | 'dashboardContainer' >; export const isCompleteDashboardAppState = ( state: DashboardAppState ): state is CompleteDashboardAppState => { - return ( - Boolean(state.getLatestDashboardState) && - Boolean(state.dashboardContainer) && - Boolean(state.savedDashboard) && - Boolean(state.applyFilters) - ); + return Boolean(state.getLatestDashboardState) && Boolean(state.dashboardContainer); }; export interface DashboardTopNavProps { @@ -101,24 +95,28 @@ export function DashboardTopNav({ }: DashboardTopNavProps) { const { setHeaderActionMenu } = useDashboardMountContext(); const { + dashboardSavedObject: { + checkForDuplicateDashboardTitle, + saveDashboardStateToSavedObject, + savedObjectsClient, + }, chrome: { getIsVisible$: getChromeIsVisible$, recentlyAccessed: chromeRecentlyAccessed, docTitle, }, coreContext: { i18nContext }, - dashboardCapabilities, + share, + overlays, + notifications, + usageCollection, data: { query, search }, - embeddable: { getEmbeddableFactory, getEmbeddableFactories, getStateTransfer }, - initializerContext: { allowByValueEmbeddables }, navigation: { TopNavMenu }, - notifications, - overlays, - savedObjects, - savedObjectsTagging: { hasTagDecoration, hasApi }, settings: { uiSettings, theme }, - share, - usageCollection, + initializerContext: { allowByValueEmbeddables }, + dashboardCapabilities: { showWriteControls, saveQuery: showSaveQuery }, + savedObjectsTagging: { hasApi: hasSavedObjectsTagging }, + embeddable: { getEmbeddableFactory, getEmbeddableFactories, getStateTransfer }, visualizations: { get: getVisualization, getAliases: getVisTypeAliases }, } = pluginServices.getServices(); @@ -144,24 +142,18 @@ export function DashboardTopNav({ const visibleSubscription = getChromeIsVisible$().subscribe((chromeIsVisible) => { setState((s) => ({ ...s, chromeIsVisible })); }); - const { id, title, getFullEditPath } = dashboardAppState.savedDashboard; - if (id && title) { + const { savedObjectId, title, viewMode } = dashboardState; + if (savedObjectId && title) { chromeRecentlyAccessed.add( - getFullEditPath(dashboardState.viewMode === ViewMode.EDIT), + getFullEditPath(savedObjectId, viewMode === ViewMode.EDIT), title, - id + savedObjectId ); } return () => { visibleSubscription.unsubscribe(); }; - }, [ - getChromeIsVisible$, - chromeRecentlyAccessed, - allowByValueEmbeddables, - dashboardState.viewMode, - dashboardAppState.savedDashboard, - ]); + }, [allowByValueEmbeddables, chromeRecentlyAccessed, dashboardState, getChromeIsVisible$]); const addFromLibrary = useCallback(() => { if (!isErrorEmbeddable(dashboardAppState.dashboardContainer)) { @@ -173,7 +165,7 @@ export function DashboardTopNav({ getFactory: getEmbeddableFactory, notifications, overlays, - SavedObjectFinder: getSavedObjectFinder(savedObjects, uiSettings), + SavedObjectFinder: getSavedObjectFinder({ client: savedObjectsClient }, uiSettings), reportUiCounter: usageCollection.reportUiCounter, theme, }), @@ -181,14 +173,14 @@ export function DashboardTopNav({ } }, [ dashboardAppState.dashboardContainer, + usageCollection.reportUiCounter, getEmbeddableFactories, getEmbeddableFactory, + savedObjectsClient, notifications, - savedObjects, overlays, - theme, uiSettings, - usageCollection, + theme, ]); const createNewVisType = useCallback( @@ -258,48 +250,71 @@ export function DashboardTopNav({ onTitleDuplicate, isTitleDuplicateConfirmed, }: DashboardSaveOptions): Promise => { + const { + timefilter: { timefilter }, + } = query; + const saveOptions = { confirmOverwrite: false, isTitleDuplicateConfirmed, onTitleDuplicate, + saveAsCopy: newCopyOnSave, }; - const stateFromSaveModal = { + const stateFromSaveModal: Pick< + DashboardState, + 'title' | 'description' | 'timeRestore' | 'timeRange' | 'refreshInterval' | 'tags' + > = { title: newTitle, + tags: [] as string[], description: newDescription, timeRestore: newTimeRestore, - tags: [] as string[], + timeRange: newTimeRestore ? timefilter.getTime() : undefined, + refreshInterval: newTimeRestore ? timefilter.getRefreshInterval() : undefined, }; - if (hasApi && newTags) { - // remove `hasAPI` once the savedObjectsTagging service is optional + if (hasSavedObjectsTagging && newTags) { + // remove `hasSavedObjectsTagging` once the savedObjectsTagging service is optional stateFromSaveModal.tags = newTags; } - dashboardAppState.savedDashboard.copyOnSave = newCopyOnSave; - const saveResult = await saveDashboard({ + if ( + !(await checkForDuplicateDashboardTitle({ + title: newTitle, + onTitleDuplicate, + lastSavedTitle: currentState.title, + copyOnSave: newCopyOnSave, + isTitleDuplicateConfirmed, + })) + ) { + // do not save if title is duplicate and is unconfirmed + return {}; + } + + const saveResult = await saveDashboardStateToSavedObject({ redirectTo, saveOptions, - savedDashboard: dashboardAppState.savedDashboard, currentState: { ...currentState, ...stateFromSaveModal }, }); if (saveResult.id && !saveResult.redirected) { dispatchDashboardStateChange(setStateFromSaveModal(stateFromSaveModal)); - dashboardAppState.updateLastSavedState?.(); - docTitle.change(stateFromSaveModal.title); + setTimeout(() => { + /** + * set timeout so dashboard state subject can update with the new title before updating the last saved state. + * TODO: Remove this timeout once the last saved state is also handled in Redux. + **/ + dashboardAppState.updateLastSavedState?.(); + docTitle.change(stateFromSaveModal.title); + }, 1); } - return saveResult.id ? { id: saveResult.id } : { error: saveResult.error }; + return saveResult.id ? { id: saveResult.id } : { error: new Error(saveResult.error) }; }; - const lastDashboardId = dashboardAppState.savedDashboard.id; - const savedTags = hasTagDecoration?.(dashboardAppState.savedDashboard) - ? dashboardAppState.savedDashboard.getTags() - : []; - const currentTagsSet = new Set([...savedTags, ...currentState.tags]); + const lastDashboardId = currentState.savedObjectId; const dashboardSaveModal = ( {}} - tags={Array.from(currentTagsSet)} + tags={currentState.tags} title={currentState.title} timeRestore={currentState.timeRestore} description={currentState.description} @@ -309,24 +324,25 @@ export function DashboardTopNav({ closeAllFlyouts(); showSaveModal(dashboardSaveModal, i18nContext); }, [ + saveDashboardStateToSavedObject, + checkForDuplicateDashboardTitle, dispatchDashboardStateChange, - hasApi, - hasTagDecoration, + hasSavedObjectsTagging, dashboardAppState, - i18nContext, - docTitle, closeAllFlyouts, + i18nContext, redirectTo, + docTitle, + query, ]); const runQuickSave = useCallback(async () => { setState((s) => ({ ...s, isSaveInProgress: true })); const currentState = dashboardAppState.getLatestDashboardState(); - const saveResult = await saveDashboard({ + const saveResult = await saveDashboardStateToSavedObject({ redirectTo, currentState, saveOptions: {}, - savedDashboard: dashboardAppState.savedDashboard, }); if (saveResult.id && !saveResult.redirected) { dashboardAppState.updateLastSavedState?.(); @@ -336,7 +352,7 @@ export function DashboardTopNav({ if (!mounted) return; setState((s) => ({ ...s, isSaveInProgress: false })); }, DashboardConstants.CHANGE_CHECK_DEBOUNCE); - }, [dashboardAppState, redirectTo, mounted]); + }, [dashboardAppState, saveDashboardStateToSavedObject, redirectTo, mounted]); const runClone = useCallback(() => { const currentState = dashboardAppState.getLatestDashboardState(); @@ -345,22 +361,33 @@ export function DashboardTopNav({ isTitleDuplicateConfirmed: boolean, onTitleDuplicate: () => void ) => { - dashboardAppState.savedDashboard.copyOnSave = true; - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - }; - const saveResult = await saveDashboard({ + if ( + !(await checkForDuplicateDashboardTitle({ + title: newTitle, + onTitleDuplicate, + lastSavedTitle: currentState.title, + copyOnSave: true, + isTitleDuplicateConfirmed, + })) + ) { + // do not clone if title is duplicate and is unconfirmed + return {}; + } + + const saveResult = await saveDashboardStateToSavedObject({ redirectTo, - saveOptions, - savedDashboard: dashboardAppState.savedDashboard, + saveOptions: { saveAsCopy: true }, currentState: { ...currentState, title: newTitle }, }); return saveResult.id ? { id: saveResult.id } : { error: saveResult.error }; }; showCloneModal({ onClone, title: currentState.title }); - }, [dashboardAppState, redirectTo]); + }, [ + checkForDuplicateDashboardTitle, + saveDashboardStateToSavedObject, + dashboardAppState, + redirectTo, + ]); const showOptions = useCallback( (anchorElement: HTMLElement) => { @@ -394,7 +421,6 @@ export function DashboardTopNav({ ShowShareModal({ anchorElement, currentDashboardState: currentState, - savedDashboard: dashboardAppState.savedDashboard, isDirty: Boolean(dashboardAppState.hasUnsavedChanges), }); }, @@ -442,7 +468,7 @@ export function DashboardTopNav({ }); const getNavBarProps = (): TopNavMenuProps => { - const { hasUnsavedChanges, savedDashboard } = dashboardAppState; + const { hasUnsavedChanges } = dashboardAppState; const shouldShowNavBarComponent = (forceShow: boolean): boolean => (forceShow || state.chromeIsVisible) && !dashboardState.fullScreenMode; @@ -464,11 +490,11 @@ export function DashboardTopNav({ dashboardAppState.getLatestDashboardState().viewMode, dashboardTopNavActions, { - showWriteControls: dashboardCapabilities.showWriteControls, - isDirty: Boolean(dashboardAppState.hasUnsavedChanges), - isSaveInProgress: state.isSaveInProgress, - isNewDashboard: !savedDashboard.id, isLabsEnabled, + showWriteControls, + isSaveInProgress: state.isSaveInProgress, + isNewDashboard: !dashboardState.savedObjectId, + isDirty: Boolean(dashboardAppState.hasUnsavedChanges), } ); @@ -485,22 +511,22 @@ export function DashboardTopNav({ return { badges, - appName: 'dashboard', - config: showTopNavMenu ? topNav : undefined, - className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, screenTitle, - showSearchBar, showQueryBar, + showSearchBar, + showFilterBar, + showSaveQuery, showQueryInput, showDatePicker, - showFilterBar, - setMenuMountPoint: embedSettings ? undefined : setHeaderActionMenu, - indexPatterns: dashboardAppState.dataViews, - showSaveQuery: dashboardCapabilities.saveQuery, + appName: 'dashboard', useDefaultBehaviors: true, + visible: printMode !== true, savedQuery: state.savedQuery, savedQueryId: dashboardState.savedQuery, - visible: printMode !== true, + indexPatterns: dashboardAppState.dataViews, + config: showTopNavMenu ? topNav : undefined, + setMenuMountPoint: embedSettings ? undefined : setHeaderActionMenu, + className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, onQuerySubmit: (_payload, isUpdate) => { if (isUpdate === false) { dashboardAppState.$triggerDashboardRefresh.next({ force: true }); diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx index ad9d85926a94b..7e76ef1789a38 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx @@ -10,10 +10,9 @@ import { Capabilities } from '@kbn/core/public'; import { DashboardState } from '../../types'; import { DashboardAppLocatorParams } from '../..'; +import { pluginServices } from '../../services/plugin_services'; import { stateToRawDashboardState } from '../lib/convert_dashboard_state'; -import { getSavedDashboardMock } from '../test_helpers'; import { showPublicUrlSwitch, ShowShareModal, ShowShareModalProps } from './show_share_modal'; -import { pluginServices } from '../../services/plugin_services'; describe('showPublicUrlSwitch', () => { test('returns false if "dashboard" app is not available', () => { @@ -75,9 +74,8 @@ describe('ShowShareModal', () => { .mockReturnValue(unsavedState); return { isDirty: true, - savedDashboard: getSavedDashboardMock(), anchorElement: document.createElement('div'), - currentDashboardState: { panels: {} } as unknown as DashboardState, + currentDashboardState: { panels: {} } as DashboardState, }; }; @@ -139,7 +137,7 @@ describe('ShowShareModal', () => { }); unsavedStateKeys.forEach((key) => { expect(shareLocatorParams[key]).toStrictEqual( - (rawDashboardState as Partial)[key] + (rawDashboardState as unknown as Partial)[key] ); }); }); diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx index f93061f56d6ec..0f7d119427dd7 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx @@ -9,28 +9,26 @@ import moment from 'moment'; import React, { ReactElement, useState } from 'react'; -import { EuiCheckboxGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { EuiCheckboxGroup } from '@elastic/eui'; import type { Capabilities } from '@kbn/core/public'; -import type { SerializableControlGroupInput } from '@kbn/controls-plugin/common'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public'; +import type { SerializableControlGroupInput } from '@kbn/controls-plugin/common'; -import type { DashboardSavedObject } from '../..'; -import { shareModalStrings } from '../../dashboard_strings'; -import { DashboardAppLocatorParams, DASHBOARD_APP_LOCATOR } from '../../locator'; import type { DashboardState } from '../../types'; import { dashboardUrlParams } from '../dashboard_router'; -import { stateToRawDashboardState } from '../lib/convert_dashboard_state'; -import { convertPanelMapToSavedPanels } from '../lib/convert_dashboard_panels'; +import { shareModalStrings } from '../../dashboard_strings'; +import { convertPanelMapToSavedPanels } from '../../../common'; import { pluginServices } from '../../services/plugin_services'; +import { stateToRawDashboardState } from '../lib/convert_dashboard_state'; +import { DashboardAppLocatorParams, DASHBOARD_APP_LOCATOR } from '../../locator'; const showFilterBarId = 'showFilterBar'; export interface ShowShareModalProps { isDirty: boolean; anchorElement: HTMLElement; - savedDashboard: DashboardSavedObject; currentDashboardState: DashboardState; } @@ -45,7 +43,6 @@ export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => export function ShowShareModal({ isDirty, anchorElement, - savedDashboard, currentDashboardState, }: ShowShareModalProps) { const { @@ -59,6 +56,7 @@ export function ShowShareModal({ }, }, share: { toggleShareContextMenu }, + initializerContext: { kibanaVersion }, } = pluginServices.getServices(); if (!toggleShareContextMenu) return; // TODO: Make this logic cleaner once share is an optional service @@ -124,7 +122,9 @@ export function ShowShareModal({ DashboardAppLocatorParams, 'options' | 'query' | 'savedQuery' | 'filters' | 'panels' | 'controlGroupInput' > = {}; - const unsavedDashboardState = dashboardSessionStorage.getState(savedDashboard.id); + const { savedObjectId, title } = currentDashboardState; + const unsavedDashboardState = dashboardSessionStorage.getState(savedObjectId); + if (unsavedDashboardState) { unsavedStateForLocator = { query: unsavedDashboardState.query, @@ -133,13 +133,16 @@ export function ShowShareModal({ savedQuery: unsavedDashboardState.savedQuery, controlGroupInput: unsavedDashboardState.controlGroupInput as SerializableControlGroupInput, panels: unsavedDashboardState.panels - ? convertPanelMapToSavedPanels(unsavedDashboardState.panels) + ? (convertPanelMapToSavedPanels( + unsavedDashboardState.panels, + kibanaVersion + ) as DashboardAppLocatorParams['panels']) : undefined, }; } const locatorParams: DashboardAppLocatorParams = { - dashboardId: savedDashboard.id, + dashboardId: savedObjectId, preserveSavedFilters: true, refreshInterval: undefined, // We don't share refresh interval externally viewMode: ViewMode.VIEW, // For share locators we always load the dashboard in view mode @@ -161,11 +164,11 @@ export function ShowShareModal({ { useHash: false, storeInHashQuery: true }, unhashUrl(window.location.href) ), - objectId: savedDashboard.id, + objectId: savedObjectId, objectType: 'dashboard', sharingData: { title: - savedDashboard.title || + title || i18n.translate('dashboard.share.defaultDashboardTitle', { defaultMessage: 'Dashboard [{date}]', values: { date: moment().toISOString(true) }, diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index badc14ddaee66..9bbe97681032d 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -6,9 +6,18 @@ * Side Public License, v 1. */ +import { ViewMode } from '@kbn/embeddable-plugin/common'; +import type { DashboardState } from './types'; + export const DASHBOARD_STATE_STORAGE_KEY = '_a'; export const GLOBAL_STATE_STORAGE_KEY = '_g'; +export const DASHBOARD_GRID_COLUMN_COUNT = 48; +export const DASHBOARD_GRID_HEIGHT = 20; +export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; +export const DEFAULT_PANEL_HEIGHT = 15; +export const DASHBOARD_CONTAINER_TYPE = 'dashboard'; + export const DashboardConstants = { LANDING_PAGE_PATH: '/list', CREATE_NEW_DASHBOARD_URL: '/create', @@ -18,11 +27,37 @@ export const DashboardConstants = { ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', DASHBOARDS_ID: 'dashboards', DASHBOARD_ID: 'dashboard', + DASHBOARD_SAVED_OBJECT_TYPE: 'dashboard', SEARCH_SESSION_ID: 'searchSessionId', CHANGE_CHECK_DEBOUNCE: 100, CHANGE_APPLY_DEBOUNCE: 50, }; +export const defaultDashboardState: DashboardState = { + viewMode: ViewMode.EDIT, // new dashboards start in edit mode. + fullScreenMode: false, + timeRestore: false, + query: { query: '', language: 'kuery' }, + description: '', + filters: [], + panels: {}, + title: '', + tags: [], + options: { + useMargins: true, + syncColors: false, + syncTooltips: false, + hidePanelTitles: false, + }, +}; + +export const getFullPath = (aliasId?: string, id?: string) => + `/app/dashboards#${createDashboardEditUrl(aliasId || id)}`; + +export const getFullEditPath = (id?: string, editMode?: boolean) => { + return `/app/dashboards#${createDashboardEditUrl(id, editMode)}`; +}; + export function createDashboardEditUrl(id?: string, editMode?: boolean) { if (!id) { return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`; diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 5679ac28f838b..6474c7dc2bba6 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -382,7 +382,7 @@ export const panelStorageErrorStrings = { }), }; -export const dashboardLoadingErrorStrings = { +export const dashboardSavedObjectErrorStrings = { getDashboardLoadError: (message: string) => i18n.translate('dashboard.loadingError.errorMessage', { defaultMessage: 'Error encountered while loading saved dashboard: {message}', @@ -393,6 +393,14 @@ export const dashboardLoadingErrorStrings = { defaultMessage: 'Unable to load dashboard: {message}', values: { message }, }), + getErrorDeletingDashboardToast: () => + i18n.translate('dashboard.deleteError.toastDescription', { + defaultMessage: 'Error encountered while deleting dashboard', + }), + getPanelTooOldError: () => + i18n.translate('dashboard.loadURLError.PanelTooOld', { + defaultMessage: 'Cannot load panels from a URL created in a version older than 7.3', + }), }; /* @@ -432,7 +440,7 @@ export const emptyScreenStrings = { /* Dashboard Listing Page */ -export const dashboardListingTable = { +export const dashboardListingTableStrings = { getEntityName: () => i18n.translate('dashboard.listing.table.entityName', { defaultMessage: 'dashboard', @@ -450,8 +458,8 @@ export const dashboardUnsavedListingStrings = { defaultMessage: 'You have unsaved changes in the following {dash}:', values: { dash: plural - ? dashboardListingTable.getEntityNamePlural() - : dashboardListingTable.getEntityName(), + ? dashboardListingTableStrings.getEntityNamePlural() + : dashboardListingTableStrings.getEntityName(), }, }), getLoadingTitle: () => diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 7cb54db209c1d..598940bbd666c 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -9,11 +9,7 @@ import { PluginInitializerContext } from '@kbn/core/public'; import { DashboardPlugin } from './plugin'; -export { - DashboardContainer, - DashboardContainerFactoryDefinition, - DASHBOARD_CONTAINER_TYPE, -} from './application'; +export { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; export type { DashboardSetup, DashboardStart, DashboardFeatureFlagConfig } from './plugin'; @@ -23,7 +19,6 @@ export { cleanEmptyKeys, } from './locator'; -export type { DashboardSavedObject } from './saved_dashboards'; export type { SavedDashboardPanel, DashboardContainerInput } from './types'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts index e3cd3f159f738..a66015afcb00b 100644 --- a/src/plugins/dashboard/public/locator.ts +++ b/src/plugins/dashboard/public/locator.ts @@ -94,7 +94,7 @@ export type DashboardAppLocatorParams = { /** * List of dashboard panels */ - panels?: SavedDashboardPanel[]; + panels?: Array; // used SerializableRecord here to force the GridData type to be read as serializable /** * Saved query ID diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 9d1df9d2acb12..a0d35e2c7be48 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -9,73 +9,56 @@ import { BehaviorSubject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; -import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; -import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { App, Plugin, - type CoreSetup, - type CoreStart, AppUpdater, ScopedHistory, + type CoreSetup, + type CoreStart, AppMountParameters, DEFAULT_APP_CATEGORIES, PluginInitializerContext, SavedObjectsClientContract, } from '@kbn/core/public'; -import { - CONTEXT_MENU_TRIGGER, - EmbeddableSetup, - EmbeddableStart, - PANEL_BADGE_TRIGGER, - PANEL_NOTIFICATION_TRIGGER, -} from '@kbn/embeddable-plugin/public'; import type { ScreenshotModePluginSetup, ScreenshotModePluginStart, } from '@kbn/screenshot-mode-plugin/public'; -import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; -import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public'; -import type { VisualizationsStart } from '@kbn/visualizations-plugin/public'; -import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { UsageCollectionSetup, UsageCollectionStart, } from '@kbn/usage-collection-plugin/public'; +import { APP_WRAPPER_CLASS } from '@kbn/core/public'; +import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; +import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public'; + +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import type { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; +import type { VisualizationsStart } from '@kbn/visualizations-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { getSavedObjectFinder, type SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { - ClonePanelAction, - createDashboardContainerByValueRenderer, - DASHBOARD_CONTAINER_TYPE, - DashboardContainerFactory, + type DashboardContainerFactory, DashboardContainerFactoryDefinition, - ExpandPanelAction, - ReplacePanelAction, - UnlinkFromLibraryAction, - AddToLibraryAction, - LibraryNotificationAction, - CopyToDashboardAction, -} from './application'; -import { SavedObjectLoader } from './services/saved_object_loader'; -import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator'; -import { createSavedDashboardLoader } from './saved_dashboards'; -import { DashboardConstants } from './dashboard_constants'; -import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; -import { ExportCSVAction } from './application/actions/export_csv_action'; -import { dashboardFeatureCatalog } from './dashboard_strings'; -import { FiltersNotificationBadge } from './application/actions/filters_notification_badge'; + createDashboardContainerByValueRenderer, +} from './application/embeddable'; import type { DashboardMountContextProps } from './types'; +import { dashboardFeatureCatalog } from './dashboard_strings'; +import { type DashboardAppLocator, DashboardAppLocatorDefinition } from './locator'; +import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; +import { DashboardConstants, DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; @@ -118,7 +101,6 @@ export interface DashboardSetup { } export interface DashboardStart { - getSavedDashboardLoader: () => SavedObjectLoader; getDashboardContainerByValueRenderer: () => ReturnType< typeof createDashboardContainerByValueRenderer >; @@ -159,9 +141,14 @@ export class DashboardPlugin new DashboardAppLocatorDefinition({ useHashedUrl: core.uiSettings.get('state:storeInSessionStorage'), getDashboardFilterFields: async (dashboardId: string) => { - const [, , selfStart] = await core.getStartServices(); - const dashboard = await selfStart.getSavedDashboardLoader().get(dashboardId); - return dashboard?.searchSource?.getField('filter') ?? []; + const { pluginServices } = await import('./services/plugin_services'); + const { + dashboardSavedObject: { loadDashboardStateFromSavedObject }, + } = pluginServices.getServices(); + return ( + (await loadDashboardStateFromSavedObject({ id: dashboardId })).dashboardState + ?.filters ?? [] + ); }, }) ); @@ -305,58 +292,16 @@ export class DashboardPlugin } public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart { - const { uiSettings } = core; - const { uiActions, share, presentationUtil } = plugins; - this.startDashboardKibanaServices(core, plugins, this.initializerContext).then(() => { - const clonePanelAction = new ClonePanelAction(core.savedObjects); - uiActions.registerAction(clonePanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); - - const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, uiSettings); - const changeViewAction = new ReplacePanelAction(SavedObjectFinder); - uiActions.registerAction(changeViewAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); - - const panelLevelFiltersNotification = new FiltersNotificationBadge(); - uiActions.registerAction(panelLevelFiltersNotification); - uiActions.attachAction(PANEL_BADGE_TRIGGER, panelLevelFiltersNotification.id); - - if (share) { - const ExportCSVPlugin = new ExportCSVAction(); - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, ExportCSVPlugin); - } - - if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) { - const addToLibraryAction = new AddToLibraryAction(); - uiActions.registerAction(addToLibraryAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id); - - const unlinkFromLibraryAction = new UnlinkFromLibraryAction(); - uiActions.registerAction(unlinkFromLibraryAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); - - const libraryNotificationAction = new LibraryNotificationAction(unlinkFromLibraryAction); - uiActions.registerAction(libraryNotificationAction); - uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id); - - const copyToDashboardAction = new CopyToDashboardAction(presentationUtil.ContextProvider); - uiActions.registerAction(copyToDashboardAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id); - } - }); - - const expandPanelAction = new ExpandPanelAction(); // this action does't rely on any services - uiActions.registerAction(expandPanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); - - const savedDashboardLoader = createSavedDashboardLoader({ - savedObjectsClient: core.savedObjects.client, - savedObjects: plugins.savedObjects, - embeddableStart: plugins.embeddable, + this.startDashboardKibanaServices(core, plugins, this.initializerContext).then(async () => { + const { buildAllDashboardActions } = await import('./application/actions'); + buildAllDashboardActions({ + core, + plugins, + allowByValueEmbeddables: this.dashboardFeatureFlagConfig?.allowByValueEmbeddables, + }); }); return { - getSavedDashboardLoader: () => savedDashboardLoader, getDashboardContainerByValueRenderer: () => { const dashboardContainerFactory = plugins.embeddable.getEmbeddableFactory(DASHBOARD_CONTAINER_TYPE); diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts deleted file mode 100644 index c23b6eb2a87e0..0000000000000 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { assign, cloneDeep } from 'lodash'; -import { SavedObjectsClientContract } from '@kbn/core/public'; -import type { ResolvedSimpleSavedObject } from '@kbn/core/public'; -import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types'; -import { RawControlGroupAttributes } from '@kbn/controls-plugin/common'; -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import { ISearchSource } from '@kbn/data-plugin/common'; -import { RefreshInterval } from '@kbn/data-plugin/public'; -import { Query, Filter } from '@kbn/es-query'; -import type { SavedObject, SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; - -import { createDashboardEditUrl } from '../dashboard_constants'; -import { extractReferences, injectReferences } from '../../common/saved_dashboard_references'; - -import { DashboardOptions } from '../types'; - -export interface DashboardSavedObject extends SavedObject { - id?: string; - timeRestore: boolean; - timeTo?: string; - timeFrom?: string; - description?: string; - panelsJSON: string; - optionsJSON?: string; - // TODO: write a migration to rid of this, it's only around for bwc. - uiStateJSON?: string; - lastSavedTitle: string; - refreshInterval?: RefreshInterval; - searchSource: ISearchSource; - getQuery(): Query; - getFilters(): Filter[]; - getFullEditPath: (editMode?: boolean) => string; - outcome?: ResolvedSimpleSavedObject['outcome']; - aliasId?: ResolvedSimpleSavedObject['alias_target_id']; - aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose']; - - controlGroupInput?: Omit; -} - -const defaults = { - title: '', - hits: 0, - description: '', - panelsJSON: '[]', - optionsJSON: JSON.stringify({ - // for BWC reasons we can't default dashboards that already exist without this setting to true. - useMargins: true, - syncColors: false, - syncTooltips: false, - hidePanelTitles: false, - } as DashboardOptions), - version: 1, - timeRestore: false, - timeTo: undefined, - timeFrom: undefined, - refreshInterval: undefined, -}; - -// Used only by the savedDashboards service, usually no reason to change this -export function createSavedDashboardClass( - savedObjectStart: SavedObjectsStart, - embeddableStart: EmbeddableStart, - savedObjectsClient: SavedObjectsClientContract -): new (id: string) => DashboardSavedObject { - class SavedDashboard extends savedObjectStart.SavedObjectClass { - // save these objects with the 'dashboard' type - public static type = 'dashboard'; - - // if type:dashboard has no mapping, we push this mapping into ES - public static mapping = { - title: 'text', - hits: 'integer', - description: 'text', - panelsJSON: 'text', - optionsJSON: 'text', - version: 'integer', - timeRestore: 'boolean', - timeTo: 'keyword', - timeFrom: 'keyword', - refreshInterval: { - type: 'object', - properties: { - display: { type: 'keyword' }, - pause: { type: 'boolean' }, - section: { type: 'integer' }, - value: { type: 'integer' }, - }, - }, - controlGroupInput: { - type: 'object', - properties: { - controlStyle: { type: 'keyword' }, - panelsJSON: { type: 'text' }, - }, - }, - }; - public static fieldOrder = ['title', 'description']; - public static searchSource = true; - public showInRecentlyAccessed = true; - - public outcome?: ResolvedSimpleSavedObject['outcome']; - public aliasId?: ResolvedSimpleSavedObject['alias_target_id']; - public aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose']; - - constructor(arg: { id: string; useResolve: boolean } | string) { - super({ - type: SavedDashboard.type, - mapping: SavedDashboard.mapping, - searchSource: SavedDashboard.searchSource, - extractReferences: (opts: { - attributes: SavedObjectAttributes; - references: SavedObjectReference[]; - }) => extractReferences(opts, { embeddablePersistableStateService: embeddableStart }), - injectReferences: (so: DashboardSavedObject, references: SavedObjectReference[]) => { - const newAttributes = injectReferences( - { attributes: so._serialize().attributes, references }, - { - embeddablePersistableStateService: embeddableStart, - } - ); - Object.assign(so, newAttributes); - }, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id: typeof arg === 'object' ? arg.id : arg, - - // default values that will get assigned if the doc is new - defaults, - }); - - const id: string = typeof arg === 'object' ? arg.id : arg; - const useResolve = typeof arg === 'object' ? arg.useResolve : false; - - this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(this.aliasId || this.id)}`; - - // Overwrite init if we want to use resolve - if (useResolve || true) { - this.init = async () => { - const esType = SavedDashboard.type; - // ensure that the esType is defined - if (!esType) throw new Error('You must define a type name to use SavedObject objects.'); - - if (!id) { - // just assign the defaults and be done - assign(this, defaults); - await this.hydrateIndexPattern!(); - - return this; - } - - const { - outcome, - alias_target_id: aliasId, - alias_purpose: aliasPurpose, - saved_object: resp, - } = await savedObjectsClient.resolve(esType, id); - - const respMapped = { - _id: resp.id, - _type: resp.type, - _source: cloneDeep(resp.attributes), - references: resp.references, - found: !!resp._version, - }; - - this.outcome = outcome; - this.aliasId = aliasId; - this.aliasPurpose = aliasPurpose; - await this.applyESResp(respMapped); - - return this; - }; - } - } - - getQuery() { - return this.searchSource!.getOwnField('query') || { query: '', language: 'kuery' }; - } - - getFilters() { - return this.searchSource!.getOwnField('filter') || []; - } - - getFullEditPath = (editMode?: boolean) => { - return `/app/dashboards#${createDashboardEditUrl(this.id, editMode)}`; - }; - } - - // Unfortunately this throws a typescript error without the casting. I think it's due to the - // convoluted way SavedObjects are created. - return SavedDashboard as unknown as new (id: string) => DashboardSavedObject; -} diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts deleted file mode 100644 index a154fdad96562..0000000000000 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsClientContract } from '@kbn/core/public'; -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import type { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; - -import { SavedObjectLoader } from '../services/saved_object_loader'; -import { createSavedDashboardClass } from './saved_dashboard'; - -interface Services { - savedObjectsClient: SavedObjectsClientContract; - savedObjects: SavedObjectsStart; - embeddableStart: EmbeddableStart; -} - -/** - * @param services - */ -export function createSavedDashboardLoader({ - savedObjects, - savedObjectsClient, - embeddableStart, -}: Services) { - const SavedDashboard = createSavedDashboardClass( - savedObjects, - embeddableStart, - savedObjectsClient - ); - return new SavedObjectLoader(SavedDashboard, savedObjectsClient); -} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts new file mode 100644 index 0000000000000..c23a76746404d --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { savedObjectsServiceMock } from '@kbn/core/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardAttributes } from '../../application'; +import { FindDashboardSavedObjectsResponse } from './lib/find_dashboard_saved_objects'; + +import { DashboardSavedObjectService } from './types'; +import { LoadDashboardFromSavedObjectReturn } from './lib/load_dashboard_state_from_saved_object'; + +type DashboardSavedObjectServiceFactory = PluginServiceFactory; + +export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = () => { + const { client: savedObjectsClient } = savedObjectsServiceMock.createStartContract(); + return { + loadDashboardStateFromSavedObject: jest.fn().mockImplementation(() => + Promise.resolve({ + dashboardState: {}, + } as LoadDashboardFromSavedObjectReturn) + ), + saveDashboardStateToSavedObject: jest.fn(), + findDashboards: { + findSavedObjects: jest.fn().mockImplementation(({ search, size }) => { + const sizeToUse = size ?? 10; + const hits: FindDashboardSavedObjectsResponse['hits'] = []; + for (let i = 0; i < sizeToUse; i++) { + hits.push({ + type: 'dashboard', + id: `dashboard${i}`, + attributes: { + description: `dashboard${i} desc`, + title: `dashboard${i} - ${search} - title`, + }, + } as FindDashboardSavedObjectsResponse['hits'][0]); + } + return Promise.resolve({ + total: sizeToUse, + hits, + }); + }), + findByIds: jest.fn().mockImplementation(() => + Promise.resolve([ + { + id: `dashboardUnsavedOne`, + status: 'success', + attributes: { + title: `Dashboard Unsaved One`, + } as unknown as DashboardAttributes, + }, + { + id: `dashboardUnsavedTwo`, + status: 'success', + attributes: { + title: `Dashboard Unsaved Two`, + } as unknown as DashboardAttributes, + }, + { + id: `dashboardUnsavedThree`, + status: 'success', + attributes: { + title: `Dashboard Unsaved Three`, + } as unknown as DashboardAttributes, + }, + ]) + ), + findByTitle: jest.fn(), + }, + checkForDuplicateDashboardTitle: jest.fn(), + savedObjectsClient, + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts new file mode 100644 index 0000000000000..7fb558309936e --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; + +import type { DashboardStartDependencies } from '../../plugin'; +import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashboard_title'; +import { + findDashboardIdByTitle, + findDashboardSavedObjects, + findDashboardSavedObjectsByIds, +} from './lib/find_dashboard_saved_objects'; +import { loadDashboardStateFromSavedObject } from './lib/load_dashboard_state_from_saved_object'; +import { saveDashboardStateToSavedObject } from './lib/save_dashboard_state_to_saved_object'; +import type { DashboardSavedObjectRequiredServices, DashboardSavedObjectService } from './types'; + +export type DashboardSavedObjectServiceFactory = KibanaPluginServiceFactory< + DashboardSavedObjectService, + DashboardStartDependencies, + DashboardSavedObjectRequiredServices +>; + +export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = ( + { coreStart }, + requiredServices +) => { + const { + savedObjects: { client: savedObjectsClient }, + } = coreStart; + + return { + loadDashboardStateFromSavedObject: ({ id, getScopedHistory }) => + loadDashboardStateFromSavedObject({ + id, + getScopedHistory, + savedObjectsClient, + ...requiredServices, + }), + saveDashboardStateToSavedObject: ({ currentState, redirectTo, saveOptions }) => + saveDashboardStateToSavedObject({ + redirectTo, + saveOptions, + currentState, + savedObjectsClient, + ...requiredServices, + }), + findDashboards: { + findSavedObjects: ({ hasReference, search, size }) => + findDashboardSavedObjects({ hasReference, search, size, savedObjectsClient }), + findByIds: (ids) => findDashboardSavedObjectsByIds(savedObjectsClient, ids), + findByTitle: (title) => findDashboardIdByTitle(title, savedObjectsClient), + }, + checkForDuplicateDashboardTitle: (props) => + checkForDuplicateDashboardTitle(props, savedObjectsClient), + savedObjectsClient, + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts new file mode 100644 index 0000000000000..e03345e78c41f --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectsClientContract } from '@kbn/core/public'; + +import { DashboardConstants } from '../../..'; +import type { DashboardAttributes } from '../../../application'; + +export interface DashboardDuplicateTitleCheckProps { + title: string; + copyOnSave: boolean; + lastSavedTitle: string; + onTitleDuplicate: () => void; + isTitleDuplicateConfirmed: boolean; +} + +/** + * check for an existing dashboard with the same title in ES + * returns Promise when there is no duplicate, or runs the provided onTitleDuplicate + * function when the title already exists + */ +export async function checkForDuplicateDashboardTitle( + { + title, + copyOnSave, + lastSavedTitle, + onTitleDuplicate, + isTitleDuplicateConfirmed, + }: DashboardDuplicateTitleCheckProps, + savedObjectsClient: SavedObjectsClientContract +): Promise { + // Don't check for duplicates if user has already confirmed save with duplicate title + if (isTitleDuplicateConfirmed) { + return true; + } + + // Don't check if the user isn't updating the title, otherwise that would become very annoying to have + // to confirm the save every time, except when copyOnSave is true, then we do want to check. + if (title === lastSavedTitle && !copyOnSave) { + return true; + } + const response = await savedObjectsClient.find({ + perPage: 10, + fields: ['title'], + search: `"${title}"`, + searchFields: ['title'], + type: DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, + }); + const duplicate = response.savedObjects.find( + (obj) => obj.get('title').toLowerCase() === title.toLowerCase() + ); + if (!duplicate) { + return true; + } + onTitleDuplicate?.(); + return false; +} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts new file mode 100644 index 0000000000000..c24511f56d3e2 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + SavedObjectError, + SavedObjectsClientContract, + SavedObjectsFindOptionsReference, + SimpleSavedObject, +} from '@kbn/core/public'; + +import { DashboardConstants } from '../../..'; +import type { DashboardAttributes } from '../../../application'; + +export interface FindDashboardSavedObjectsArgs { + hasReference?: SavedObjectsFindOptionsReference[]; + savedObjectsClient: SavedObjectsClientContract; + search: string; + size: number; +} + +export interface FindDashboardSavedObjectsResponse { + total: number; + hits: Array>; +} + +export async function findDashboardSavedObjects({ + savedObjectsClient, + hasReference, + search, + size, +}: FindDashboardSavedObjectsArgs): Promise { + const { total, savedObjects } = await savedObjectsClient.find({ + type: DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, + search: search ? `${search}*` : undefined, + searchFields: ['title^3', 'description'], + defaultSearchOperator: 'AND' as 'AND', + perPage: size, + hasReference, + page: 1, + }); + return { + total, + hits: savedObjects, + }; +} + +export type FindDashboardBySavedObjectIdsResult = { id: string } & ( + | { status: 'success'; attributes: DashboardAttributes } + | { status: 'error'; error: SavedObjectError } +); + +export async function findDashboardSavedObjectsByIds( + savedObjectsClient: SavedObjectsClientContract, + ids: string[] +): Promise { + const { savedObjects } = await savedObjectsClient.bulkGet( + ids.map((id) => ({ id, type: DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE })) + ); + + return savedObjects.map((savedObjectResult) => { + if (savedObjectResult.error) + return { status: 'error', error: savedObjectResult.error, id: savedObjectResult.id }; + const { attributes, id } = savedObjectResult; + return { + id, + status: 'success', + attributes: attributes as DashboardAttributes, + }; + }); +} + +export async function findDashboardIdByTitle( + title: string, + savedObjectsClient: SavedObjectsClientContract +): Promise<{ id: string } | undefined> { + const results = await savedObjectsClient.find({ + search: `"${title}"`, + searchFields: ['title'], + type: 'dashboard', + }); + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = results.savedObjects.filter( + (dashboard) => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + return { id: matchingDashboards[0].id }; + } +} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts new file mode 100644 index 0000000000000..963814013dc30 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ReactElement } from 'react'; + +import { Filter } from '@kbn/es-query'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public'; +import { rawControlGroupAttributesToControlGroupInput } from '@kbn/controls-plugin/common'; +import { parseSearchSourceJSON, injectSearchSourceReferences } from '@kbn/data-plugin/public'; +import { SavedObjectAttributes, SavedObjectsClientContract, ScopedHistory } from '@kbn/core/public'; + +import { migrateLegacyQuery } from '../../../application/lib/migrate_legacy_query'; + +import { + DashboardConstants, + defaultDashboardState, + createDashboardEditUrl, +} from '../../../dashboard_constants'; +import type { DashboardAttributes } from '../../../application'; +import { DashboardSavedObjectRequiredServices } from '../types'; +import { DashboardOptions, DashboardState } from '../../../types'; +import { cleanFiltersForSerialize } from '../../../application/lib'; +import { convertSavedPanelsToPanelMap, injectReferences } from '../../../../common'; + +export type LoadDashboardFromSavedObjectProps = DashboardSavedObjectRequiredServices & { + id?: string; + getScopedHistory?: () => ScopedHistory; + savedObjectsClient: SavedObjectsClientContract; +}; + +export interface LoadDashboardFromSavedObjectReturn { + redirectedToAlias?: boolean; + dashboardState?: DashboardState; + createConflictWarning?: () => ReactElement | undefined; +} + +type SuccessfulLoadDashboardFromSavedObjectReturn = LoadDashboardFromSavedObjectReturn & { + dashboardState: DashboardState; +}; + +export const dashboardStateLoadWasSuccessful = ( + incoming?: LoadDashboardFromSavedObjectReturn +): incoming is SuccessfulLoadDashboardFromSavedObjectReturn => { + return Boolean(incoming && incoming?.dashboardState && !incoming.redirectedToAlias); +}; + +export const loadDashboardStateFromSavedObject = async ({ + savedObjectsTagging, + savedObjectsClient, + getScopedHistory, + screenshotMode, + embeddable, + spaces, + data, + id, +}: LoadDashboardFromSavedObjectProps): Promise => { + const { + search: dataSearchService, + query: { queryString }, + } = data; + + /** + * This is a newly created dashboard, so there is no saved object state to load. + */ + if (!id) return { dashboardState: defaultDashboardState }; + + /** + * Load the saved object + */ + const { + outcome, + alias_purpose: aliasPurpose, + alias_target_id: aliasId, + saved_object: rawDashboardSavedObject, + } = await savedObjectsClient.resolve( + DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, + id + ); + if (!rawDashboardSavedObject._version) { + throw new SavedObjectNotFound(DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, id); + } + + /** + * Inject saved object references back into the saved object attributes + */ + const { references, attributes: rawAttributes } = rawDashboardSavedObject; + const attributes = (() => { + if (!references || references.length === 0) return rawAttributes; + return injectReferences( + { references, attributes: rawAttributes as unknown as SavedObjectAttributes }, + { + embeddablePersistableStateService: embeddable, + } + ) as unknown as DashboardAttributes; + })(); + + /** + * Handle saved object resolve alias outcome by redirecting + */ + const scopedHistory = getScopedHistory?.(); + if (scopedHistory && outcome === 'aliasMatch' && id && aliasId) { + const path = scopedHistory.location.hash.replace(id, aliasId); + if (screenshotMode.isScreenshotMode()) { + scopedHistory.replace(path); + } else { + await spaces.redirectLegacyUrl?.({ path, aliasPurpose }); + } + return { redirectedToAlias: true }; + } + + /** + * Create conflict warning component if there is a saved object id conflict + */ + const createConflictWarning = + scopedHistory && outcome === 'conflict' && aliasId + ? () => + spaces.getLegacyUrlConflict?.({ + currentObjectId: id, + otherObjectId: aliasId, + otherObjectPath: `#${createDashboardEditUrl(aliasId)}${scopedHistory.location.search}`, + }) + : undefined; + + /** + * Create search source and pull filters and query from it. + */ + const searchSourceJSON = attributes.kibanaSavedObjectMeta.searchSourceJSON; + const searchSource = await (async () => { + if (!searchSourceJSON) { + return await dataSearchService.searchSource.create(); + } + try { + let searchSourceValues = parseSearchSourceJSON(searchSourceJSON); + searchSourceValues = injectSearchSourceReferences(searchSourceValues as any, references); + return await dataSearchService.searchSource.create(searchSourceValues); + } catch (error: any) { + return await dataSearchService.searchSource.create(); + } + })(); + + const filters = cleanFiltersForSerialize((searchSource?.getOwnField('filter') as Filter[]) ?? []); + + const query = migrateLegacyQuery( + searchSource?.getOwnField('query') || queryString.getDefaultQuery() // TODO SAVED DASHBOARDS determine if migrateLegacyQuery is still needed + ); + + const { + refreshInterval, + description, + timeRestore, + optionsJSON, + panelsJSON, + timeFrom, + timeTo, + title, + } = attributes; + + const timeRange = + timeRestore && timeFrom && timeTo + ? { + from: timeFrom, + to: timeTo, + } + : undefined; + + /** + * Parse panels and options from JSON + */ + const options: DashboardOptions = optionsJSON ? JSON.parse(optionsJSON) : undefined; + const panels = convertSavedPanelsToPanelMap(panelsJSON ? JSON.parse(panelsJSON) : []); + + return { + createConflictWarning, + dashboardState: { + ...defaultDashboardState, + + savedObjectId: id, + refreshInterval, + timeRestore, + description, + timeRange, + options, + filters, + panels, + title, + query, + + viewMode: ViewMode.VIEW, // dashboards loaded from saved object default to view mode. If it was edited recently, the view mode from session storage will override this. + tags: savedObjectsTagging.getTagIdsFromReferences?.(references) ?? [], + + controlGroupInput: + attributes.controlGroupInput && + rawControlGroupAttributesToControlGroupInput(attributes.controlGroupInput), + }, + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts new file mode 100644 index 0000000000000..11c6988d22f96 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { pick } from 'lodash'; + +import { isFilterPinned } from '@kbn/es-query'; +import { SavedObjectsClientContract } from '@kbn/core/public'; +import { SavedObjectAttributes } from '@kbn/core-saved-objects-common'; + +import { extractSearchSourceReferences, RefreshInterval } from '@kbn/data-plugin/public'; +import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; + +import type { DashboardAttributes } from '../../../application'; +import { DashboardSavedObjectRequiredServices } from '../types'; +import { DashboardConstants } from '../../../dashboard_constants'; +import { convertTimeToUTCString } from '../../../application/lib'; +import { DashboardRedirect, DashboardState } from '../../../types'; +import { dashboardSaveToastStrings } from '../../../dashboard_strings'; +import { convertPanelMapToSavedPanels, extractReferences } from '../../../../common'; +import { serializeControlGroupInput } from '../../../application/lib/dashboard_control_group'; + +export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolean }; + +export type SaveDashboardProps = DashboardSavedObjectRequiredServices & { + currentState: DashboardState; + redirectTo: DashboardRedirect; + saveOptions: SavedDashboardSaveOpts; + savedObjectsClient: SavedObjectsClientContract; +}; + +export interface SaveDashboardReturn { + id?: string; + error?: string; + redirected?: boolean; +} + +export const saveDashboardStateToSavedObject = async ({ + data, + redirectTo, + embeddable, + saveOptions, + currentState, + savedObjectsClient, + savedObjectsTagging, + dashboardSessionStorage, + notifications: { toasts }, + initializerContext: { kibanaVersion }, +}: SaveDashboardProps): Promise => { + const { + search: dataSearchService, + query: { + timefilter: { timefilter }, + }, + } = data; + + const { + tags, + query, + title, + panels, + filters, + options, + timeRestore, + description, + savedObjectId, + controlGroupInput, + } = currentState; + + /** + * Stringify filters and query into search source JSON + */ + const { searchSourceJSON, searchSourceReferences } = await (async () => { + const searchSource = await dataSearchService.searchSource.create(); + searchSource.setField( + 'filter', // save only unpinned filters + filters.filter((filter) => !isFilterPinned(filter)) + ); + searchSource.setField('query', query); + + const rawSearchSourceFields = searchSource.getSerializedFields(); + const [fields, references] = extractSearchSourceReferences(rawSearchSourceFields); + return { searchSourceReferences: references, searchSourceJSON: JSON.stringify(fields) }; + })(); + + /** + * Stringify options and panels + */ + const optionsJSON = JSON.stringify(options); + const panelsJSON = JSON.stringify(convertPanelMapToSavedPanels(panels, kibanaVersion)); + + /** + * Parse global time filter settings + */ + const { from, to } = timefilter.getTime(); + const timeFrom = timeRestore ? convertTimeToUTCString(from) : undefined; + const timeTo = timeRestore ? convertTimeToUTCString(to) : undefined; + const refreshInterval = timeRestore + ? (pick(timefilter.getRefreshInterval(), [ + 'display', + 'pause', + 'section', + 'value', + ]) as RefreshInterval) + : undefined; + + const rawDashboardAttributes: DashboardAttributes = { + controlGroupInput: serializeControlGroupInput(controlGroupInput), + kibanaSavedObjectMeta: { searchSourceJSON }, + refreshInterval, + timeRestore, + optionsJSON, + description, + panelsJSON, + timeFrom, + title, + timeTo, + version: 1, // todo - where does version come from? Why is it needed? + }; + + /** + * Extract references from raw attributes and tags into the references array. + */ + const { attributes, references: dashboardReferences } = extractReferences( + { + attributes: rawDashboardAttributes as unknown as SavedObjectAttributes, + references: searchSourceReferences, + }, + { embeddablePersistableStateService: embeddable } + ); + const references = savedObjectsTagging.updateTagsReferences + ? savedObjectsTagging.updateTagsReferences(dashboardReferences, tags) + : dashboardReferences; + + /** + * Save the saved object using the saved objects client + */ + const idToSaveTo = saveOptions.saveAsCopy ? undefined : savedObjectId; + try { + const { id: newId } = await savedObjectsClient.create( + DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE, + attributes, + { + id: idToSaveTo, + overwrite: true, + references, + } + ); + + if (newId) { + toasts.addSuccess({ + title: dashboardSaveToastStrings.getSuccessString(currentState.title), + 'data-test-subj': 'saveDashboardSuccess', + }); + + /** + * If the dashboard id has been changed, redirect to the new ID to keep the url param in sync. + */ + if (newId !== savedObjectId) { + dashboardSessionStorage.clearState(savedObjectId); + redirectTo({ + id: newId, + editMode: true, + useReplace: true, + destination: 'dashboard', + }); + return { redirected: true, id: newId }; + } + } + return { id: newId }; + } catch (error) { + toasts.addDanger({ + title: dashboardSaveToastStrings.getFailureString(currentState.title, error.message), + 'data-test-subj': 'saveDashboardFailure', + }); + return { error }; + } +}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts new file mode 100644 index 0000000000000..dd817c751aa8d --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SavedObjectsClientContract } from '@kbn/core/public'; + +import { DashboardDataService } from '../data/types'; +import { DashboardSpacesService } from '../spaces/types'; +import { DashboardEmbeddableService } from '../embeddable/types'; +import { DashboardNotificationsService } from '../notifications/types'; +import { DashboardScreenshotModeService } from '../screenshot_mode/types'; +import { DashboardInitializerContextService } from '../initializer_context/types'; +import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types'; +import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types'; + +import { + LoadDashboardFromSavedObjectProps, + LoadDashboardFromSavedObjectReturn, +} from './lib/load_dashboard_state_from_saved_object'; +import { + SaveDashboardProps, + SaveDashboardReturn, +} from './lib/save_dashboard_state_to_saved_object'; +import { + FindDashboardBySavedObjectIdsResult, + FindDashboardSavedObjectsArgs, + FindDashboardSavedObjectsResponse, +} from './lib/find_dashboard_saved_objects'; +import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title'; + +export interface DashboardSavedObjectRequiredServices { + screenshotMode: DashboardScreenshotModeService; + embeddable: DashboardEmbeddableService; + spaces: DashboardSpacesService; + data: DashboardDataService; + initializerContext: DashboardInitializerContextService; + notifications: DashboardNotificationsService; + savedObjectsTagging: DashboardSavedObjectsTaggingService; + dashboardSessionStorage: DashboardSessionStorageServiceType; +} + +export interface DashboardSavedObjectService { + loadDashboardStateFromSavedObject: ( + props: Pick + ) => Promise; + + saveDashboardStateToSavedObject: ( + props: Pick + ) => Promise; + findDashboards: { + findSavedObjects: ( + props: Pick + ) => Promise; + findByIds: (ids: string[]) => Promise; + findByTitle: (title: string) => Promise<{ id: string } | undefined>; + }; + checkForDuplicateDashboardTitle: (meta: DashboardDuplicateTitleCheckProps) => Promise; + savedObjectsClient: SavedObjectsClientContract; +} diff --git a/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts b/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts index 18f952d4620e2..a1a6b2973664f 100644 --- a/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts +++ b/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts @@ -16,9 +16,13 @@ export const embeddableServiceFactory: EmbeddableServiceFactory = () => { const pluginMock = embeddablePluginMock.createStartContract(); return { - getEmbeddableFactory: pluginMock.getEmbeddableFactory, getEmbeddableFactories: pluginMock.getEmbeddableFactories, + getEmbeddableFactory: pluginMock.getEmbeddableFactory, getStateTransfer: pluginMock.getStateTransfer, + getAllMigrations: pluginMock.getAllMigrations, EmbeddablePanel: pluginMock.EmbeddablePanel, + telemetry: pluginMock.telemetry, + extract: pluginMock.extract, + inject: pluginMock.inject, }; }; diff --git a/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts b/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts index 258c11f697bc5..c796bbde2d7da 100644 --- a/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts +++ b/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts @@ -6,7 +6,10 @@ * Side Public License, v 1. */ +import { pick } from 'lodash'; + import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; + import type { DashboardStartDependencies } from '../../plugin'; import type { DashboardEmbeddableService } from './types'; @@ -15,14 +18,16 @@ export type EmbeddableServiceFactory = KibanaPluginServiceFactory< DashboardStartDependencies >; export const embeddableServiceFactory: EmbeddableServiceFactory = ({ startPlugins }) => { - const { - embeddable: { getEmbeddableFactory, getEmbeddableFactories, getStateTransfer, EmbeddablePanel }, - } = startPlugins; + const { embeddable } = startPlugins; - return { - getEmbeddableFactory, - getEmbeddableFactories, - getStateTransfer, - EmbeddablePanel, - }; + return pick(embeddable, [ + 'getEmbeddableFactory', + 'getEmbeddableFactories', + 'getStateTransfer', + 'EmbeddablePanel', + 'getAllMigrations', + 'telemetry', + 'extract', + 'inject', + ]); }; diff --git a/src/plugins/dashboard/public/services/embeddable/types.ts b/src/plugins/dashboard/public/services/embeddable/types.ts index ef24db7c2e624..40d0ae02bfa7c 100644 --- a/src/plugins/dashboard/public/services/embeddable/types.ts +++ b/src/plugins/dashboard/public/services/embeddable/types.ts @@ -8,9 +8,14 @@ import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; -export interface DashboardEmbeddableService { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - getStateTransfer: EmbeddableStart['getStateTransfer']; - EmbeddablePanel: EmbeddableStart['EmbeddablePanel']; -} +export type DashboardEmbeddableService = Pick< + EmbeddableStart, + | 'getEmbeddableFactories' + | 'getEmbeddableFactory' + | 'getAllMigrations' + | 'getStateTransfer' + | 'EmbeddablePanel' + | 'telemetry' + | 'extract' + | 'inject' +>; diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts index c703b8b6767ac..b8c39909dd61a 100644 --- a/src/plugins/dashboard/public/services/plugin_services.stub.ts +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -29,7 +29,6 @@ import { initializerContextServiceFactory } from './initializer_context/initiali import { navigationServiceFactory } from './navigation/navigation.stub'; import { notificationsServiceFactory } from './notifications/notifications.stub'; import { overlaysServiceFactory } from './overlays/overlays.stub'; -import { savedObjectsServiceFactory } from './saved_objects/saved_objects.stub'; import { savedObjectsTaggingServiceFactory } from './saved_objects_tagging/saved_objects_tagging.stub'; import { screenshotModeServiceFactory } from './screenshot_mode/screenshot_mode.stub'; import { settingsServiceFactory } from './settings/settings.stub'; @@ -38,8 +37,10 @@ import { usageCollectionServiceFactory } from './usage_collection/usage_collecti import { spacesServiceFactory } from './spaces/spaces.stub'; import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub'; import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; +import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub'; export const providers: PluginServiceProviders = { + dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory), analytics: new PluginServiceProvider(analyticsServiceFactory), application: new PluginServiceProvider(applicationServiceFactory), chrome: new PluginServiceProvider(chromeServiceFactory), @@ -55,7 +56,6 @@ export const providers: PluginServiceProviders = { navigation: new PluginServiceProvider(navigationServiceFactory), notifications: new PluginServiceProvider(notificationsServiceFactory), overlays: new PluginServiceProvider(overlaysServiceFactory), - savedObjects: new PluginServiceProvider(savedObjectsServiceFactory), savedObjectsTagging: new PluginServiceProvider(savedObjectsTaggingServiceFactory), screenshotMode: new PluginServiceProvider(screenshotModeServiceFactory), settings: new PluginServiceProvider(settingsServiceFactory), diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts index 421b5e75c482c..b4ee1b566a8ac 100644 --- a/src/plugins/dashboard/public/services/plugin_services.ts +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -30,7 +30,6 @@ import { navigationServiceFactory } from './navigation/navigation_service'; import { notificationsServiceFactory } from './notifications/notifications_service'; import { overlaysServiceFactory } from './overlays/overlays_service'; import { screenshotModeServiceFactory } from './screenshot_mode/screenshot_mode_service'; -import { savedObjectsServiceFactory } from './saved_objects/saved_objects_service'; import { savedObjectsTaggingServiceFactory } from './saved_objects_tagging/saved_objects_tagging_service'; import { settingsServiceFactory } from './settings/settings_service'; import { shareServiceFactory } from './share/share_services'; @@ -39,17 +38,29 @@ import { urlForwardingServiceFactory } from './url_forwarding/url_forwarding_ser import { visualizationsServiceFactory } from './visualizations/visualizations_service'; import { usageCollectionServiceFactory } from './usage_collection/usage_collection_service'; import { analyticsServiceFactory } from './analytics/analytics_service'; +import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service'; const providers: PluginServiceProviders = { + dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [ + 'dashboardSessionStorage', + 'savedObjectsTagging', + 'initializerContext', + 'screenshotMode', + 'notifications', + 'embeddable', + 'spaces', + 'data', + ]), + dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory, [ + 'notifications', + 'spaces', + ]), + analytics: new PluginServiceProvider(analyticsServiceFactory), application: new PluginServiceProvider(applicationServiceFactory), chrome: new PluginServiceProvider(chromeServiceFactory), coreContext: new PluginServiceProvider(coreContextServiceFactory), dashboardCapabilities: new PluginServiceProvider(dashboardCapabilitiesServiceFactory), - dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory, [ - 'notifications', - 'spaces', - ]), data: new PluginServiceProvider(dataServiceFactory), dataViewEditor: new PluginServiceProvider(dataViewEditorServiceFactory), documentationLinks: new PluginServiceProvider(documentationLinksServiceFactory), @@ -59,7 +70,6 @@ const providers: PluginServiceProviders SavedObject; - public type: string; - public lowercaseType: string; - public loaderProperties: Record; - - constructor( - SavedObjectClass: any, - private readonly savedObjectsClient: SavedObjectsClientContract - ) { - this.type = SavedObjectClass.type; - this.Class = SavedObjectClass; - this.lowercaseType = this.type.toLowerCase(); - - this.loaderProperties = { - name: `${this.lowercaseType}s`, - noun: upperFirst(this.type), - nouns: `${this.lowercaseType}s`, - }; - } - - /** - * Retrieve a saved object by id or create new one. - * Returns a promise that completes when the object finishes - * initializing. - * @param opts - * @returns {Promise} - */ - async get(opts?: Record | string) { - // can accept object as argument in accordance to SavedVis class - // see src/plugins/saved_objects/public/saved_object/saved_object_loader.ts - // @ts-ignore - const obj = new this.Class(opts); - return obj.init(); - } - - urlFor(id: string) { - return `#/${this.lowercaseType}/${encodeURIComponent(id)}`; - } - - async delete(ids: string | string[]) { - const idsUsed = !Array.isArray(ids) ? [ids] : ids; - - const deletions = idsUsed.map((id) => { - // @ts-ignore - const savedObject = new this.Class(id); - return savedObject.delete(); - }); - await Promise.all(deletions); - } - - /** - * Updates source to contain an id, url and references fields, and returns the updated - * source object. - * @param source - * @param id - * @param references - * @returns {source} The modified source object, with an id and url field. - */ - mapHitSource( - source: Record, - id: string, - references: SavedObjectReference[] = [], - updatedAt?: string - ): Record { - return { - ...source, - id, - url: this.urlFor(id), - references, - updatedAt, - }; - } - - /** - * Updates hit.attributes to contain an id and url field, and returns the updated - * attributes object. - * @param hit - * @returns {hit.attributes} The modified hit.attributes object, with an id and url field. - */ - mapSavedObjectApiHits({ - attributes, - id, - references = [], - updatedAt, - }: { - attributes: Record; - id: string; - references?: SavedObjectReference[]; - updatedAt?: string; - }) { - return this.mapHitSource(attributes, id, references, updatedAt); - } - - /** - * TODO: Rather than use a hardcoded limit, implement pagination. See - * https://github.com/elastic/kibana/issues/8044 for reference. - * - * @param search - * @param size - * @param fields - * @returns {Promise} - */ - private findAll( - search: string = '', - { size = 100, fields, hasReference }: SavedObjectLoaderFindOptions - ) { - return this.savedObjectsClient - .find>({ - type: this.lowercaseType, - search: search ? `${search}*` : undefined, - perPage: size, - page: 1, - searchFields: ['title^3', 'description'], - defaultSearchOperator: 'AND', - fields, - hasReference, - } as SavedObjectsFindOptions) - .then((resp) => { - return { - total: resp.total, - hits: resp.savedObjects.map((savedObject) => this.mapSavedObjectApiHits(savedObject)), - }; - }); - } - - find(search: string = '', sizeOrOptions: number | SavedObjectLoaderFindOptions = 100) { - const options: SavedObjectLoaderFindOptions = - typeof sizeOrOptions === 'number' - ? { - size: sizeOrOptions, - } - : sizeOrOptions; - - return this.findAll(search, options).then((resp) => { - return { - total: resp.total, - hits: resp.hits.filter((savedObject) => !savedObject.error), - }; - }); - } -} diff --git a/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts b/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts deleted file mode 100644 index f26e36392603f..0000000000000 --- a/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { DashboardSavedObjectsService } from './types'; - -type SavedObjectsServiceFactory = PluginServiceFactory; - -export const savedObjectsServiceFactory: SavedObjectsServiceFactory = () => { - const pluginMock = savedObjectsServiceMock.createStartContract(); - - return { - client: pluginMock.client, - }; -}; diff --git a/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts b/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts deleted file mode 100644 index 3fff4d9e1c361..0000000000000 --- a/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import type { DashboardStartDependencies } from '../../plugin'; -import type { DashboardSavedObjectsService } from './types'; - -export type SavedObjectsServiceFactory = KibanaPluginServiceFactory< - DashboardSavedObjectsService, - DashboardStartDependencies ->; - -export const savedObjectsServiceFactory: SavedObjectsServiceFactory = ({ coreStart }) => { - const { - savedObjects: { client }, - } = coreStart; - - return { - client, - }; -}; diff --git a/src/plugins/dashboard/public/services/saved_objects/types.ts b/src/plugins/dashboard/public/services/saved_objects/types.ts deleted file mode 100644 index d7d06131f32cb..0000000000000 --- a/src/plugins/dashboard/public/services/saved_objects/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { CoreStart } from '@kbn/core/public'; - -export interface DashboardSavedObjectsService { - client: CoreStart['savedObjects']['client']; -} diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts index d526eedbc1e47..1a1bcd6ca93bf 100644 --- a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts @@ -20,9 +20,10 @@ export const savedObjectsTaggingServiceFactory: SavedObjectsTaggingServiceFactor // I'm not defining components so that I don't have to update the snapshot of `save_modal.test` // However, if it's ever necessary, it can be done via: `components: pluginMock.components`, - getSearchBarFilter: pluginMock.getSearchBarFilter, - getTableColumnDefinition: pluginMock.getTableColumnDefinition, hasTagDecoration: pluginMock.hasTagDecoration, parseSearchQuery: pluginMock.parseSearchQuery, + getSearchBarFilter: pluginMock.getSearchBarFilter, + getTagIdsFromReferences: pluginMock.getTagIdsFromReferences, + getTableColumnDefinition: pluginMock.getTableColumnDefinition, }; }; diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts index 7e252ed79d7b7..a100282b4cff2 100644 --- a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts @@ -27,19 +27,23 @@ export const savedObjectsTaggingServiceFactory: SavedObjectsTaggingServiceFactor const { ui: { components, + parseSearchQuery, + hasTagDecoration, getSearchBarFilter, + updateTagsReferences, + getTagIdsFromReferences, getTableColumnDefinition, - hasTagDecoration, - parseSearchQuery, }, } = taggingApi; return { hasApi: true, components, - getSearchBarFilter, - getTableColumnDefinition, hasTagDecoration, parseSearchQuery, + getSearchBarFilter, + updateTagsReferences, + getTagIdsFromReferences, + getTableColumnDefinition, }; }; diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts index 803db5ff46d9a..ba08a53709346 100644 --- a/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts @@ -12,9 +12,10 @@ export interface DashboardSavedObjectsTaggingService { hasApi: boolean; // remove this once the entire service is optional components?: SavedObjectsTaggingApi['ui']['components']; - getSearchBarFilter?: SavedObjectsTaggingApi['ui']['getSearchBarFilter']; - getTableColumnDefinition?: SavedObjectsTaggingApi['ui']['getTableColumnDefinition']; hasTagDecoration?: SavedObjectsTaggingApi['ui']['hasTagDecoration']; parseSearchQuery?: SavedObjectsTaggingApi['ui']['parseSearchQuery']; + getSearchBarFilter?: SavedObjectsTaggingApi['ui']['getSearchBarFilter']; + updateTagsReferences?: SavedObjectsTaggingApi['ui']['updateTagsReferences']; getTagIdsFromReferences?: SavedObjectsTaggingApi['ui']['getTagIdsFromReferences']; + getTableColumnDefinition?: SavedObjectsTaggingApi['ui']['getTableColumnDefinition']; } diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts index 3309ce0575971..5d14b59e8a125 100644 --- a/src/plugins/dashboard/public/services/types.ts +++ b/src/plugins/dashboard/public/services/types.ts @@ -15,6 +15,7 @@ import { DashboardApplicationService } from './application/types'; import { DashboardChromeService } from './chrome/types'; import { DashboardCoreContextService } from './core_context/types'; import { DashboardCapabilitiesService } from './dashboard_capabilities/types'; +import { DashboardSavedObjectService } from './dashboard_saved_object/types'; import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types'; import { DashboardDataService } from './data/types'; import { DashboardDataViewEditorService } from './data_view_editor/types'; @@ -25,7 +26,6 @@ import { DashboardInitializerContextService } from './initializer_context/types' import { DashboardNavigationService } from './navigation/types'; import { DashboardNotificationsService } from './notifications/types'; import { DashboardOverlaysService } from './overlays/types'; -import { DashboardSavedObjectsService } from './saved_objects/types'; import { DashboardSavedObjectsTaggingService } from './saved_objects_tagging/types'; import { DashboardScreenshotModeService } from './screenshot_mode/types'; import { DashboardSettingsService } from './settings/types'; @@ -39,12 +39,14 @@ export type DashboardPluginServiceParams = KibanaPluginServiceParams void; -export interface SavedDashboardPanelMap { - [key: string]: SavedDashboardPanel; -} - -export interface DashboardPanelMap { - [key: string]: DashboardPanelState; -} /** * DashboardState contains all pieces of tracked state for an individual dashboard @@ -48,11 +41,13 @@ export interface DashboardState { description: string; savedQuery?: string; timeRestore: boolean; + timeRange?: TimeRange; + savedObjectId?: string; fullScreenMode: boolean; expandedPanelId?: string; options: DashboardOptions; panels: DashboardPanelMap; - timeRange?: TimeRange; + refreshInterval?: RefreshInterval; timeslice?: [number, number]; controlGroupInput?: PersistableControlGroupInput; @@ -95,19 +90,17 @@ export interface DashboardAppState { dataViews?: DataView[]; updateLastSavedState?: () => void; resetToLastSavedState?: () => void; - savedDashboard?: DashboardSavedObject; dashboardContainer?: DashboardContainer; + createConflictWarning?: () => ReactElement | undefined; getLatestDashboardState?: () => DashboardState; $triggerDashboardRefresh: Subject<{ force?: boolean }>; $onDashboardStateChange: BehaviorSubject; - applyFilters?: (query: Query, filters: Filter[]) => void; } /** * The shared services and tools used to build a dashboard from a saved object ID. */ -// TODO: Remove reference to DashboardAppServices as part of https://github.com/elastic/kibana/pull/138774 -export type DashboardBuildContext = Pick & { +export interface DashboardBuildContext { locatorState?: DashboardAppLocatorParams; history: History; isEmbeddedExternally: boolean; @@ -118,7 +111,7 @@ export type DashboardBuildContext = Pick; $onDashboardStateChange: BehaviorSubject; executionContext?: KibanaExecutionContext; -}; +} // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type DashboardOptions = { @@ -156,8 +149,3 @@ export interface DashboardMountContextProps { onAppLeave: AppMountParameters['onAppLeave']; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } - -// TODO: Remove DashboardAppServices as part of https://github.com/elastic/kibana/pull/138774 -export interface DashboardAppServices { - savedDashboards: SavedObjectLoader; -} diff --git a/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts b/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts index 31c236da607a4..df183f631a3ec 100644 --- a/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts +++ b/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts @@ -8,10 +8,7 @@ import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { EmbeddableRegistryDefinition } from '@kbn/embeddable-plugin/server'; -import { - createExtract, - createInject, -} from '../../common/embeddable/dashboard_container_persistable_state'; +import { createExtract, createInject } from '../../common'; export const dashboardPersistableStateServiceFactory = ( persistableStateService: EmbeddablePersistableStateService diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts deleted file mode 100644 index b3625bec3e8a9..0000000000000 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Serializable } from '@kbn/utility-types'; -import { get, flow, mapValues } from 'lodash'; -import { - SavedObjectAttributes, - SavedObjectMigrationFn, - SavedObjectMigrationMap, -} from '@kbn/core/server'; - -import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; -import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-plugin/common'; -import { - mergeMigrationFunctionMaps, - MigrateFunction, - MigrateFunctionsObject, -} from '@kbn/kibana-utils-plugin/common'; -import { - CONTROL_GROUP_TYPE, - rawControlGroupAttributesToSerializable, - serializableToRawControlGroupAttributes, -} from '@kbn/controls-plugin/common'; -import { migrations730 } from './migrations_730'; -import { SavedDashboardPanel } from '../../common/types'; -import { migrateMatchAllQuery } from './migrate_match_all_query'; -import { DashboardDoc700To720, DashboardDoc730ToLatest } from '../../common'; -import { injectReferences, extractReferences } from '../../common/saved_dashboard_references'; -import { - convertPanelStateToSavedDashboardPanel, - convertSavedDashboardPanelToPanelState, -} from '../../common/embeddable/embeddable_saved_object_converters'; -import { replaceIndexPatternReference } from './replace_index_pattern_reference'; - -function migrateIndexPattern(doc: DashboardDoc700To720) { - const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); - if (typeof searchSourceJSON !== 'string') { - return; - } - let searchSource; - try { - searchSource = JSON.parse(searchSourceJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return; - } - if (searchSource.index) { - searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; - doc.references.push({ - name: searchSource.indexRefName, - type: DATA_VIEW_SAVED_OBJECT_TYPE, - id: searchSource.index, - }); - delete searchSource.index; - } - if (searchSource.filter) { - searchSource.filter.forEach((filterRow: any, i: number) => { - if (!filterRow.meta || !filterRow.meta.index) { - return; - } - filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; - doc.references.push({ - name: filterRow.meta.indexRefName, - type: DATA_VIEW_SAVED_OBJECT_TYPE, - id: filterRow.meta.index, - }); - delete filterRow.meta.index; - }); - } - doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); -} - -const migrations700: SavedObjectMigrationFn = (doc): DashboardDoc700To720 => { - // Set new "references" attribute - doc.references = doc.references || []; - - // Migrate index pattern - migrateIndexPattern(doc as DashboardDoc700To720); - // Migrate panels - const panelsJSON = get(doc, 'attributes.panelsJSON'); - if (typeof panelsJSON !== 'string') { - return doc as DashboardDoc700To720; - } - let panels; - try { - panels = JSON.parse(panelsJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return doc as DashboardDoc700To720; - } - if (!Array.isArray(panels)) { - return doc as DashboardDoc700To720; - } - panels.forEach((panel, i) => { - if (!panel.type || !panel.id) { - return; - } - panel.panelRefName = `panel_${i}`; - doc.references!.push({ - name: `panel_${i}`, - type: panel.type, - id: panel.id, - }); - delete panel.type; - delete panel.id; - }); - doc.attributes.panelsJSON = JSON.stringify(panels); - return doc as DashboardDoc700To720; -}; - -/** - * In 7.8.0 we introduced dashboard drilldowns which are stored inside dashboard saved object as part of embeddable state - * In 7.11.0 we created an embeddable references/migrations system that allows to properly extract embeddable persistable state - * https://github.com/elastic/kibana/issues/71409 - * The idea of this migration is to inject all the embeddable panel references and then run the extraction again. - * As the result of the extraction: - * 1. In addition to regular `panel_` we will get new references which are extracted by `embeddablePersistableStateService` (dashboard drilldown references) - * 2. `panel_` references will be regenerated - * All other references like index-patterns are forwarded non touched - * @param deps - */ -function createExtractPanelReferencesMigration( - deps: DashboardSavedObjectTypeMigrationsDeps -): SavedObjectMigrationFn { - return (doc) => { - const references = doc.references ?? []; - - /** - * Remembering this because dashboard's extractReferences won't return those - * All other references like `panel_` will be overwritten - */ - const oldNonPanelReferences = references.filter((ref) => !ref.name.startsWith('panel_')); - - const injectedAttributes = injectReferences( - { - attributes: doc.attributes as unknown as SavedObjectAttributes, - references, - }, - { embeddablePersistableStateService: deps.embeddable } - ); - - const { attributes, references: newPanelReferences } = extractReferences( - { attributes: injectedAttributes, references: [] }, - { embeddablePersistableStateService: deps.embeddable } - ); - - return { - ...doc, - references: [...oldNonPanelReferences, ...newPanelReferences], - attributes, - }; - }; -} - -type ValueOrReferenceInput = SavedObjectEmbeddableInput & { - attributes?: Serializable; - savedVis?: Serializable; -}; - -/** - * Before 7.10, hidden panel titles were stored as a blank string on the title attribute. In 7.10, this was replaced - * with a usage of the existing hidePanelTitles key. Even though blank string titles still technically work - * in versions > 7.10, they are less explicit than using the hidePanelTitles key. This migration transforms all - * blank string titled panels to panels with the titles explicitly hidden. - */ -export const migrateExplicitlyHiddenTitles: SavedObjectMigrationFn = (doc) => { - const { attributes } = doc; - - // Skip if panelsJSON is missing - if (typeof attributes?.panelsJSON !== 'string') return doc; - - try { - const panels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; - // Same here, prevent failing saved object import if ever panels aren't an array. - if (!Array.isArray(panels)) return doc; - - const newPanels: SavedDashboardPanel[] = []; - panels.forEach((panel) => { - // Convert each panel into the dashboard panel state - const originalPanelState = - convertSavedDashboardPanelToPanelState(panel); - newPanels.push( - convertPanelStateToSavedDashboardPanel( - { - ...originalPanelState, - explicitInput: { - ...originalPanelState.explicitInput, - ...(originalPanelState.explicitInput.title === '' && - !originalPanelState.explicitInput.hidePanelTitles - ? { hidePanelTitles: true } - : {}), - }, - }, - panel.version - ) - ); - }); - return { - ...doc, - attributes: { - ...attributes, - panelsJSON: JSON.stringify(newPanels), - }, - }; - } catch { - return doc; - } -}; - -// Runs the embeddable migrations on each panel -const migrateByValuePanels = - (migrate: MigrateFunction, version: string): SavedObjectMigrationFn => - (doc: any) => { - const { attributes } = doc; - - if (attributes?.controlGroupInput) { - const controlGroupInput = rawControlGroupAttributesToSerializable( - attributes.controlGroupInput - ); - const migratedControlGroupInput = migrate({ - ...controlGroupInput, - type: CONTROL_GROUP_TYPE, - }); - attributes.controlGroupInput = - serializableToRawControlGroupAttributes(migratedControlGroupInput); - } - - // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when - // importing objects without panelsJSON. At development time of this, there is no guarantee each saved - // object has panelsJSON in all previous versions of kibana. - if (typeof attributes?.panelsJSON !== 'string') { - return doc; - } - - const panels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; - // Same here, prevent failing saved object import if ever panels aren't an array. - if (!Array.isArray(panels)) { - return doc; - } - const newPanels: SavedDashboardPanel[] = []; - panels.forEach((panel) => { - // Convert each panel into a state that can be passed to EmbeddablesSetup.migrate - const originalPanelState = - convertSavedDashboardPanelToPanelState(panel); - - // saved vis is used to store by value input for Visualize. This should eventually be renamed to `attributes` to align with Lens and Maps - if ( - originalPanelState.explicitInput.attributes || - originalPanelState.explicitInput.savedVis - ) { - // If this panel is by value, migrate the state using embeddable migrations - const migratedInput = migrate({ - ...originalPanelState.explicitInput, - type: originalPanelState.type, - }); - // Convert the embeddable state back into the panel shape - newPanels.push( - convertPanelStateToSavedDashboardPanel( - { - ...originalPanelState, - explicitInput: { ...migratedInput, id: migratedInput.id as string }, - }, - version - ) - ); - } else { - newPanels.push(panel); - } - }); - return { - ...doc, - attributes: { - ...attributes, - panelsJSON: JSON.stringify(newPanels), - }, - }; - }; - -export interface DashboardSavedObjectTypeMigrationsDeps { - embeddable: EmbeddableSetup; -} - -export const createDashboardSavedObjectTypeMigrations = ( - deps: DashboardSavedObjectTypeMigrationsDeps -): SavedObjectMigrationMap => { - const embeddableMigrations = mapValues( - deps.embeddable.getAllMigrations(), - migrateByValuePanels - ) as MigrateFunctionsObject; - - const dashboardMigrations = { - /** - * We need to have this migration twice, once with a version prior to 7.0.0 once with a version - * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already - * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running, - * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we - * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects - * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced - * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 - * only contained the 6.7.2 migration and not the 7.0.1 migration. - */ - '6.7.2': flow(migrateMatchAllQuery), - '7.0.0': flow(migrations700), - '7.3.0': flow(migrations730), - '7.9.3': flow(migrateMatchAllQuery), - '7.11.0': flow(createExtractPanelReferencesMigration(deps)), - '7.14.0': flow(replaceIndexPatternReference), - '7.17.3': flow(migrateExplicitlyHiddenTitles), - }; - - return mergeMigrationFunctionMaps(dashboardMigrations, embeddableMigrations); -}; diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard_saved_object.ts similarity index 97% rename from src/plugins/dashboard/server/saved_objects/dashboard.ts rename to src/plugins/dashboard/server/saved_objects/dashboard_saved_object.ts index 953852bee59cf..b8474149ca87b 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard_saved_object.ts @@ -10,7 +10,7 @@ import { SavedObjectsType } from '@kbn/core/server'; import { createDashboardSavedObjectTypeMigrations, DashboardSavedObjectTypeMigrationsDeps, -} from './dashboard_migrations'; +} from './migrations/dashboard_saved_object_migrations'; export const createDashboardSavedObjectType = ({ migrationDeps, diff --git a/src/plugins/dashboard/server/saved_objects/index.ts b/src/plugins/dashboard/server/saved_objects/index.ts index af3de2dfca529..c16af55945f9d 100644 --- a/src/plugins/dashboard/server/saved_objects/index.ts +++ b/src/plugins/dashboard/server/saved_objects/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { createDashboardSavedObjectType } from './dashboard'; +export { createDashboardSavedObjectType } from './dashboard_saved_object'; diff --git a/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts deleted file mode 100644 index cea39fc45b0fe..0000000000000 --- a/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectUnsanitizedDoc } from '@kbn/core/server'; -import { DashboardDoc730ToLatest } from '../../common'; - -function isDoc( - doc: { [key: string]: unknown } | SavedObjectUnsanitizedDoc -): doc is SavedObjectUnsanitizedDoc { - return ( - typeof doc.id === 'string' && - typeof doc.type === 'string' && - doc.attributes !== null && - typeof doc.attributes === 'object' && - doc.references !== null && - typeof doc.references === 'object' - ); -} - -export function isDashboardDoc( - doc: { [key: string]: unknown } | DashboardDoc730ToLatest -): doc is DashboardDoc730ToLatest { - if (!isDoc(doc)) { - return false; - } - - if (typeof (doc as DashboardDoc730ToLatest).attributes.panelsJSON !== 'string') { - return false; - } - - return true; -} diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.test.ts similarity index 99% rename from src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.test.ts index 0cefab5104d7d..1a2655c481835 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.test.ts @@ -6,17 +6,15 @@ * Side Public License, v 1. */ -import { SavedObjectReference, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { SerializableRecord } from '@kbn/utility-types'; import { savedObjectsServiceMock } from '@kbn/core/server/mocks'; import { createEmbeddableSetupMock } from '@kbn/embeddable-plugin/server/mocks'; -import { createDashboardSavedObjectTypeMigrations } from './dashboard_migrations'; -import { DashboardDoc730ToLatest } from '../../common'; -import { - createExtract, - createInject, -} from '../../common/embeddable/dashboard_container_persistable_state'; +import { SavedObjectReference, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; + +import { createExtract, createInject } from '../../../common'; import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; -import { SerializableRecord } from '@kbn/utility-types'; +import { createDashboardSavedObjectTypeMigrations } from './dashboard_saved_object_migrations'; +import { DashboardDoc730ToLatest } from './migrate_to_730/types'; const embeddableSetupMock = createEmbeddableSetupMock(); const extract = createExtract(embeddableSetupMock); diff --git a/src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.ts b/src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.ts new file mode 100644 index 0000000000000..2f93c038065bb --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/dashboard_saved_object_migrations.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { flow, mapValues } from 'lodash'; + +import { + mergeMigrationFunctionMaps, + MigrateFunctionsObject, +} from '@kbn/kibana-utils-plugin/common'; +import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { SavedObjectMigrationFn, SavedObjectMigrationMap } from '@kbn/core/server'; + +import { migrations730, migrations700 } from './migrate_to_730'; +import { migrateMatchAllQuery } from './migrate_match_all_query'; +import { migrateExplicitlyHiddenTitles } from './migrate_hidden_titles'; +import { replaceIndexPatternReference } from './migrate_index_pattern_reference'; +import { migrateByValueDashboardPanels } from './migrate_by_value_dashboard_panels'; +import { createExtractPanelReferencesMigration } from './migrate_extract_panel_references'; + +export interface DashboardSavedObjectTypeMigrationsDeps { + embeddable: EmbeddableSetup; +} + +export const createDashboardSavedObjectTypeMigrations = ( + deps: DashboardSavedObjectTypeMigrationsDeps +): SavedObjectMigrationMap => { + const embeddableMigrations = mapValues( + deps.embeddable.getAllMigrations(), + migrateByValueDashboardPanels + ) as MigrateFunctionsObject; + + const dashboardMigrations = { + '6.7.2': flow(migrateMatchAllQuery), + '7.0.0': flow(migrations700), + '7.3.0': flow(migrations730), + '7.9.3': flow(migrateMatchAllQuery), + '7.11.0': flow(createExtractPanelReferencesMigration(deps)), + '7.14.0': flow(replaceIndexPatternReference), + '7.17.3': flow(migrateExplicitlyHiddenTitles), + }; + + return mergeMigrationFunctionMaps(dashboardMigrations, embeddableMigrations); +}; diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_by_value_dashboard_panels.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_by_value_dashboard_panels.ts new file mode 100644 index 0000000000000..3bad12b537103 --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_by_value_dashboard_panels.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + CONTROL_GROUP_TYPE, + rawControlGroupAttributesToSerializable, + serializableToRawControlGroupAttributes, +} from '@kbn/controls-plugin/common'; +import { Serializable } from '@kbn/utility-types'; +import { SavedObjectMigrationFn } from '@kbn/core/server'; +import { MigrateFunction } from '@kbn/kibana-utils-plugin/common'; +import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; + +import { + convertPanelStateToSavedDashboardPanel, + convertSavedDashboardPanelToPanelState, + SavedDashboardPanel, +} from '../../../common'; + +type ValueOrReferenceInput = SavedObjectEmbeddableInput & { + attributes?: Serializable; + savedVis?: Serializable; +}; + +// Runs the embeddable migrations on each panel +export const migrateByValueDashboardPanels = + (migrate: MigrateFunction, version: string): SavedObjectMigrationFn => + (doc: any) => { + const { attributes } = doc; + + if (attributes?.controlGroupInput) { + const controlGroupInput = rawControlGroupAttributesToSerializable( + attributes.controlGroupInput + ); + const migratedControlGroupInput = migrate({ + ...controlGroupInput, + type: CONTROL_GROUP_TYPE, + }); + attributes.controlGroupInput = + serializableToRawControlGroupAttributes(migratedControlGroupInput); + } + + // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when + // importing objects without panelsJSON. At development time of this, there is no guarantee each saved + // object has panelsJSON in all previous versions of kibana. + if (typeof attributes?.panelsJSON !== 'string') { + return doc; + } + + const panels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; + // Same here, prevent failing saved object import if ever panels aren't an array. + if (!Array.isArray(panels)) { + return doc; + } + const newPanels: SavedDashboardPanel[] = []; + panels.forEach((panel) => { + // Convert each panel into a state that can be passed to EmbeddablesSetup.migrate + const originalPanelState = + convertSavedDashboardPanelToPanelState(panel); + + // saved vis is used to store by value input for Visualize. This should eventually be renamed to `attributes` to align with Lens and Maps + if ( + originalPanelState.explicitInput.attributes || + originalPanelState.explicitInput.savedVis + ) { + // If this panel is by value, migrate the state using embeddable migrations + const migratedInput = migrate({ + ...originalPanelState.explicitInput, + type: originalPanelState.type, + }); + // Convert the embeddable state back into the panel shape + newPanels.push( + convertPanelStateToSavedDashboardPanel( + { + ...originalPanelState, + explicitInput: { ...migratedInput, id: migratedInput.id as string }, + }, + version + ) + ); + } else { + newPanels.push(panel); + } + }); + return { + ...doc, + attributes: { + ...attributes, + panelsJSON: JSON.stringify(newPanels), + }, + }; + }; diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts new file mode 100644 index 0000000000000..5a8de73af988b --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SavedObjectAttributes, SavedObjectMigrationFn } from '@kbn/core/server'; + +import { DashboardAttributes, extractReferences, injectReferences } from '../../../common'; +import { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations'; + +/** + * In 7.8.0 we introduced dashboard drilldowns which are stored inside dashboard saved object as part of embeddable state + * In 7.11.0 we created an embeddable references/migrations system that allows to properly extract embeddable persistable state + * https://github.com/elastic/kibana/issues/71409 + * The idea of this migration is to inject all the embeddable panel references and then run the extraction again. + * As the result of the extraction: + * 1. In addition to regular `panel_` we will get new references which are extracted by `embeddablePersistableStateService` (dashboard drilldown references) + * 2. `panel_` references will be regenerated + * All other references like index-patterns are forwarded non touched + * @param deps + */ +export function createExtractPanelReferencesMigration( + deps: DashboardSavedObjectTypeMigrationsDeps +): SavedObjectMigrationFn { + return (doc) => { + const references = doc.references ?? []; + + /** + * Remembering this because dashboard's extractReferences won't return those + * All other references like `panel_` will be overwritten + */ + const oldNonPanelReferences = references.filter((ref) => !ref.name.startsWith('panel_')); + + const injectedAttributes = injectReferences( + { + attributes: doc.attributes as unknown as SavedObjectAttributes, + references, + }, + { embeddablePersistableStateService: deps.embeddable } + ); + + const { attributes, references: newPanelReferences } = extractReferences( + { attributes: injectedAttributes, references: [] }, + { embeddablePersistableStateService: deps.embeddable } + ); + + return { + ...doc, + references: [...oldNonPanelReferences, ...newPanelReferences], + attributes, + }; + }; +} diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_hidden_titles.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_hidden_titles.ts new file mode 100644 index 0000000000000..8a9a917231204 --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_hidden_titles.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SavedObjectMigrationFn } from '@kbn/core/server'; +import { EmbeddableInput } from '@kbn/embeddable-plugin/common'; + +import { + convertSavedDashboardPanelToPanelState, + convertPanelStateToSavedDashboardPanel, + SavedDashboardPanel, +} from '../../../common'; + +/** + * Before 7.10, hidden panel titles were stored as a blank string on the title attribute. In 7.10, this was replaced + * with a usage of the existing hidePanelTitles key. Even though blank string titles still technically work + * in versions > 7.10, they are less explicit than using the hidePanelTitles key. This migration transforms all + * blank string titled panels to panels with the titles explicitly hidden. + */ +export const migrateExplicitlyHiddenTitles: SavedObjectMigrationFn = (doc) => { + const { attributes } = doc; + + // Skip if panelsJSON is missing + if (typeof attributes?.panelsJSON !== 'string') return doc; + + try { + const panels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; + // Same here, prevent failing saved object import if ever panels aren't an array. + if (!Array.isArray(panels)) return doc; + + const newPanels: SavedDashboardPanel[] = []; + panels.forEach((panel) => { + // Convert each panel into the dashboard panel state + const originalPanelState = convertSavedDashboardPanelToPanelState(panel); + newPanels.push( + convertPanelStateToSavedDashboardPanel( + { + ...originalPanelState, + explicitInput: { + ...originalPanelState.explicitInput, + ...(originalPanelState.explicitInput.title === '' && + !originalPanelState.explicitInput.hidePanelTitles + ? { hidePanelTitles: true } + : {}), + }, + }, + panel.version + ) + ); + }); + return { + ...doc, + attributes: { + ...attributes, + panelsJSON: JSON.stringify(newPanels), + }, + }; + } catch { + return doc; + } +}; diff --git a/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_index_pattern_reference.test.ts similarity index 94% rename from src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_index_pattern_reference.test.ts index 13db82790c9d0..a9682bdb8719d 100644 --- a/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_index_pattern_reference.test.ts @@ -7,7 +7,7 @@ */ import type { SavedObjectMigrationContext, SavedObjectMigrationFn } from '@kbn/core/server'; -import { replaceIndexPatternReference } from './replace_index_pattern_reference'; +import { replaceIndexPatternReference } from './migrate_index_pattern_reference'; describe('replaceIndexPatternReference', () => { const savedObjectMigrationContext = null as unknown as SavedObjectMigrationContext; diff --git a/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_index_pattern_reference.ts similarity index 100% rename from src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_index_pattern_reference.ts diff --git a/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_match_all_query.test.ts similarity index 100% rename from src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_match_all_query.test.ts diff --git a/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_match_all_query.ts similarity index 100% rename from src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_match_all_query.ts index 147aa47a7a6e9..a7c1f0ff6bdb5 100644 --- a/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_match_all_query.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { SavedObjectMigrationFn } from '@kbn/core/server'; import { get } from 'lodash'; import { DEFAULT_QUERY_LANGUAGE } from '@kbn/data-plugin/common'; +import { SavedObjectMigrationFn } from '@kbn/core/server'; /** * This migration script is related to: diff --git a/src/plugins/dashboard/public/saved_dashboards/index.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/index.ts similarity index 73% rename from src/plugins/dashboard/public/saved_dashboards/index.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/index.ts index 7a17aa6f2c0e0..2cf0813583235 100644 --- a/src/plugins/dashboard/public/saved_dashboards/index.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export * from '../../common/saved_dashboard_references'; -export * from './saved_dashboard'; -export * from './saved_dashboards'; +export { migrations730 } from './migrations_730'; +export { migrations700 } from './migrations_700'; diff --git a/src/plugins/dashboard/common/migrate_to_730_panels.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.test.ts similarity index 99% rename from src/plugins/dashboard/common/migrate_to_730_panels.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.test.ts index bdcd3bf8cedc0..4eacf9b93d85d 100644 --- a/src/plugins/dashboard/common/migrate_to_730_panels.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.test.ts @@ -8,13 +8,14 @@ import { migratePanelsTo730 } from './migrate_to_730_panels'; import { + SavedDashboardPanel730ToLatest, + RawSavedDashboardPanel640To720, RawSavedDashboardPanelTo60, RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, RawSavedDashboardPanel610, RawSavedDashboardPanel620, -} from './bwc/types'; -import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from './types'; + SavedDashboardPanelTo60, +} from './types'; test('6.0 migrates uiState, sort, scales, and gridData', async () => { const uiState = { diff --git a/src/plugins/dashboard/common/migrate_to_730_panels.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.ts similarity index 98% rename from src/plugins/dashboard/common/migrate_to_730_panels.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.ts index f40240bd7247c..531a0715038d5 100644 --- a/src/plugins/dashboard/common/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrate_to_730_panels.ts @@ -6,25 +6,25 @@ * Side Public License, v 1. */ +import uuid from 'uuid'; +import semverSatisfies from 'semver/functions/satisfies'; + import { i18n } from '@kbn/i18n'; import type { SerializableRecord } from '@kbn/utility-types'; -import semverSatisfies from 'semver/functions/satisfies'; -import uuid from 'uuid'; + import { - GridData, - SavedDashboardPanelTo60, SavedDashboardPanel620, SavedDashboardPanel630, SavedDashboardPanel610, -} from '.'; -import { - RawSavedDashboardPanelTo60, + SavedDashboardPanelTo60, RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, RawSavedDashboardPanel610, RawSavedDashboardPanel620, -} from './bwc/types'; + RawSavedDashboardPanelTo60, + RawSavedDashboardPanel640To720, + RawSavedDashboardPanel730ToLatest, +} from './types'; +import { GridData } from '../../../../common'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; @@ -266,7 +266,6 @@ export function migratePanelsTo730( | RawSavedDashboardPanel620 | RawSavedDashboardPanel630 | RawSavedDashboardPanel640To720 - // We run these on post processed panels too for url BWC | SavedDashboardPanelTo60 | SavedDashboardPanel610 | SavedDashboardPanel620 diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_700.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_700.ts new file mode 100644 index 0000000000000..d1954e8266d88 --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_700.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { get } from 'lodash'; + +import { SavedObjectMigrationFn } from '@kbn/core/server'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; + +import { DashboardDoc700To720 } from './types'; + +function migrateIndexPattern(doc: DashboardDoc700To720) { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + if (typeof searchSourceJSON !== 'string') { + return; + } + let searchSource; + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return; + } + if (searchSource.index) { + searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + doc.references.push({ + name: searchSource.indexRefName, + type: DATA_VIEW_SAVED_OBJECT_TYPE, + id: searchSource.index, + }); + delete searchSource.index; + } + if (searchSource.filter) { + searchSource.filter.forEach((filterRow: any, i: number) => { + if (!filterRow.meta || !filterRow.meta.index) { + return; + } + filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + doc.references.push({ + name: filterRow.meta.indexRefName, + type: DATA_VIEW_SAVED_OBJECT_TYPE, + id: filterRow.meta.index, + }); + delete filterRow.meta.index; + }); + } + doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); +} + +export const migrations700: SavedObjectMigrationFn = (doc): DashboardDoc700To720 => { + // Set new "references" attribute + doc.references = doc.references || []; + + // Migrate index pattern + migrateIndexPattern(doc as DashboardDoc700To720); + // Migrate panels + const panelsJSON = get(doc, 'attributes.panelsJSON'); + if (typeof panelsJSON !== 'string') { + return doc as DashboardDoc700To720; + } + let panels; + try { + panels = JSON.parse(panelsJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc as DashboardDoc700To720; + } + if (!Array.isArray(panels)) { + return doc as DashboardDoc700To720; + } + panels.forEach((panel, i) => { + if (!panel.type || !panel.id) { + return; + } + panel.panelRefName = `panel_${i}`; + doc.references!.push({ + name: `panel_${i}`, + type: panel.type, + id: panel.id, + }); + delete panel.type; + delete panel.id; + }); + doc.attributes.panelsJSON = JSON.stringify(panels); + return doc as DashboardDoc700To720; +}; diff --git a/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.test.ts similarity index 96% rename from src/plugins/dashboard/server/saved_objects/migrations_730.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.test.ts index 2fc999b067496..6314546b5933a 100644 --- a/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.test.ts @@ -7,12 +7,17 @@ */ import { savedObjectsServiceMock } from '@kbn/core/server/mocks'; -import { createDashboardSavedObjectTypeMigrations } from './dashboard_migrations'; -import { migrations730 } from './migrations_730'; -import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common'; -import { RawSavedDashboardPanel730ToLatest } from '../../common'; import { createEmbeddableSetupMock } from '@kbn/embeddable-plugin/server/mocks'; +import { + DashboardDocPre700, + DashboardDoc700To720, + DashboardDoc730ToLatest, + RawSavedDashboardPanel730ToLatest, +} from './types'; +import { migrations730 } from './migrations_730'; +import { createDashboardSavedObjectTypeMigrations } from '../dashboard_saved_object_migrations'; + const mockContext = savedObjectsServiceMock.createMigrationContext(); const migrations = createDashboardSavedObjectTypeMigrations({ embeddable: createEmbeddableSetupMock(), diff --git a/src/plugins/dashboard/server/saved_objects/migrations_730.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts similarity index 69% rename from src/plugins/dashboard/server/saved_objects/migrations_730.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts index 3cc34d8513a45..dcd1c2f2cb878 100644 --- a/src/plugins/dashboard/server/saved_objects/migrations_730.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts @@ -7,11 +7,38 @@ */ import { inspect } from 'util'; -import { SavedObjectMigrationContext } from '@kbn/core/server'; -import { DashboardDoc730ToLatest } from '../../common'; -import { isDashboardDoc } from './is_dashboard_doc'; +import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; + import { moveFiltersToQuery } from './move_filters_to_query'; -import { migratePanelsTo730, DashboardDoc700To720 } from '../../common'; +import { migratePanelsTo730 } from './migrate_to_730_panels'; +import { DashboardDoc730ToLatest, DashboardDoc700To720 } from './types'; + +function isDoc( + doc: { [key: string]: unknown } | SavedObjectUnsanitizedDoc +): doc is SavedObjectUnsanitizedDoc { + return ( + typeof doc.id === 'string' && + typeof doc.type === 'string' && + doc.attributes !== null && + typeof doc.attributes === 'object' && + doc.references !== null && + typeof doc.references === 'object' + ); +} + +export function isDashboardDoc( + doc: { [key: string]: unknown } | DashboardDoc730ToLatest +): doc is DashboardDoc730ToLatest { + if (!isDoc(doc)) { + return false; + } + + if (typeof (doc as DashboardDoc730ToLatest).attributes.panelsJSON !== 'string') { + return false; + } + + return true; +} export const migrations730 = (doc: DashboardDoc700To720, { log }: SavedObjectMigrationContext) => { if (!isDashboardDoc(doc)) { diff --git a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/move_filters_to_query.test.ts similarity index 100% rename from src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/move_filters_to_query.test.ts diff --git a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/move_filters_to_query.ts similarity index 100% rename from src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts rename to src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/move_filters_to_query.ts diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/readme.md b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/readme.md new file mode 100644 index 0000000000000..50f1b3283ca3f --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/readme.md @@ -0,0 +1,3 @@ +## Legacy Pre 7.3 Migrations + +This folder contains legacy migrations that migrate dashboard saved object from any previous version into Kibana 7.3.0. The migrations in this folder need to be able to handle state from any older version of dashboard from as early as 5.0 because Saved Object Migrations did not exist, and in-place migrations were used instead. After 7.3.0, saved object migrations are in place, so it can be assumed that any saved migration that is registered there will receive state from the version before. diff --git a/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/types.ts b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/types.ts new file mode 100644 index 0000000000000..2257b05c0a64e --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/types.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Serializable } from '@kbn/utility-types'; +import { SavedObjectReference } from '@kbn/core/server'; + +import type { + GridData, + DashboardAttributes as CurrentDashboardAttributes, // Dashboard attributes from common are the source of truth for the current version. +} from '../../../../common'; + +interface SavedObjectAttributes { + kibanaSavedObjectMeta: { + searchSourceJSON: string; + }; +} + +interface Doc { + references: SavedObjectReference[]; + attributes: Attributes; + id: string; + type: string; +} + +interface DocPre700 { + attributes: Attributes; + id: string; + type: string; +} + +interface DashboardAttributesTo720 extends SavedObjectAttributes { + panelsJSON: string; + description: string; + uiStateJSON?: string; + version: number; + timeRestore: boolean; + useMargins?: boolean; + title: string; + optionsJSON?: string; +} + +export type DashboardDoc730ToLatest = Doc; + +export type DashboardDoc700To720 = Doc; + +export type DashboardDocPre700 = DocPre700; + +// Note that these types are prefixed with `Raw` because there are some post processing steps +// that happen before the saved objects even reach the client. Namely, injecting type and id +// parameters back into the panels, where the raw saved objects actually have them stored elsewhere. +// +// Ideally, everywhere in the dashboard code would use references at the top level instead of +// embedded in the panels. The reason this is stored at the top level is so the references can be uniformly +// updated across all saved object types that have references. + +// Starting in 7.3 we introduced the possibility of embeddables existing without an id +// parameter. If there was no id, then type remains on the panel. So it either will have a name, +// or a type property. +export type RawSavedDashboardPanel730ToLatest = Pick< + RawSavedDashboardPanel640To720, + Exclude +> & { + // Should be either type, and not name (not backed by a saved object), or name but not type (backed by a + // saved object and type and id are stored on references). Had trouble with oring the two types + // because of optional properties being marked as required: https://github.com/microsoft/TypeScript/issues/20722 + readonly type?: string; + readonly name?: string; + + panelIndex: string; + panelRefName?: string; +}; + +// NOTE!! +// All of these types can actually exist in 7.2! The names are pretty confusing because we did +// in place migrations for so long. For example, `RawSavedDashboardPanelTo60` is what a panel +// created in 6.0 will look like after it's been migrated up to 7.2, *not* what it would look like in 6.0. +// That's why it actually doesn't have id or type, but has a name property, because that was a migration +// added in 7.0. + +// Hopefully since we finally have a formal saved object migration system and we can do less in place +// migrations, this will be easier to understand moving forward. + +// Starting in 6.4 we added an in-place edit on panels to remove columns and sort properties and put them +// inside the embeddable config (https://github.com/elastic/kibana/pull/17446). +// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in +// this shape in v 7.2. +export type RawSavedDashboardPanel640To720 = Pick< + RawSavedDashboardPanel630, + Exclude +>; + +// In 6.3.0 we expanded the number of grid columns and rows: https://github.com/elastic/kibana/pull/16763 +// We added in-place migrations to multiply older x,y,h,w numbers. Note the typescript shape here is the same +// because it's just multiplying existing fields. +// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in 7.2 +// that need to be modified. +export type RawSavedDashboardPanel630 = RawSavedDashboardPanel620; + +// In 6.2 we added an inplace migration, moving uiState into each panel's new embeddableConfig property. +// Source: https://github.com/elastic/kibana/pull/14949 +export type RawSavedDashboardPanel620 = RawSavedDashboardPanel610 & { + embeddableConfig: { [key: string]: Serializable }; + version: string; +}; + +// In 6.1 we switched from an angular grid to react grid layout (https://github.com/elastic/kibana/pull/13853) +// This used gridData instead of size_x, size_y, row and col. We also started tracking the version this panel +// was created in to make future migrations easier. +// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in +// this shape in v 7.2. +export type RawSavedDashboardPanel610 = Pick< + RawSavedDashboardPanelTo60, + Exclude +> & { gridData: GridData; version: string }; + +export interface RawSavedDashboardPanelTo60 { + readonly columns?: string[]; + readonly sort?: string; + readonly size_x?: number; + readonly size_y?: number; + readonly row: number; + readonly col: number; + panelIndex?: number | string; // earlier versions allowed this to be number or string. Some very early versions seem to be missing this entirely + readonly name: string; + + // This is where custom panel titles are stored prior to Embeddable API v2 + title?: string; +} + +export type SavedDashboardPanel640To720 = Pick< + RawSavedDashboardPanel640To720, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel630 = Pick< + RawSavedDashboardPanel630, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel620 = Pick< + RawSavedDashboardPanel620, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel610 = Pick< + RawSavedDashboardPanel610, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanelTo60 = Pick< + RawSavedDashboardPanelTo60, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +// id becomes optional starting in 7.3.0 +export type SavedDashboardPanel730ToLatest = Pick< + RawSavedDashboardPanel730ToLatest, + Exclude +> & { + readonly id?: string; + readonly type: string; +}; diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts index ea981be3515f8..25a4986208d31 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedDashboardPanel730ToLatest } from '../../common'; +import { SavedDashboardPanel } from '../../common'; import { getEmptyDashboardData, collectPanelsByType } from './dashboard_telemetry'; import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; @@ -18,7 +18,7 @@ const visualizationType1ByValue = { }, }, type: 'visualization', -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const visualizationType2ByValue = { embeddableConfig: { @@ -27,7 +27,7 @@ const visualizationType2ByValue = { }, }, type: 'visualization', -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const visualizationType2ByReference = { ...visualizationType2ByValue, @@ -41,7 +41,7 @@ const lensTypeAByValue = { visualizationType: 'a', }, }, -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const lensTypeAByReference = { ...lensTypeAByValue, @@ -60,7 +60,7 @@ const lensXYSeriesA = { }, }, }, -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const lensXYSeriesB = { type: 'lens', @@ -90,7 +90,7 @@ const lensXYSeriesB = { }, }, }, -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const embeddablePersistableStateService = createEmbeddablePersistableStateServiceMock(); diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts index ce41c50834689..1e8a0192ec38a 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts @@ -16,7 +16,7 @@ import { } from '@kbn/controls-plugin/common'; import { initializeControlGroupTelemetry } from '@kbn/controls-plugin/server'; import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; -import { SavedDashboardPanel730ToLatest } from '../../common'; +import type { SavedDashboardPanel } from '../../common'; import { TASK_ID, DashboardTelemetryTaskState } from './dashboard_telemetry_collection_task'; export interface DashboardCollectorData { panels: { @@ -55,7 +55,7 @@ export const getEmptyPanelTypeData = () => ({ }); export const collectPanelsByType = ( - panels: SavedDashboardPanel730ToLatest[], + panels: SavedDashboardPanel[], collectorData: DashboardCollectorData, embeddableService: EmbeddablePersistableStateService ) => { diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts index 823b5fadaaeae..1ca13b4308586 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts @@ -14,17 +14,13 @@ import { TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; -import { SavedDashboardPanel730ToLatest } from '../../common'; -import { - injectReferences, - SavedObjectAttributesAndReferences, -} from '../../common/saved_dashboard_references'; import { controlsCollectorFactory, collectPanelsByType, getEmptyDashboardData, DashboardCollectorData, } from './dashboard_telemetry'; +import { injectReferences, SavedDashboardPanel } from '../../common'; // This task is responsible for running daily and aggregating all the Dashboard telemerty data // into a single document. This is an effort to make sure the load of fetching/parsing all of the @@ -32,6 +28,11 @@ import { const TELEMETRY_TASK_TYPE = 'dashboard_telemetry'; export const TASK_ID = `Dashboard-${TELEMETRY_TASK_TYPE}`; +interface SavedObjectAttributesAndReferences { + attributes: SavedObjectAttributes; + references: SavedObjectReference[]; +} + export interface DashboardTelemetryTaskState { runs: number; telemetry: DashboardCollectorData; @@ -102,7 +103,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: try { const panels = JSON.parse( attributes.panelsJSON as string - ) as unknown as SavedDashboardPanel730ToLatest[]; + ) as unknown as SavedDashboardPanel[]; collectPanelsByType(panels, dashboardData, embeddable); } catch (e) { diff --git a/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts b/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts index 8a3cdd71539f8..c5e8da8acbd4a 100644 --- a/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts +++ b/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedDashboardPanel730ToLatest } from '../../common'; +import type { SavedDashboardPanel } from '../../common'; import { findByValueEmbeddables } from './find_by_value_embeddables'; const visualizationByValue = { @@ -14,18 +14,18 @@ const visualizationByValue = { value: 'visualization-by-value', }, type: 'visualization', -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const mapByValue = { embeddableConfig: { value: 'map-by-value', }, type: 'map', -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; const embeddableByRef = { panelRefName: 'panel_ref_1', -} as unknown as SavedDashboardPanel730ToLatest; +} as unknown as SavedDashboardPanel; describe('findByValueEmbeddables', () => { it('finds the by value embeddables for the given type', async () => { diff --git a/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts b/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts index 694fe2007f844..502ba828944d4 100644 --- a/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts +++ b/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts @@ -7,7 +7,7 @@ */ import { ISavedObjectsRepository, SavedObjectAttributes } from '@kbn/core/server'; -import { SavedDashboardPanel730ToLatest } from '../../common'; +import type { SavedDashboardPanel } from '../../common'; export const findByValueEmbeddables = async ( savedObjectClient: Pick, @@ -22,7 +22,7 @@ export const findByValueEmbeddables = async ( try { return JSON.parse( dashboard.attributes.panelsJSON as string - ) as unknown as SavedDashboardPanel730ToLatest[]; + ) as unknown as SavedDashboardPanel[]; } catch (exception) { return []; } diff --git a/test/functional/apps/dashboard/group3/bwc_shared_urls.ts b/test/functional/apps/dashboard/group3/bwc_shared_urls.ts index 35d13b715c14c..0b06e28af0f11 100644 --- a/test/functional/apps/dashboard/group3/bwc_shared_urls.ts +++ b/test/functional/apps/dashboard/group3/bwc_shared_urls.ts @@ -12,9 +12,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header']); - const dashboardExpect = getService('dashboardExpect'); - const pieChart = getService('pieChart'); - const elasticChart = getService('elasticChart'); + const toasts = getService('toasts'); const browser = getService('browser'); const log = getService('log'); const queryBar = getService('queryBar'); @@ -42,11 +40,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `legendOpen:!t))),` + `viewMode:edit)`; - const enableNewChartLibraryDebug = async () => { - await elasticChart.setNewChartUiDebugFlag(); - await queryBar.submitQuery(); - }; - describe('bwc shared urls', function describeIndexTests() { before(async function () { await PageObjects.dashboard.initTests(); @@ -81,13 +74,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); await PageObjects.header.waitUntilLoadingHasFinished(); - await elasticChart.setNewChartUiDebugFlag(true); const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); - await pieChart.expectEmptyPieChart(); - await dashboardExpect.panelCount(2); + const warningToast = await toasts.getToastElement(1); + expect(await warningToast.getVisibleText()).to.contain('Cannot load panels'); + await PageObjects.dashboard.waitForRenderComplete(); }); }); @@ -99,15 +92,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const url = `${kibanaLegacyBaseUrl}#/dashboard?${urlQuery}`; log.debug(`Navigating to ${url}`); await browser.get(url, true); - enableNewChartLibraryDebug(); await PageObjects.header.waitUntilLoadingHasFinished(); const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); - await pieChart.expectPieSliceCount(5); - await dashboardExpect.panelCount(2); + const warningToast = await toasts.getToastElement(1); + expect(await warningToast.getVisibleText()).to.contain('Cannot load panels'); await PageObjects.dashboard.waitForRenderComplete(); - await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); }); it('loads a saved dashboard', async function () { @@ -120,15 +111,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); await PageObjects.header.waitUntilLoadingHasFinished(); - enableNewChartLibraryDebug(); const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); - await pieChart.expectPieSliceCount(5); - await dashboardExpect.panelCount(2); await PageObjects.dashboard.waitForRenderComplete(); - await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); }); it('loads a saved dashboard with query via dashboard_no_match', async function () { @@ -143,7 +130,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const query = await queryBar.getQueryString(); expect(query).to.equal('boop'); - await dashboardExpect.panelCount(2); await PageObjects.dashboard.waitForRenderComplete(); }); @@ -154,33 +140,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); - await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.header.waitUntilLoadingHasFinished(); - await dashboardExpect.selectedLegendColorCount('#000000', 5); }); it('back button works for old dashboards after state migrations', async () => { await PageObjects.dashboard.preserveCrossAppState(); const oldId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); await PageObjects.dashboard.waitForRenderComplete(); - await dashboardExpect.selectedLegendColorCount('#000000', 5); const url = `${kibanaLegacyBaseUrl}#/dashboard?${urlQuery}`; log.debug(`Navigating to ${url}`); await browser.get(url); - await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); - await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); await browser.goBack(); await PageObjects.header.waitUntilLoadingHasFinished(); const newId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); expect(newId).to.be.equal(oldId); await PageObjects.dashboard.waitForRenderComplete(); - await elasticChart.setNewChartUiDebugFlag(true); await queryBar.submitQuery(); - await dashboardExpect.selectedLegendColorCount('#000000', 5); }); }); }); diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index bc3f2ed2774a0..2c79f1fd61d23 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import chroma from 'chroma-js'; -import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/public/application/embeddable/dashboard_constants'; +import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/public/dashboard_constants'; import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../../page_objects/dashboard_page'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/test/functional/apps/dashboard/group4/dashboard_empty.ts b/test/functional/apps/dashboard/group4/dashboard_empty.ts index fe5a74bebbc25..02437b0685694 100644 --- a/test/functional/apps/dashboard/group4/dashboard_empty.ts +++ b/test/functional/apps/dashboard/group4/dashboard_empty.ts @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); // create the new data view from the dashboards/create route in order to test that the dashboard is loaded properly as soon as the data view is created... - await PageObjects.common.navigateToUrl('dashboard', '/create'); + await PageObjects.common.navigateToApp('dashboard', { hash: '/create' }); const button = await testSubjects.find('createDataViewButton'); button.click(); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 7a579f4e4f84b..bad1f6bdebf2b 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -12,6 +12,7 @@ export const LINE_CHART_VIS_NAME = 'Visualization漢字 LineChart'; import expect from '@kbn/expect'; import { FtrService } from '../ftr_provider_context'; +import { CommonPageObject } from './common_page'; interface SaveDashboardOptions { /** @@ -430,6 +431,31 @@ export class DashboardPageObject extends FtrService { await this.switchToEditMode(); } + public async gotoDashboardURL({ + id, + args, + editMode, + }: { + id?: string; + editMode?: boolean; + args?: Parameters['navigateToActualUrl']>[2]; + } = {}) { + let dashboardLocation = `/create`; + if (id) { + const edit = editMode ? `?_a=(viewMode:edit)` : ''; + dashboardLocation = `/view/${id}${edit}`; + } + this.common.navigateToActualUrl('dashboard', dashboardLocation, args); + } + + public async gotoDashboardListingURL({ + args, + }: { + args?: Parameters['navigateToActualUrl']>[2]; + } = {}) { + await this.common.navigateToActualUrl('dashboard', '/list', args); + } + public async renameDashboard(dashboardName: string) { this.log.debug(`Naming dashboard ` + dashboardName); await this.testSubjects.click('dashboardRenameButton'); diff --git a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_swimlane_to_dashboard_controls.tsx b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_swimlane_to_dashboard_controls.tsx index 7f109defdff79..93fa7e908f35a 100644 --- a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_swimlane_to_dashboard_controls.tsx +++ b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_swimlane_to_dashboard_controls.tsx @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { DashboardSavedObject } from '@kbn/dashboard-plugin/public'; +import { DashboardAttributes } from '@kbn/dashboard-plugin/common'; import type { Query } from '@kbn/es-query'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { getDefaultSwimlanePanelTitle } from '../../../embeddables/anomaly_swimlane/anomaly_swimlane_embeddable'; @@ -30,7 +30,7 @@ export interface DashboardItem { id: string; title: string; description: string | undefined; - attributes: DashboardSavedObject; + attributes: DashboardAttributes; } export type EuiTableProps = EuiInMemoryTableProps; diff --git a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/use_dashboards_table.tsx b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/use_dashboards_table.tsx index ac023017d43f5..8bb7705938d84 100644 --- a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/use_dashboards_table.tsx +++ b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/use_dashboards_table.tsx @@ -8,7 +8,7 @@ import type { EuiInMemoryTableProps } from '@elastic/eui'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { debounce } from 'lodash'; -import type { DashboardSavedObject } from '@kbn/dashboard-plugin/public'; +import type { DashboardAttributes } from '@kbn/dashboard-plugin/common'; import { useDashboardService } from '../../services/dashboard_service'; import { useMlKibana } from '../../contexts/kibana'; @@ -16,7 +16,7 @@ export interface DashboardItem { id: string; title: string; description: string | undefined; - attributes: DashboardSavedObject; + attributes: DashboardAttributes; } export type EuiTableProps = EuiInMemoryTableProps; diff --git a/x-pack/plugins/ml/public/application/services/dashboard_service.ts b/x-pack/plugins/ml/public/application/services/dashboard_service.ts index ff7b696551f41..abd97722f86bc 100644 --- a/x-pack/plugins/ml/public/application/services/dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/services/dashboard_service.ts @@ -7,7 +7,8 @@ import { SavedObjectsClientContract } from '@kbn/core/public'; import { useMemo } from 'react'; -import { DashboardSavedObject, DashboardAppLocator } from '@kbn/dashboard-plugin/public'; +import { DashboardAppLocator } from '@kbn/dashboard-plugin/public'; +import type { DashboardAttributes } from '@kbn/dashboard-plugin/common'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { useMlKibana } from '../contexts/kibana'; @@ -22,7 +23,7 @@ export function dashboardServiceProvider( * Fetches dashboards */ async fetchDashboards(query?: string) { - return await savedObjectClient.find({ + return await savedObjectClient.find({ type: 'dashboard', perPage: 1000, search: query ? `${query}*` : '', diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ce86f1de798de..612838bf5ea5f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -988,7 +988,6 @@ "dashboard.panel.copyToDashboard.goToDashboard": "Copier et accéder au tableau de bord", "dashboard.panel.copyToDashboard.newDashboardOptionLabel": "Nouveau tableau de bord", "dashboard.panel.copyToDashboard.title": "Copier dans le tableau de bord", - "dashboard.panel.invalidData": "Données non valides dans l'url", "dashboard.panel.LibraryNotification": "Notification de la bibliothèque Visualize", "dashboard.panel.libraryNotification.ariaLabel": "Afficher les informations de la bibliothèque et dissocier ce panneau", "dashboard.panel.libraryNotification.toolTip": "La modification de ce panneau pourrait affecter d’autres tableaux de bord. Pour modifier ce panneau uniquement, dissociez-le de la bibliothèque.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5b56cc076172c..1ace6b0a76659 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -986,7 +986,6 @@ "dashboard.panel.copyToDashboard.goToDashboard": "コピーしてダッシュボードを開く", "dashboard.panel.copyToDashboard.newDashboardOptionLabel": "新規ダッシュボード", "dashboard.panel.copyToDashboard.title": "ダッシュボードにコピー", - "dashboard.panel.invalidData": "URL の無効なデータ", "dashboard.panel.LibraryNotification": "Visualize ライブラリ通知", "dashboard.panel.libraryNotification.ariaLabel": "ライブラリ情報を表示し、このパネルのリンクを解除します", "dashboard.panel.libraryNotification.toolTip": "このパネルを編集すると、他のダッシュボードに影響する場合があります。このパネルのみを変更するには、ライブラリからリンクを解除します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 09fb50c164e9c..2cdc7fa516263 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -988,7 +988,6 @@ "dashboard.panel.copyToDashboard.goToDashboard": "复制并前往仪表板", "dashboard.panel.copyToDashboard.newDashboardOptionLabel": "新仪表板", "dashboard.panel.copyToDashboard.title": "复制到仪表板", - "dashboard.panel.invalidData": "url 中的数据无效", "dashboard.panel.LibraryNotification": "可视化库通知", "dashboard.panel.libraryNotification.ariaLabel": "查看库信息并取消链接此面板", "dashboard.panel.libraryNotification.toolTip": "编辑此面板可能会影响其他仪表板。要仅更改此面板,请取消其与库的链接。", diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 8f7fc4fe73062..3a33f14d682a6 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -6,10 +6,6 @@ */ import expect from '@kbn/expect'; -import { - createDashboardEditUrl, - DashboardConstants, -} from '@kbn/dashboard-plugin/public/dashboard_constants'; import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { @@ -32,6 +28,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const savedQueryManagementComponent = getService('savedQueryManagementComponent'); const kbnServer = getService('kibanaServer'); + const navigationArgs = { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }; + describe('dashboard feature controls security', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); @@ -97,14 +98,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`landing page shows "Create new Dashboard" button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardListingURL({ + args: navigationArgs, + }); await testSubjects.existOrFail('dashboardLandingPage', { timeout: config.get('timeouts.waitFor'), }); @@ -116,28 +112,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`create new dashboard shows addNew button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ args: navigationArgs }); await testSubjects.existOrFail('emptyDashboardWidget', { timeout: config.get('timeouts.waitFor'), }); }); it(`can view existing Dashboard`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-exist'), - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ + id: 'i-exist', + args: navigationArgs, + }); await testSubjects.existOrFail('embeddablePanelHeading-APie', { timeout: config.get('timeouts.waitFor'), }); @@ -307,14 +292,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardListingURL({ + args: navigationArgs, + }); await testSubjects.existOrFail('dashboardLandingPage', { timeout: config.get('timeouts.waitFor'), }); @@ -322,38 +302,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`shows read-only badge`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardListingURL({ + args: navigationArgs, + }); await globalNav.badgeExistsOrFail('Read only'); }); it(`create new dashboard shows the read only warning`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ + args: navigationArgs, + }); await testSubjects.existOrFail('dashboardEmptyReadOnly', { timeout: 20000 }); }); it(`can view existing Dashboard`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-exist'), - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ + id: 'i-exist', + args: navigationArgs, + }); await testSubjects.existOrFail('embeddablePanelHeading-APie', { timeout: config.get('timeouts.waitFor'), }); @@ -438,14 +404,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardListingURL({ + args: navigationArgs, + }); await testSubjects.existOrFail('dashboardLandingPage', { timeout: 10000 }); await testSubjects.missingOrFail('newItemButton'); }); @@ -455,26 +416,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`create new dashboard shows the read only warning`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ + args: navigationArgs, + }); await testSubjects.existOrFail('dashboardEmptyReadOnly', { timeout: 20000 }); }); it(`can view existing Dashboard`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-exist'), - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ id: 'i-exist', args: navigationArgs }); await testSubjects.existOrFail('embeddablePanelHeading-APie', { timeout: 10000 }); }); @@ -552,50 +501,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`landing page shows 403`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardListingURL({ + args: navigationArgs, + }); await PageObjects.error.expectForbidden(); }); it(`create new dashboard shows 403`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ args: navigationArgs }); await PageObjects.error.expectForbidden(); }); it(`edit dashboard for object which doesn't exist shows 403`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-dont-exist'), - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ id: 'i-dont-exist', args: navigationArgs }); await PageObjects.error.expectForbidden(); }); it(`edit dashboard for object which exists shows 403`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-exist'), - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.dashboard.gotoDashboardURL({ id: 'i-exist', args: navigationArgs }); await PageObjects.error.expectForbidden(); }); }); diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts index a702b7a716a15..ef8c83ec667ba 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts @@ -6,10 +6,6 @@ */ import expect from '@kbn/expect'; -import { - createDashboardEditUrl, - DashboardConstants, -} from '@kbn/dashboard-plugin/public/dashboard_constants'; import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { @@ -53,15 +49,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`landing page shows "Create new Dashboard" button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.LANDING_PAGE_PATH, - { + await PageObjects.dashboard.gotoDashboardListingURL({ + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await testSubjects.existOrFail('dashboardLandingPage', { timeout: config.get('timeouts.waitFor'), }); @@ -69,30 +63,27 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`create new dashboard shows addNew button`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { + await PageObjects.dashboard.gotoDashboardURL({ + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await testSubjects.existOrFail('emptyDashboardWidget', { timeout: config.get('timeouts.waitFor'), }); }); it(`can view existing Dashboard`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('8fba09d8-df3f-5aa1-83cc-65f7fbcbc0d9'), - { + await PageObjects.dashboard.gotoDashboardURL({ + id: '8fba09d8-df3f-5aa1-83cc-65f7fbcbc0d9', + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await testSubjects.existOrFail('embeddablePanelHeading-APie', { timeout: config.get('timeouts.waitFor'), }); @@ -125,41 +116,37 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`create new dashboard shows 404`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - DashboardConstants.CREATE_NEW_DASHBOARD_URL, - { + await PageObjects.dashboard.gotoDashboardURL({ + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await PageObjects.error.expectNotFound(); }); it(`edit dashboard for object which doesn't exist shows 404`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-dont-exist'), - { + await PageObjects.dashboard.gotoDashboardURL({ + id: 'i-dont-exist', + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await PageObjects.error.expectNotFound(); }); it(`edit dashboard for object which exists shows 404`, async () => { - await PageObjects.common.navigateToActualUrl( - 'dashboard', - createDashboardEditUrl('i-exist'), - { + await PageObjects.dashboard.gotoDashboardURL({ + id: 'i-exist', + args: { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, - } - ); + }, + }); await PageObjects.error.expectNotFound(); }); }); From 670b6adb3ee48890d5d9d0bcff7ef48cd75c7571 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Wed, 28 Sep 2022 15:10:51 -0500 Subject: [PATCH 143/172] [DOCS] Adds time slider control (#141832) * [DOCS] Adds time slider control * Review comment Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dashboard_timeSliderControl_8.5.0.gif | Bin 0 -> 1196260 bytes ...imeSliderControl_advanceBackward_8.5.0.png | Bin 0 -> 1522 bytes ...timeSliderControl_advanceForward_8.5.0.png | Bin 0 -> 1531 bytes ...hboard_timeSliderControl_animate_8.5.0.png | Bin 0 -> 1559 bytes .../make-dashboards-interactive.asciidoc | 55 ++++++++++++++---- 5 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 docs/user/dashboard/images/dashboard_timeSliderControl_8.5.0.gif create mode 100644 docs/user/dashboard/images/dashboard_timeSliderControl_advanceBackward_8.5.0.png create mode 100644 docs/user/dashboard/images/dashboard_timeSliderControl_advanceForward_8.5.0.png create mode 100644 docs/user/dashboard/images/dashboard_timeSliderControl_animate_8.5.0.png diff --git a/docs/user/dashboard/images/dashboard_timeSliderControl_8.5.0.gif b/docs/user/dashboard/images/dashboard_timeSliderControl_8.5.0.gif new file mode 100644 index 0000000000000000000000000000000000000000..89ca09dccc71e321a95088334240cef55405facc GIT binary patch literal 1196260 zcmWh!byyQ#7hcf}28@s#NSC9#1!+ciDJmlBK%@lSMt2D!=Fx z=)T`~{UfS#U_i7BeoT-Msg0bmbkax!*t@yK?ubn)=<^73Z&E*JN~ z(>`qS^?gG1sIvf#rts@$_irD3>i0Ati0;_{Mc_D1;0Sf_L|E9%x|abt*f2rd1P&K< zE21STI$kMe=wn=VLPAnl1DufDu`ppv!L zk(HD6Iw$XSTHoupIXU^Ixw)BnFRb$yeDa6=^3w_miir5~z`~K4!cejpFWxVFdXN3|K6k3V?V!GIqM>rXp{ud6d8e_dr>SwEsXVv2$h+ldb4z<$O9iO~ zzu8)~)YcW;e%#XD`LX>~UPo1NXH`k(o6gRz#IA2uU0F3vH&F${(Der6Q>l=98-~6_}tfs%Lr9WqUaAbP0Z(wM2Xryg^q?t4__G9GP zk5S_6*x2@1(f6^0>haFn@v5=$i7(^%hvU`p6aQK!x(g>ic21TLPZmv1&3v0`shU3j zIo&il^SSC%`^=}$v!6!a%#tf+2RdfIcYPik_}t$(*U>rGKRdTr`(?D@%jo=>~#l@Ai#i{R$3uQ|qwM)d#rHRHB;>pU;mCcmJFA54R6~OdXQ{9`2D3&kl}GPmd1v zzF(YtKRG)2`QLE%WB>BU-1pOq)6<{-PLC_kiZ^~9ZTuvk{rr7-{-^G;^8C-=u+EnqW3{`BLZ`@pY>$$_> z&y1nwsxR+7`r>&^vLxqlj-M)B6P~R)Hu-Lh=Bu&Sc83I??yZcqzIX2qXuQ0>H}#0r z7gHndHa;&p_q@=n+(V{ac55t4?#^m;yWF>_0^8%=Tg3d`PvvG2CgH02odKtmAMTlT zwj3^%dol(da<+c|O0ax=eNnCLWVw+`!hKR**v^kb-SDk>cgN5D`NoG|l7V`axxv>u z$Sm%`>f`?ZW|+3$NNbp!PHc?5{mQgoK_{TQ$u5J_qWLR$HT3nxC99z>6@*t5gyW|k z_Q3r9Tjmh~Jh#WcybQtgu!Ea#t?CB#{7P3l58lZL6@IsA?t9j}vz`F;{5(!^8RxNq zqmG9mXq3)(Hd3^HPz$E&GQ9ap5xo5BwyuCcqJn~+JP~!Prk2^1U zQd1)tcs4n7skk;dHj~u$g7G;@=SsS7)OCaJya8WwfGC3l42eSj4%XwfUe}2hsg>s4 zM8(RcwFxTktl0>etUY?LTRJw9t&_y>jn$=p=#gL+qwi69M3d`u zm_=&9|wW7z~m)cv`0 z1eZ^S9^#R`FT*a4h|>1Tg^wTX=bS{eK{VQVsiU&jrg%s$esKy>7a27Qh*#@VtXlLP zW70u4U1_gVgwIqrQeaJpJa6H>eT>q<&avPrw)9@Nb6FjwT zOmAIIK=`P(^mFy7SNkcSAuFAyw28-cXCjGUP%>q$%X++o@5QOEN)TKRri?NE`*d28 z_TX~g=t1SzYRPK;NH*>#saQ5Y7c`cwtbf{6&I~e>$TmSeJ>JLOUu}fwJzZ@$35GDo z(w0F4`o(&Nm~J7asAe>(2ZeVFw3YxqX3cTB5=*Ra13WLa`D?#WFbpW4^wOHUr0LR{ zlXUns({LHto=#Dsz06t15z?>lkTB(Zy7O~zMD`TBwq-XmsntRWRvcrBRFCzf*(WldjrZLt$W zf5Z<~57S8sj_ngWhSADbTHcv=-e5m!8@le5LXk7C{lY&`^QHPegYgFb&(2ANuJRhQ zKFrXwR-Ys2xQ7)%&;j3fz)94tQIP5=8FFl6rQ7;Ii5`7CwS)*}Nud9TKbCQ9&Tu*@ z2^C+o$m((&VMX%5MwNSr-E^QWlLAs<@N5ho4iFJQA)PM?%d&iegCmK2m^wncpi=5+ zW(3rvPLS~kX2N~2PPIhp5t*}VW#{n9{-bNZUpGvidNF!4@V5?DIGrG4S+jSW(VBy@ zS_B;Z!IVWZ)tpWN8@!!M^3=^eanGs6#GulMuW*8A9I@L+>K3agBMDP4w=sJXHZ=Vsv@`B4}YxthwiQ-pLQqoIQ#c z-qEG%XpLq&%bFVZ{Tr=`oecvtz_pox7qCH*fS7+UIL%nojI{h1#&Z)tcvVPPlt zXMvcksE3g{^l{FeK2?6K(lT-Q_^C5*Gqt{WzuA#rnE-3Ea!l)&?!$n#KP$({tzF;D zzK1UTS-qHS{qSqz`-?w+zW%+}N?5A-j-?CxN_k(Ym*wa%N-l4S;bpLjz&~`n-10ij zP+O0JCOVmq`zz&}7$rfsWc2a|^$OXjeN+a3z204B7jyh( z4YOW7N(w;UhAr)!t84E96srlm!cTCOFM?9EQ$4`ST)JAlGAZnJKoBQ^yOnh+{n6Q! z@sUH7jNgMKftxk=|8ev)6Z{ec5^p22uM2kSZ~IhE{+qE>Y)v(cAe0eHyL9D^$!KurW>#e|sOmvlli z@^XMZ8-lFuL(A?1s8JB{%wQ-7v%Lpp>=4zZKZQO@Y6}B?ivjO?Sb!~Q7lx>UkkmnF zTl-YePY?zZ0+k5~V%bP%Jxg&9O}COB%6A6ZMbp>=pe6)rF{J1@z(TGKv`S*sM~cb} zQtIo&B#^YNMEOJ%{bx8=F@V8s0NfVk?(&q%JT<&F)%sLdYV>|I&>Iuv0liTNwd@0D zZP0SoL3M?wqR1#}Wddb=4`nTpA`t~)vZ3v4!U>*Wb`iQn!lOeJgc^;#mK~D&i{ktk zTZsb}-3op84N&R;xlxA8+hIDwnEUI+=%+GdZ7_mMo(mCGQozyGC#Ba!f=B~!2mE3Q@+R1&IGoBC{k$M zAY&rISj7k!CUu>$7y!Z*X_pef`XmN_6zJUs19X;RR2k-COFyP0eS-v{qXrEe)A%Cf zJMqt>Jd*uWUuuyUOo%rqdZ?=IFzRun==D+kDwlqZe>LHiIVH;|M4;S7hHHXBtt2P{ zNnOu`5r!nC6w5ypvN1=2TJd@Z^0c2J%3yCOlG@=dO7*^ShQJAC>zF1H>A&(WAh8Zw zOJrCF1S(}n@YabZcm=MdGPDAqg>M*OF>eIcl=iG>!(bMPFfmdp0|}-xMx_@4P<#sz zO}S+kaV*yfco{_n)xN2}G$;k;PZRN@DNSV{rZR+Kc;}FeHgI=JPpCf`%{&+!SMez` z(Svcok=}NTAp%7`I6!rTqW5lxMj)vV*BH#R!?uW$IsM54cX{)~6e36rq+x0b2Zr%< zi>48#oZZKuGOIns^xyn}N=4~PBDu}VxzI{?atMq*9=?RU_kOz2(H^!-dP7h|4pZVS|2MDGzY4i=IQi4QF23nh0$Z~=M_}c1(5gW|sVpEb zpcG%qFIQKn=TqX4Q+o1QP^_(llAYlP^07Y~?fP~ppGdCY>vyoScl*)rd@tXjIm`Sn z-#lGJpW2lL=ahwZl=;#=3A=pv;_`98Wm&|c-*1ERr=jJs9e(WOwFkM|4@v@Iv+qh@ ziB~kKJk012jgz^XxmfXOy8_QyS)^K7;#B!Aw6Z*>va+L6!%wQVL!gY`y_vtN!I;Ye zR237=`M$EM&8O;}WmN^gJ7JWQuvgg^&oy9N{U1l`T&(KpsBV#|9#XC9?Qs7@Q}a=# z=A)1I3}@ATIW5Y)>_5G|g=+ z&NV)=N>1a77yHwcMc16c)yz94xms7hN~%ZXHr-!pE&9{yA<^RF+&uWK?aZo?^ zS7&Kw^J}#Z^W092-27RImJfeg8|a$E6Y@Vi?;Mn9J|HuH2sdfz;A$BFv?M-kQGq30QHQe!B`Dz`czP|gXhO8Cx+TONRUVD?q<)?xe3d7+>vbfF1YsySro!#SGmWK zQXc1cmr`muw-rwi84rxLQhnE7a73c51Kt!4tIQ3d!Vrz4>heobJ!)YIrwbG&%M=?k zy<5kE$h=Bf^~j{_xKt4ZmQWlfRmwe9@pN=6 zb+kD#jf1{wq^>Ws!a3eI>th!#u1jFPy|3-TI0-X8G9J3pJI<`$Gj+g7gf-5nPvE{t zeUWXP^`+5 zLqI}!9lJ(OGQAO*e#!|47Q%p|V8-_hh?C#4QZ@|RAg+|-<8^tJ3zB1zuVMWGG-Agf zWi*uu3M5SesS_wU@xUfqXX#Xs7Mb`T^_Wt|fDvd4)^*q4kf_oPGIa%&qKGCQK(SPYwZ+7$@|0GpC9UC5q zKmw~Gn1Y0OIggnT9zewLjAbz_KgnU?fR-m0mI_I<>@&yuLYD+>wl*Q>s5k;gc1k1D5M2_+z7*y%Q(9j_FQf(yW2s|08HR0n0Qo$Dr`EKv;{`S^ zAnribMPsD0iz$k{K0(v7^iOC^qIaHB25568r(6P*M)JPKfWRr6EC4>GxuY` zCrZgTCSF2LmZ?r?5Zc5gU4?`EC@VRV?gJPba$GHV;c8+PTn~bx6innuiUInCe_Kf_ z7!qle(#O*T35+7D&f$UY8fQ$30iHkSBfkMefIuM+7nM!MOO0fN$455O*9(dj3QKwz zO4J;%#)6j*@&NQ?E?@5_eSH9AuL6qbeA>AK6j@`iYJ(u~Ynx$Hp5uZN{%p~n9F9!e z`%c^UUTk|8Z2Np*hpycXXRAf?e)H$$J{lYjTB{w*sSRP^xK+QsSqa;=TGcm&MWH}! zb$Tb{XB4l^W;r(XgaD#38n9Zxijw(Oy(KlQKvL5jR&*1_iK0C-gCX$ph`Klmoh=IB zRw)qT_iT12j4Jn^YT@5Cp8ORD$Q_81-*?4tU90=u(z&YR!|=ks0ZeS{1e4cq&|EmI zSDNVwcCyM(D!dqGy1Su{2MPfes45~9G+;)XjOGQBV-sNG4FIDJ=#L8w;06;rUXVur z2MW!=ej7GVcb+8fLHNxTlMfADIo=#WXnFS+ySHSVX(wv$aGeQ0dBJ&zWQ3BB8Td|M zPq<<3C(L{&@RuiSS|>gAC*L~eIio+*$S}UCS?GSyEc*6`_{Se>Bt~^4kPQj^Nd6{> z@D$ptvC5b1D_afYgmJ~U9dc}jr5rKtXut*lD*=XQ;%!He6VT*0Mg*FsP!m?{#%W)- ztY)$EI%x}BKeq!~i$VgYKn$Q8NUnm;Qc8Dm+q#xThR5V{G~=(u#dAORUyrqZ_51t^ zO!?L0^eb$Qvt@qepTLWp`~_drwAN#l_>UKfUoVo~=>^F%Y;|A9-A?!Dd)}uFP^%XbtMM8v=%;M{)q7m37qh!f+d3kAL%bWI!<~=c=-N=!Tnnh`Npr%?>Z7^inWl?GXe?nNRw0QJw?I zLe6ss3sI#P)LqRJTut?gREu)4b4RXSA=8uAqB84Ufm@^czF(wyNWD=sBKMZ(Pwot- z30b^zY5%e2Pq)9PKWx9&BA_qo&!G06E+vUpDdWQ%7`JlJ>GGiy*$lnZ5;4b_{+U#p z=MwpzeQ@>g$*OlD*re`Fd{^~R#8%)F@Z{p_R9KTPuzh@}K4fZT> zZL0ivegRH15!v~Nzd1nU2w$A5C6|Y^TpP~~!240ij4|7wbtt*|O&fn5SW9U(`ZU%m z~Ux3=-aA6_jC)ZFghD}o2P?BY^P3_ObyT#r09viP)J z{0tkVd$3*bnTq%Cdlui^(t0rJ>6L0}P;nP#5bx;)KY{rkn;`paj(cbtGhl_zs^iH8 zR*{0kx^w36*pB&`g)P&$oFZbrgqwqaoB){hRWn;np|mJDQc>a?n_`|v3Cv)=O<*Qz z`I;BXbVz)&_(zuM@Qv|arj!(?%90-$I-?KDvTh$Ro!UCwF*8-vX2E61sndXN8NvPN z_U$G&&u;b23y+>TcxBku54d)nP)W1GdmxaRIypw@#{4sVA;3%eJ8N)zpU9uBP5~|u{c9Whvo)&nY{k;i| z_^mZf*8jvOmle-NHYT;mVA-aa1R3Mh$AeDYCbzjF&yU*(K98Aom$ zcOJE^C+pT`e~U4ZZtrC^s1@b%y{oD#lC{ux89<<7iRx@rT$;&{_nXL&yPBP z{d<1g$Nuu_JGk?WUl+&MyEGZ}AMdb_URxO88PYPsF3y0{8kAC6xE_;RZ#BC z(Han86qe4*1I%-wO>Ka}&2uGqNCxZBrCUUBZF-QYG|DxI*QlPbxR4z18TrwWyGrV( zRCG+e9kHp$q2|ui^@8c0fbC;%sg9^dbh#da(lS=BDe0RFR#QOO5+&6ERW@eQ6*91l zw+@@v+dI<}zsIDhepblVeW8zJh&P{8Ti2030kJQyG5cd_bgTVnA`NN$9N>m303Eh( z)oJpNZc_6(?-+0+rWImXxEv?-n6&N<@NId2ji=nXDH?5=o>3y`SS`xr&Qk(2IUZs~ z*F}Z|CxwH6L(DVYNkW8adiy#9nk-bJS~ZFG2r$H${WL~M*;2lMJ5I}`2dwO-^E60Q zm*3bEyR`L#d0+?UZM%R-t?Ox#hAnjd5|m9@aOkqpgAf_k;b5xm!)()3DD=#zq}f^3eM*+eNAe2rj_ zT?_ry=P9SEv7%|d;QU$u6+E6K+|V@4I$7mqWEMJ8*A=aNs%I!B-x_HK!i7r^S+=qW zl=j&`NTO(utg8omZ) zf$NJ#OK5sh=KMqW2ZwC3ScW<;+ch}aSI2HUI8wVjmY{&rFt^UzgC4?rDZ;#cnT|X< z>o1Efl`p=r68tFYJxg=x#d@WqM@Nd-3UkT6mD@Oo^9OiwP?9lFdh8^^--Z8m6dD)i zzE1lRj>}b^FQJpJhXlYqbhKkhN`&xa``t16AF&8&abbQ0}U?Qu!$= zNGf0E5nr0J>LQaUUVG>`AGN zjy%bP+hc&9fz?nZ%LB?w0x0qQ!*aShMW$f@PRiv%X+dcYC3&HU%+n!eG=Uo3MxfaA zSNI-#8E4{~&@$c_t2|few+ZT;8WSn0r-$?GqYEF3vy8nTd+Zb1jd9hnyW-djbWw^810I8*G~&f*k)^wPRP30ui@?d7Vw+XC@X`XA!B zow(dLlY+?3TN16wg>^}=7>kI{u7nloYRmXXkbzF2*{|nAcnt7YE4r-tOE+~JG+~})l-`BSxlUD(R701sRH9pS#|BW0%ckT zmyP!AtvZ#!H+jv{%AQNiP%$XNLTXdZMt}!B6{LAZqo@uQmOS{|-7gKU6 z6&ZYzb>HojTFk9}wtxFI^-~%#hf??VF!(Su0MRK5L^wT&uG%|e84lMG6A&4HhyURR znI<2oF-^o);YbbMe<2K;d-1H2Jv8}~p|rGEaPx8DpuR^xL{?gMX3-ZEjv{j?%?;b`|@(4(dlgw=RUIa4blE2AXx+ruXFrh z_fY&H8YJsmt$L{&gz1qE7Af=);`PG`98H=-)60jZcy?LRvea34i3N_`Fu36H z`XdH;8s#r5qo$T54WQzH$1W~qk;!%U^@7M$+G_yZi)b+2F-*~x$li$GJcEGZx z!bO9D5~h+cya8`rnU+KkA3=KgL1j<}q$2hP78XbFdHZO%M~#@SOFiW3k=%7B`687r zZd!@f_a;VN>BSi(*1120g^tB5b|sY#w%ej>2z8=OrWJ^F0>a5e6&4yXs+*>Xd5o(G|dW08x?rr3hp44_JDaF)!Via;Ej#W*;> z{Tfz^0#VGcCs}cp64?)}xbsoS8k9IIku2zH#VHl#=VwJjVJ()4QqH%6+Y%Y0rnx^8 zS(>d?O0B8&XSmI#k>S>YtTr+~i4>nL#edE)xy}gISW)v>sjym$JhD=JMifx6QLeEu zcxK6dh*JDHO;!3y>aR5ube79?+Bn~e2Krey=`-h{m4d!4XZUBJtF>s7wUQLdzKz0O zlya9e^`*T0le(RkhTWaR=6IhuuW-A&kL(^OysC|~)5HF^m|McnJqn+@+dTKA+3v2s z-F>LNpN74E_uSpTc29Zi1Afj4GbaXIBt5=s_q^sy(C4{_Kj*^3?O$$vd3N~aL8+bp z;i&JS{qvvp-WOlI9({>6bBIow_s*Orhn3F9G|vZqw)bszNYk@xb6w+OQBpZ>&hz)f9dn}`-zF`5@>F7XzeMR`B{iZ?@X1H$1pmzJBEN;V zQj4XBi>2KQ)ljFRnuXeYr~IFewKbw~o_4K(7=Luuy}wRaTl;3)r6x1Gy6~my&kJ>| zi;)v!`FHKwx}D3D7T>xqHmyXDM=f?_E*3W16$>p+WjarXI}Nb9v`a0|Xe`cZtPJQc zPYyUuE7;X#y3QXi5LcZ0e>y(fnj=GH78XQfZlkFbW}yj-V?W`r1cB86BGlV^HBOWY z=$F;~YIUbMdV6AZJ)e5B)NSjt+xoMZrIdRnP8&Rw4 zD`RN`ZdX#U_3qWZ(&U7O=>PQ2+)uEcVUyDuw~frN`&({%+HMIxt3U6qUTSzy6;M+T zuAU^h(JH#{M@3%@xI>Rte^IPmNv-|scE6DFJlt}tl>ACD=pMBY-2(HNkm4lF8Xrix zZ$9#1kajyPb>B5xr9JmNgL?7~%Fq?KGjFdB*sQ|;k@(KP{ycML7hY#KaN{#s<8xbo zWJvYv=c?f9?Zd-W-qrQLLSM;<4@vdis|OnFO6KeGM+^dUUr(f9I||N?-EKdhZOGm8 zAn20>vq%TeZnrvYs0@10%z4~Dq1?B1>-Z#^1d5qa7nsr>EA6+K-%;2fQ6ZQb8Jsyg zH54u2ovQkc-(H#Jo+iCN;fSa+DlI8h>HvwLyZsHL*LjRznn=lTjl5@i_vtyb<|+qk z$Y?D*%6lUBD;VmK{@x-Ox`L;x<&MoAK)M62rb3BJQ0UwhbC$_l8Asf!o5C^Inx8aUus4Qm|xu#3eNRvh{-0a%N+ zh(om+R?+?fC!F@x?%S;L7IBdFF|z!&oJg$vW3Pyhdv*V1Y)V2JGZL(52-KW^Wm2b! z*a2}H0!7*&IeR#GYDK52H!U!79W$J_&ZCG&2A*eov(W~9N*57OzY>qWO-=PzbRyNc z^?d+nRYhvQvb-C9KjdMt1sYIkqDu;`{d4Dj?KSF+;*M26APfz%Z@}mRKs8MOx*ZUm z0}w$3@S?TJFheX=7?3v@2qOV=IB1|3IqYa0D-k1M2+W~=3>A&}29ANZ;b_nxx?n7v z`sPcHYBf=yP=-E~q*AYXR62)&Ul5oL_nD6mSxJMn!6Iufw5d$7d_>I1_QU<-htYKy z8bsRyMov+=X2371^G!@-#PZt+HTO8CJyFi2N~Y%h|2oPGFEG z8<3q%HDr0KS3J_+o8>zPH9G+0_$QJbfHT>l)fD~j#)04`YH|QvPvv9Sj##7YO>KZ2 z!Xpxnxo*I5bQ?`r_yfF7hKQ1Uxc}3fIHeVrCAx3x`*%c~MRZ5i$Ayrv{&=6>h68d1 zv%-sx_)#2Am!xuVDZDxJ4g{C+>)wL$AkSnEbjGR>*X!L zF1Ts!-22FDk2g{~Im@Fnv9Ld}OF6&&Z%I|g(7jdkg{HSRn$7Q5V0Bn5u0N2${k&i^ zu%6`m%wFZbW6s0ZPXk--TKrP*WT0FA-5SObixr8*eJPCK3yu;Aj=q&|;^RgR`o+-mTv$w<2!A5`ysm9Sg~I|D9_Qk$WQ~E#YBf!Q&I7vFRzBnfLg;4%V6+?`xTiJ2Cm0Pvw2)e z4ue?GU?pvka2s$aC`?crq*Wd!_Fux8!j4i!Jw!xn;6c|>VG@|X1NJy%Hbe;pk|q7) z+_^fuK|XB7v3Y=q?{OmD5oXBn-%lXo!{Gxj!w0|pyLtnbyG`&wTGL)vSS+?li4Gt<$A zDGEMC)7#%ghx-wu%f+p7X5i96fLz_@`VgByM49e#4AuSB^NCtr!lky}l(kjaa_R$C6XRkl?# zHvAc-^clT&4d@rw#WUQzh9e(%`A5g4m32EMiiJ_qr5nBcy15feVIpi<6oVa!Jm7e; zGF=<8w^=@Vw>@U8t4C_@eXe9Aot(_ydV}rW`6-Ty8Sl^4L#g%(#(L3lnX;q1Gg8%W zMqF2h=|g=^1ucqIz7)l=xuTT}uAmXqZly-Q2BDs*(+1bq;(n2WA1UiDayg3jIIchp+ShujuYo$UTYbCYhm- zW6!lHu8b?iaX}CG%>=2In;X>E3@(ms1kIfMDDyl}4fL{IznMg2W2K8rWrhrC2W9%2 zZ^uY(+NKqRMzv#^WCzDpM#`QnruOAN-t^FW?MSkr;IAUhjJ~;-vtTTcY+BTRE2i0g z@LDA9O`HrRlb(r43_>=6*M}$&TQDuzIR0YnN@Re$U`Hh;?}`VU!|p-E81j|_tR*^H z_5|o8LOcYVgtNmG6Zt~#u19;^k7X*db)JZrP1K=PNA_4?S+3htGzMQ!>AjgTBADU9 zd?Ls|(>Iwy+v=UA1WYJ}Qq3ysGdzr8QXP>QqbKX#E$;JJAA0=4Yu2T5*yRqDSo=i!T983}s znZwMEoCa-Fz8sGcEcH!k%AY@!euO-9{{6yGlvnO5>JGLs<83JQT z>QC=Dpeh3U8%3wvCI!#NiTQ-7|B&vbHkaiUac50Fpl_rXd}YH(bH3ni5qEw2)rGoa zCkAXT=408XbHn_Bwg1DnMTw%BWKY=`se&$Yn0MCWuIC5*s#6~xOT*G6{)N>dz6AI3 zU$Uz?;)|HV0ljK@veBFXI^$vL-f(rMr1uK(5C2??p6A_az6-nVNSA2(mi80(jQ}Le zOpe{lohYHq-$0h8@+p?v&)$)D1=w@v_2iS+qV+@8%qnJBq|OO(#&{g-eyD)-y%Q?c zk`aahaYQ%{6YE%mqZ?Jd$qQMdae@^y>1gX81S_jKqK`PU36agIYjGm;JuJiJ{IVTi zSRZoS<=sN_UVy|5%O$5cw>U~{9``4=TK0x6KKD8oWr7A~gL%H{3EK95k!e1|fzwd# z84RZtriA0WKXdQEn5W}Z41p|S8SrNEupN!Uc`EDX%$$OM#h41D$5&%4N}Ll2c_C9u z@AEu@UDlIMYTbvyf)ELe^D{OzEOf{a;GYbPPB2vfsodw-3j1M`%B~sj_FX(yj>411 zX&wu!0pxMsmUM< z=_aYGq^yQJuQ~RWxSbItEFpDYJ0qENs6`J|4kQ2h-R72`8B;l4-{!)}Xtxi5wA5)7ixhifL4;)w*m=cp!I8GyNTx z2sWQOZ^ox-gprDLwwi2i_Hj6wzTo#MlQzuJxBjnSbLPO{3ME}4X|7w2vPKWNus3Kq?^VTw+-UupGmmQjP zsez@di8XD+q*aK(<|6vF`}xgHOw98R3vmvd#x2K7vdz8lC(q)UOgAV{F4viqmHQR# zJ*jRYdYKubTqTY>s5Hn@5ihbYrPxO>QWiHSnWbMG@S;`R#ne3+W82^Y7^Z=Vz^ttH z@_dm16?f;&#v=8z``c)K7w6zfCyEI@3B@YZl60^g+!g8Ya-3E{P#1>7NAd3A-JOn= zIJ=0q>>G5|;cr?eAWMTLpA(Gq&DTRyZeCNHFTBpK*L@y*-3Dd)&WCOnWF|>WW`zc#Uux{#*LE=B4J$qj8I_Ozix>U6Snn0p} zy#sn)XrDT|(Hp?qQ8Z4as@85-I|_ogXbDWu?ON3Pg*B__tnep|;S|>e3bbuqs*4z& zWjDMskxI~;$C97AX-0A?uibp{??!U~GDBSr*nykDRk|2@!&}iy1k27=2g+K9&&UJ2 zNEMDZqk1rPzk5t? zK51v?hhK)*NH264p>w^7QpOWuz~NXQzwnra7XZ~{| zp!a%?ZVFw9r*e~aedYeb2 zX?9;VGC^zH$)9gw+Xv-+7`x`>2)e%4UJ#@e^3bdGG!Y)1+R@Cq7_aKv2HoF@znD|^ zkF+k3Tq1K+CKlxuXFhnJ_J#OVevTG<9h!;< zqa4{p&hC(27U1N7*7pw&n)-UTL+2k#FNmGL9g}X=v){Ra^^6xa)ZNv*aFS-6jt|-h zWwR;!B_6s&>wyrUHn-Wp_0=Z0Lw*f?Hc(4{pD)JC@^7}jxKn>3>9O5Be?xnhO)Zn| zn*b(Kz3l~*ckM4ogu6g1YT;Fdoy3)nVgQA*Z{J4_m#w>ar?x&jq>JfYdoyg$7gS#I zu#?TE-r!a&QodK9IzqL)Z$B+hDLc<$voGq($lA0A-ZXfvi+k$|iiF{g7r3vkxl3^C z3c$(sn06lG^b(QPRck^Qe%6z|44`9u#dFMde_-iRVKzx@HM2eca+-_jU4HZK>wA87 zf0^907V>o;S#z<%1#CdbMBLX2T&)hDb2vZOk8zN|>S6^C640liU0{^iCp{iPDZOq_ zxaKr`?Se%8&DMfJ5CA2`Y5_jmB(5eM_pH6gJd2yW(Vl#x*TJ;6ef|QG%hxx5TArM3 zSmeF3SB$!TZ`F9KPtZzNNE0C1WT;7s5Ry!?Oq|+;ri-nD925JXvAxi2ur*N+ijHSj z*4+vg{Jr8TJ;+Bwa&euP=xV%`DZ-k_Qpgd!3vYJIaUXR=&?Zc3Di+*ntC`d_8_MzG0fPt z&04vD_~3eC**ccf75YCw1?M79$8~s-TzE=yA?L6+Q)N#xPkBYD;I!=c30`=xEkD+22F_N?3bk~skoq6pWqH&7dZy3(d`0Ms#5oL(HH{v(JS10k?o zR-1|omv4wQ>V7{F8K|iO8mojCGZ}GvE=@sIFnjYdB0stZDwrR>Cw6U9heug2hO*L= z!DoF<;tq2$uWb3p+g_+PAoMt8)@U#4L$QL7_dDFXe&)(DCrPeo@x_Yi3R;iKACC&1 z;IX=<~3r)?O3C|M$fx57H0r=dUsW{ROO)$Wzo2A+w(GT4`+>fe{29HpwJx~`gW=pDfCRbdvTI)gi&ryc{hR-q4JEZtKapsoGq2*Db zdISo^J2oBxo zZV*3L74B}vecZc`+9ZRS4Y5Cdm#i8WSNgyE`a-z7{t-`r`#|rFa^vZCerdI%M_M&L z|LnG^{42@k1IKwb0-6uwk9!&fkk;Haz%GoI3y7<(r=4=(R|%$`azF^9P4h~dFb{@M z?^0?_@oB76sWHE{!XUlrH9aDFi$qQYQcfm!?aQ;_hB}-`3g|~5tE4A`$rEf#4I|&) zhxG5$rBhZ8YvluzJ?1`{W<0Q>8}M7cOda1*>DvRNPc|ycC8+9c*p7VDCAZJMZ<v%TsH3U$VG1a2Tj`UtcDmhCre=vz-DXbR1xQsU)!Gax70Z z&LaHuG6z%>NuK=kCI z#WQ-Z#POGDOu8p&(Vz8Ufb`oX={xssK4kd^dTL91dMziw<|2WQ=~+*cyquc83<4wP zd`^2z)~=tF1~U%O?Z~I=B2ykeMFk~3!%j6>jewASr<>sH!Uuv66NPnJtWi3fz6#dE ztlYlYgUrI(r)alRm=61OZ!4Yb|9TbilgyW!)>eBrp9{yxzJpuWbH4Cd^KW7+7H5&G zHH`4vjQ5i<(Kjj$F!oIMYCJ*NoZl^8S9ieNzm(rU>(-7kHe&)8!zBsx`@*w zD;y09BXN1h2KB(^75a1FL)#mR4x4&O%{sh!r&c%Txo-CK;&?pMf9g~HA`6xqv$fJw z>HC)f1#E02*jt!~WZmqWoPH?`*eR_s7ijJJuP7)*<0R2*x=6aM+c80Rt~Z94Bl|=gnoG?v*PEe{Fvx&z8{{32ilgqpl-O%E#TUD(If{Bf1(*ru*qG zvd}Q;j_@<8WJBX+W_%9wNy&&pflc&}JVreD zGlo7e@%RY}scV7-;)ML(?-t;f2 z{5gQh2v7JIh$a1v&2hSZOu*>($OtslCyUnDst~;)=4uoaL@MUzepbU#yAgcG;2VM( zbK^)q3n^&NP}j7$OwnWM<`irjqSh3&i)OOJ=P^W&$YS(1Fe5Cuj=w|TGta}6)C0XQ3%XQEuNa`X3*|TnT+4!BZ6X#uiHq@G2(){re^Z((QXoNiamYrWH&da~IPcI)LC zZnwk8miop+*NOQ@NFtKot{a0kX?tYBrhx1IGW-bn(5A!dRe;UgNAL4r`kIT9ocL65 zGe0(}*qMxnS&7+v*tF>&M~a!h>}+@b%h<3o=ror9lGK70?U>1-|AmHWsZlv-S}lQ` zrnza>PAKo!W4UAZ=~9}9IZJ*@k_uxG z#A3sO$w0N@c6e5NXPh^UfW(`~KHyK<>@Lghhf5k&hu^%mcaQOX{-GQU!v3@6h*fKn zI5CicM|UhZY=bM}2(Lw3zTp4#HTXb~oYl{#BBOvk-!r)&zV?$-y+vd4)J?So&XgOi z7)kxkPajG6#E7lCttqAPe=rra7MCP$DL4zSmlB-!jZ<4J>{28fx*Qs57-jYxCEMOP zYrJg}e!uD2-oyS^f85&2)X`aX?Ii&^$O_rd|;OzEPdKt%yYHw|Q zC)^y|mTDmVXyT;)<>rZrs5v`uur$8jNNCx|m^yH3&{4fr@?lBo{xT}Cq(<*ykEW^Y zyquzF2N69XP1EAtVU{Dj+Q_D#7nGTDciLWHq2rd{RQFJ-J$=AVI)bcQqvEb!GmeBd zRA;op;$0ZsD@#2gnfJdBIr^ln)~0aFB>r!vhLyD9 z4ju+hCT!6Al*_-o>LDW?c3}I3y^$vnoDwtb1iX@H<+qAYR~#=(#@LKNLNII{zn+GQ zCR7mh?OMKME9!;SLRoDZ$P#WdFb_SIr{nVjOap2?W`?H1l6wGca45EQ6cDS8BTs* z@y%M2aXPnHy-83d05#^$ud&k7gOc;}$*UF%;;2uwkpP2D2JdzT$0-sJT2JS8FpONI zJ3&|#VeoaKBc5CIxg@P!%2t%?YjvGzJ<2vmYaLRBrU!fRw~N zv#y~kf(igOTfyxm*QeWQ#67kP;zeL?>itoNmf$|{!=2L}V$@v@JxC!|SsFq{PG}1W zHTIho49DhglRvf+I!n()V>brrjWgra)`6rlAAe|{+ooMPm`ccL8S4k_Tck82izCo} z8I{Kc9*anP@WCs`xM9LW>2ZnAmwa-ZJUe%rn&awUn#KuLv+igN`e)U82(ZG_-Lz|x2pl|oq3_PS< zPEA(?rjmWtzEYb0?yZRmetOdHO8v71u4hC-LoX2xEe}wOFsOnjGLqfiB)ba@kHx+b zY+$aRPZn6!L~BW?t#Apw?%6?9^{wu%e?%|~HLIc*FwyKbVe><@-Zj1k+4?oRJVl#X z0kY(yJ3;jZk}WL;BFL}BZbD}3Fd2atJcbG;R5pJVT3Kf^p@QF&Qi(3jv1}JA4zyX6 zi)?CJxh$SDiWr=&J)nwKLI9<ft=crt z*`uzwOqYj=<42<3JYS2@A%??$z7=7X%#PC_p?Hds!j5QIePASZjszPv*10r(oxM-j zx=Qmg%yMPm7R`20yarQDT{0YOQGT_N({AUylm*IvVfO zh;C!0AG4oYwRZ@L9XOvKeL^nDsc({|Ur)lU;(%J*;hdwfMq3o-wHg|{y&EP&g=_K0 z_XFKq64|yvXjf1S+U?=GuSvdePdXm}$&#w{bg5<8+cBLq^1Gh`!>1jkGha>z$O|9V z?+jt>GBl-+UU6)1YWPE2GHYd7 zyB`z#ZG+ZBSpWW$6PT+QNL_0So%rki>__PA@}MSq<@@vd#A?R%#B(LPxko%MAi0`a zagFC+AMvVlZ{1r89aBy()jZ?8*)5;}{SyxBu#Bb`dGhz1fiDEw#_MY<9ZH7(xEyP? zLA6RkH;6AP#JgMFl+g4sOojMWe||TrUm)_jg!orfbKp0GQm>N5-N54C5RozzSnY5l2Dk+n_30;2=b{X^|!ada(#isbE&ZG)P# zw9g6CeGu_u*Zn2NnVI60MS%FgG}#`e{v-h`FDB+pRQfUAh$p#JB^MoKbHI{)bYz(V zP;%m!^Et_X#cghXyV@0BwnMAi{E+S3mkGzb8+AOWAI!IF`VG!J1goD%)%gwzpsNW~ zIvWqcUyqYTcQQv^B_N7Y~_lEw|N(a~!+?W>5{?>t;941=K(m9$YfJsKCPj|Mb=OoLSY zH3|Vj^EZHEGO4njHH3?!oIo|Z2P~QqC-WtFrSYeHSQ7^ASD9=1<9)txpPCLr){=3; z41tJ~I48QC)u&PDW3O#1l+rk=+j#QZP98+iFga}Qb!a9$dhENzFbll_S`6rViKj^w z0p}BB@^K#^YBY|Vw5>4B?wFy)cYyn1!^I80wSa@mio%m?vgdbE>_hu(X5^PPX4{8G z(ujaL7+rAx%QtRMpFVp-;4)^8Yuk@~*Jx|C0;q!H2u71jxUujNASMKa9+rO9gXDF~ zvIx}#Sk+|;fDW#h)QeV_!ouRlkikISs&~1a=q}DpgoG<9AImr}#gXqwCFIX=MqvFG zO%n`A{+LDg3iM^uUmwj#^gEyPvywcrsuyC-);;p%?1^E>m}Y2!qU5VM zctIztAcn=14T#5Hx-(Iw54VycPQv9@)*x*oUm z6rlSunmzLs{A0{Qu|!X8t8FE0%ne@;i(!r-SSJsE^*-Q;hcF}=(tK!bX}r5GLtJqFw1Xx3+FqOkuMov0Y`G?kQ6 zAr&Bp9q<%}Sx}cN{65}~0DVBmIEAJ3OJ@%zP+=dWk2lgqihrp$q4$8$HVFwXM;4+AFMUOMUn#`X##~A&MjnrKn{sh*8JfJ% zdbqGWdOAM3CucLr1_t(iaoN1A;I)bQ4skY}bTSoiEjRJYf*`qTfs_U2c=kPqg#l+X zM)|jFL=~iQ#hrGs8#e!(u*)tho>Za_h6N4Nt1Y5si)PI~NkpniqL4#kTKC~GDz;Fc zqAv^bZv&PnYB^((Hz*FJT4xmg=KjSTK3|m>r}%(Q^^_~`u#pwx>aTEzSZomOUIh)u?pqjJ@)~J8&eM4EK?C)JfhB`9 z#9yCF+*%XHsGr$}%W-;{#xGOjE@;RSNxtm|XrdCFwF0qou`u-!{ni*B=ZOJ^U8 zSDwW^*fJYop1;_X`Gd`IsG)PKJEcLMD1_|awU;*HM$E~jy#96L0n33klavqt9j-}L z5HVH_k;;%}7X1rCvUuaBt<8t2Y71Z2N21UD20K$NSH`V|PIStD{B~|}m`B{bn|sfs zy`TLz>4dpV?uQdS*rU)+AC@~40+m4(qPOWgZ`Erf&A^~P;s`^Pg~1O7w=6sd?z3~K zBMpF2kQi|Pti4wnHMGI-#W%x=BTiq>yK_h0J(*$?Ne`cF<${AvOqX8sxh*p=JZ5@` z5_x%R7DL$!(m?KUvI8k@R`{>9RDzCVpBb^U?55Pta*Sq0u>Dp?#@3X31pH{!80n<> zWa8Fw^T|VoXg>xm@=o-b7vy{MW7}Pbfa~LzXVJPzi5QnGRyp)CCdF(o4?+TQ1XK{c ze|yZFnZKt8zZnsYw6s0}f<|L7pYyy>{G12U-r^zNKij>RV!r+9Xf8iQ*ZiiXNxZU0-Aw_o0U?4e> zij2~36|_40lN8Y+S{K5m7JU{L4z>)U(Lev}hLXB`e5xoL9xD`7hkCSHA+U;PPvu7l z#+Co8;M!w)wi8y4j(K+6^9-%_>|dBEv8bH17<&&Iuc!iO^9yg4OQ?fRMOgD(ahDvFf3hfl`eB7QND(kv4YB`+;q8`RH5>zfO;t-M>KY2 zo`lf>DxMp59xjz*9I78TBkB&Mt;ca_bRbC5bBpMf;tK9}Y2@t`=1nZ-eW-leVw(Rm zKA+UW{izE(pED}Bf_`$jF=T^o*5>8=;+GT4$lLfv{S7)F)iW{Gm&60+S3AW`-xQj@ zGxHOfUlDuheSl5x!khjnG?VtddRN>8Txp?u+(P-d=|JHn>EGrFg;xI)288WP{R&1q zCzeT7)2-(f(u&ulJk5E^JVr#WE)Sa;~ z&zcMUROb9}ylWfpv{f9MySZcumG^JyzNK~5d1LxhOWM=W(O;ou8I?v!RnH$M;HZ?C zz^lwg(4XOM_&b7lBr7w01PlEop1X9At`sL|Hzc__$W%JSUphpTQW{1_3`?#KiQ5gU zMhwAI@!b6*iV;Ip5iEwf?XlXX)ctt4-Po;&vB#`q`u+GoR=l{)cx36Ax!rI|#CTZ0 zyO_;zn%x*mX)L8upkKB9ZS*75Cf#kk0HGM&4y62}CK~+5H(8L@EH*A|#yljcAk-@U zMHsBTTir2wz<5hVeQO}P>B(wsLe}quyx*^jEJfOXgt^Ow=e#Rr{}Q{8F4tz642c+Z zU&X7gjv3pH=dF&HU5;^-y;uAwL-T`+IDQk0U;~ z{F`$hz&%`hU)R6ziq*H|Dese@_8SL}_(?(GPSu>!@80eg5oP3~)=ZFKCm!|+O0$aT zE^Og(X_~d?jA+>FNSV@m!Rb;Vv*x*O8F?!|yJr!Mh74tLVm_$JjWzM6d7*>FHMTs$ zKd+Npr-?Lp_|*CqDIqeE|60PuVKG5+wRvtonq;sJb(q*Ln}?@vK-oW&MhPT4ZP4B( zz@r9{`2_#BPy2!G+U4z!6Xd?A&huZo?+k3_i7<6;;Oy-Qvg;edqGM9xrw?x5K1*4b zy86s^^;o=gJd}NxgPoQBHon2`>x*|^%2q$Wc=$~!1D8_47z||g!85D$F`ASG!8fg= zch;CH)vsSRVmJ|_8)VGDKuoV+g|$p#c(J$><@&}?BF&-rA*f*~X0(RNtzDc4 z5v(@}45u!XRz`KyO#pSm8uOq+W`T0B^fTIg91YjAc12}*h7HP?4N*P(ei0f}eDmFB zf1x#p5EdOtHz&%`3ffBC@zL#>CvuXhAsTeTEtxl zquNS^AMC7NoL&ShP1P8bC`5u~>KE$F?)3(`yXn^18h-?0Ow(#l(D&&eOV0^5Kkujrp(VOflb*$y1D*U(e?v_b)D@)j}P#6m5D_8S3J5e z3?ItGIZ~qp{#-n81Z!6&=z#8z5a=RL^VcPKcBVTfx^k(f`KIkp9n|#4wEcQaRQ^dK zY(32+{km4yJbf)OB#;M7N(8x6C5Ll)97Tib`8r zJXyMqbJCrjYei;(NCg+mX%hK6y9XL@8s{n_$r0}K9mFSB9&{yzUUAj}QwTxzDWiui zV^Wq-)&8tLeRwF(3WvbjsJ#};&tEH9y)cxm-k86IwggD>>uTU!m>^8;L*y?b4Z3%& z_TJ3?!)_0_vRtz>h09P&V(Y8h>4v^}Z7IS9vzC+kP3NTV#8&7}Cj=OmU7Vj6BX*~e z1%+rzZp#Sd;!W3!t#p}{{lReor3*8N^y#Wes_e4}6R;fIXVUAHc~9U3n>W3)7CN=M z*dZaHC;v<7gUpR_M4QTDrfaS>(PF(&^9$4RgBqC>xWwJthWh;Uf{b;&I9_|DYTOIUfHO?o%C5bAs>0m{2FAv;nH>HN;y zPx(%ba#5fn?A4-=7#V+X;9}C;vQ5g^S6$1tZ4<0JkRnG>XCThQ^nmtD;P!x!!z9w} zXoD=$5oqyDIl+*;V7tc74HBs30vu>!TG(%&y#%*W4;Ez@%PpGQ8dLtr4;%{OuE_olMF`*?g`4k z!3~6JYL=CgFJ0U{nejCb$1#`~UbXM_3je!Gae$Q2Z$Y4c^_pNA=v#nBD_^G(bO)V^BYOb-gJ;$KYUfIr)92*6f}UXU6jW}^#2E&*YQ z#v@$8fLK+>gcu$?k>SVqnaoDej2D{$4GZQdcIA$HSjWbSYWY%=G(&YbUC~lut1Pwj zH#kpl73920YLJC}IO2`^vw#I{xt`EiC&+h3dU2}OvSVmYDVg;hm;fq};27gntVwOy zOyx&Arj2VRFiD;F(TY(?gQAD1LRBU|stoHTd*@KqEZsy7-tk#B-O=vlFYAkZnebS@ zL_F~0I2+i|AS;zxdLbu$@lilYQF2}r!`M5&>{<(0cFIky3F!|w0$DtxONW(*mS?At zm+k4Ih@xD|?NZM4iIO{PW%6*Pa=-BT4eo%e^Klg)sM2`E^zTOQ!Q(k9Y`3FpIlx^) z=MOAs&%aQPsI0(+_eNCnbfj40=4!i*Tym?7-$4us(;*hKo+ldIx6H@2BCcViF&$c* z-)2yhR=O~eiI3uit}J7Keoo4iz_NDHntH_!5u^Em|?qC179 zu-~qH9(bS5P#(l=uEa&&eyl^=A;qNlkc{!=k>QGw@xu38)sQ%e9JRD>Sqb2$b!Ldq zr3)&=xmt$>Smfo9u4@)zd1}+lyk!*I6!U2&JiqOk>8o(^4viO9u^1`I0|Ce5-yRB;?cX4D1>?C|RHd!YBzHfL-NchR z8ifv{qT+?bqDhwtmME6;BlS+K^GfPnT=N5R=*^1cXp7Kcr9;VR{-F)p2DP436Nt#} z0xNsL`5<$!e}-`XHg^NhAaAf2tzfDv#Ou>&YiK}*egy1T`ui_7 z@>QrV`aJF6+wlg=9m0HyKDw;!JA@c4)A~L>8{%D!((C_`=KDC5dwp-ZtM7B>$^;V+`GSKZ+Ftlwr&$uou}wB!_As<(dVKj_clJ&vSB-!h?_7sCKq<=vSvS5^rE zV7X*Ih3=>vuM)epU9~q9esii)fMG^sauSD`WH*obien3W;%`$`Pn5(?lE|^EKpQiyzgU zrEnI;m|xncU+*RU;W50tVT8q}ygOiO?x?%|$VCi;hZb^Cg?4L}dP{}&hXiB`6LE!! z_^plFF`|iUrOnn!Dzrk4e@#pur^zbBq?{&+OF~=|+3ze6HLfs_46CHdb3}mvs`w*2 z7bSiE0{w+!ieYqwf-GdpDEYZuRQ3bZp$cu*d1BUi5>^L^cE@nsr-ko=9j-rxqf{~t z0^ERxQJC;|VOCug;t3k!#@8c8WpP#LvsvYuS%-~kp%_(E>XCpe5b~}0p=K64;fPAr zRpZK!>RB^&gi+9}5%?ivBDHlu_%5W$FTG3}NTHGiB4}``WKkTEUd@t&907EW^w<76 z9^SA;zelSqC0v4M|AOxrwhLl$(Ba;qQ<0uk> zqtrO18ngs2om1)8rC&?Kyb0jEZ(z4(T5oKog<$b|Cv7^apnwcQj!~%i2G#`7GhhG& z0Fc)ROfgHY>{rNRO&5hSJj4Q7s2~&o5(!l(Ef%;aA$jFT4kCbg0YLE{^H`mXK!3U` z3247ZndAoKEVImFj^Qd3LK|3IrCZL~!J)dHPYk3YA!LzDWZy6bLyQ381ZV^nK!LrM z?*X;&DJwzAixTM@aYRy*<}7g_ZH5tY3o;Kw&`;(R_s za}_6V*~i2RP0wom7LMy(mVqRCeNKS<1tRY~yV6m$gfW=Thz3L;5px7okLYs05Jn{H zUOT=iGD{>Rp&*IUt62aYjE=zw#7h9YwZ@*DBaz|NVc``)ofR8rq~wkiudM-^Ur835 z$p8ehGl?p*3k0)@3YQI-99vknP5U>xY_%GA$W#PNBK7jP-smhnq@s&=1bWM7(`Axy z{M6N`C%(v*Dt{>?pU)fc?VB=z^BIK^;As*mz>o3}R@q%#FYZrSvj^pD&h}z9T#pMV zYp10kWRa*;5jX)pf2f440aAi!*pWa8EC(b}SC&hIR%>YXL?vxP7n?b1X4#Ffz^fxF zIT-q=TS3q?ZF^4`y&3v%LYI(6(gMfx*^x)Hjs-8Xfxt{~lvpg*mk!Q=Y0s_suGsQ9&k*=*V)@sghbTNA(Z% zk>|@2N)25kgAPyL3ablu1GBs0SA;oZ%r#QFjgQ@2R=PEcaH~dp^|2KSz(Qj))SW{*;CQ zPbS4&4|wfeS6c7P=DkpQ122jK+^e7U62~?mo4EmOC#p-oPiju!vbTdbh70hRa$fPl zApjI|gos`yUz|;li7vM@2jvB}JoYEoI&ShNki=sO_`aGa3m9@v6+(szIqU|n1)O5A zK~9+rjXxj>&NyM3fj$ZFknB*61jy2n_Vika4WJFcap8Ukg{gbfb6OC{!Fd?Tu)Z31m-Mph29J zF9N7V=E=(fn!Liub;6o-JVXjODd7#sYkhZ4L?Nz~Oua1(BSGX^JR-&Qkoi{h@63`L z{UzmUBTL!J9Mw0`+MpYVpqFe>xY=SZ=z`{FO1Fp>nqaMIDrC3I6JEn;-Z%!b;(%h4>$h$Lb+%nr=(QWxV@I@ zL17e;YFOo+u{$-L1rGT3H^R(=b}jP;fCl|9C$|Ye1PM8(pH)qJ;a_Q3ia5z;Bkce*#(R~-%Ml`|Cf?tHLv67X@h;gU9vs?P z?zSt)M&}QD=a1+pf*vWW z=<}GGd`Dj(yE-kpW*(jQ!M4}(VCDTSI?dka zS-k#fh05mG)=wjwP^I*2BkTDk>14v`XKX|fojJOuJD*9fRxy`|i0{T^K3I=6Yw-Ge zt%GYu_gSxA7<>r%i)H}S6HaS&5r-R}H8lG&X%2TIdCT7=Ij8ZzM)Dd8ioQlJF-9i^ zY>GH+P8dmwr*D3;qd}@~=HA#`Bt!~c-~QG&r46@jk2m4tTbht<#ip&r&@`RHZ6%*A z&8W>AP1_0%U~Y%)5ttMsgz<*dHZNp*Rwa`xX*}|bNoziWtn_e%d3u)gc#v*YKm@3p?yu)dievpwRo zZwvpPvA(^&v}e0<@XCUwL2R$yaOA7uSK;)`ai2}`!vkTTuTP>j*QdX?MEw{nKM1cVL;qegYXFpb=7{#k5fpk17eA)EdyMowMr9l`JvpX&a>VxRh#7i<7C+&> zd%}D7nDz59=+zP5%!%la6LILNr1+`S-BXc!(7!n75e!Jve0poZXLo;`8AAnrnu`B4zx&hD_osEnPuo{N?Pq@8`|;BW`pZT9*ZsS{ z+ED0FlZaHT|9|319-o#pru|&W~h&+_P?Za2-#2+bL>N=W`uT(l0pt(EESl zNuS?fNU15plfK$V6DWiorn-*}q=;DHfa%AUowtQ9*%AU|4-4X+-e(v!_Fs8pa(WIE z`8CAt5tmA)Hf_-NYyO?T_&YJiKhIJMlbB8f&cnD8qvZgf9HvXfe4@*~|GEm<0cYlk zHQowB{QdmmNuTgK@xuMj#?Lr8J(+tmuY(g)NA&qbcrtIl*Wck_pZRF-Nc;t$sI@j} z!tl=pK_dB$qUcj#ATYSnKGFdzkHLy4zq*~2C{hyPomeRQ_G;U^ zeSw`BqO;R(bI_USkRsYeNGO?Y-^{md>XfL^&+3h7X>jlSG)d;$aD>?Q(f+N+vz;mV z?)#7a2SsPkd(t5N4Y_2NUVei#vinCvFl~|JVR}n%|9hvIUYVl~^1t%xIsH?Othr`1 zEYx}RH3GCQng5tSJ;Z`<=u7#MrOU35+hn?ueu&6a9649YoF>`v%9>-RdPhHV8y%V1 zY_2Mt84N7u>o7E8X{T*u>=`;!pAd*V{^LG-j$sP?vo4kFN#$r zCtk`H+S3zj3G0}^uwX(Q%IAC|k-zDDGgbC5RL}gj*u{3PrO(Aqp}RxR$KsH~i?7%p zdY-$S^Y*`XtF=Lfqjg>ER?}ofhky1xkZr%TGp&spv|_z-We*BIJHW_jfDJIvCmT-F zMH+wF{;^U;4SHiY^Jkk;V0eZ}1k;?$kF^eY-wUOCn(P}&3Wr>Z-2<;sw*r}7y~?@_ zOnTCPchY?!%CW}o-^GNA4ckQ{Km3qQ^AohYv_dbZ{uW4hu$K@6 zB&-uJTa@E~aEWgiEl|eza9J)d+r)!PdVWNSi5VZ?eZho_=i?*gMxsvx9$!l%3h2ou zT`~H0c}+GtnS~=i()?1{Sgn6&%FoZCgL$e*x3sflZFDoFFSx=eoZln^=AC-?D!I~P z1P_}1vocF{xwt3Ted)7xSo#x>)NbhNg=eGXVB5Ov7rJjV1GGXMhgxoDlgzt*<$MX8 z;MMObN*Q#GV(98a%&IFfrw(V45?N`dr$W>}8v` zBzkc+UHAr7mVIM3Uz4MTgl@n{V*m5Pol8nF^}%*&!#3ec5l&gOvwG7ke=^mlZLKL7 z@vULDhUZ}fzvY==*1rju=yQ@qWU4z%^2*z<)Efe;+IYf~W(1q6kmPg{^|)TgX0%1yXSdnkM z2yU&vQnz26&0nx9fst}lE4`3Tl)*`w%>Ei4WVC)&DBZZ=R!@umCLA7M@5lNVFF!Wzuv zr-%EKp%uIYGZm~RL$w5Kfml>T{KF=ZNe@)#nc&VvOPrv~8?CHI87LZ8Vqw~dL{_i} zT0xu+the@AkuH@N9ulg}(t7&r2^GXTPfj;RYcB3X-NIkbq*2RcKWUc)7Z%A-t$=^V zx0g4q5>%IU^PNvLc<>T8Xl#Uwrjrn*&ha9J3TU0#L?k9VDbG8GYwJ9xJd($w;2g+> z(;08nzLAj3Ny=Z(IL;ywK%2n1^^q+4mzu>bM8J{m4fpA6VTD^cj(x6(8x_$k5+>i% zXPuee!V)E~T707SH~1o(JD&yHS83ZbbIGWTOEPXgb>h)Lv3ZbcPCm?xY#aVeh95MX zzXManR3o4hgUSJGy&+`d1k&SSbw7BHGirE)EQ!JdP!1CZk6?imfG_9Uk6HiCEpgD$$m-%wOHe3>7?5{jVNg@wI{Gq(Q)KX@fb;0>54eJ3W(@9d>wO7NHW44PY!K~<(CYxV?+fx z#%#GK+%iRej!vM>A`w)9t^Tr#X@SJ&(>*LieI@e4Z7CC&fN^&~jNP(vkDV1$fDn>y zWB?;7SO*cx$oNep@p|GfK-k0-<*8Yji zEp|7KlYHRG+>*)p<4?^Kxo{x(68TfBxzu}=fkB{z4mm_u;Zq;~r29H>PaQh84&-YP zU5S}16rK{Rn)=Wwl~o6}{E?wX0QN!vK7x%=;Sy<*L*&BXk`^%Ve3GbSdW3NrwSO8{ zR$6s!%FFv1T3N{kn@ZIrnUNLg*2YgL@#zA`>2}ef$LG+~#AM4DwT(p9J1wg7R;Wb* zSau?OH8eOJp9L0#HxD64aHNnrSrVcLoIVN3-~nR{dn)b$W4p+LXXQ`@X5Y`wCTJr> zt&>85IfXj%Px9bF0$Iq7lte#LWJ`|caZaTG@Hn+PhBa^ zT!~d>nb2S+2fCCKUIr#Qm6Ar63Zu*QHj1BjrxsbsH-@DU{faCcPcQ50Nf zT08*8r(c=q60NzXK5571+wruJn&V(sbw~zJUc|VmS5~wZ+S3wI$r25wKCWcVs(>3; zN(WTJovzC?ShV#j+W^mWB~w87Dptwo ze?HM+++i4E62fOMmQJ!vUl##8M=a~qf+|W|D8&Uo5w9&)t?L=RrLp2vrLzLy#_p2pPu#1G@Ii>hmO)5CfCc_Roozor-oJ3 zKS_qa1~Y2(@`o)M(j`lPuoSGxfn{dW)Ni_2V?)m1MTz$C}oR ze82`&TywNvF+#E2XK^(k@J>B#fs{7+aMMpHOhdLVqCia_ z0gX}Xyq+$hWxghN!UlU~H+pq|ee7I)QjZaJ6rA^*UNj)cW8vMm)e^{@5{!r&A{)q_ z?{hAcK;$zE?)>c6TzaURE9^MiCest9&)d8P%j`eivLEZiOXXledb<04k&umk*+ibd zig4|E({(9F5}@cr1I+a~<3%4kLJu6m=8co|z$dC% z)(bwJ7-L|cV&!BV1nwh5`x(UhStVHq<&P=5Er&cS9wYEnv5It7-{Ccs{R*qj5*yFC z&m-0OU(g)~Hy?ptNyKFA!EaB&>ysZzZ45wK$l3fGKrP@$m|@uQo0{sX1%24ZBj}eB zhCbU&w{YqzqXAV(kbpl`*DvTNPFV0h%u25H_$2=o~E=NLsoNuT_FyV9c~I z@Gvz`JJnuJJO$>6tv&+&<`Q-~-1v&dib$BC#!pf^O)>x{<>BKu_z*LK2&lJQx?L60 ziDUT$nJ^`n)GxX`#9-0~b4^IVN63%q-TXL&{GBicJ*Jv2mHTWUx{sK7kTubEDlp4R zhnWRf8NVA@W&LG9_qCs!x)e!XW^jn>APnC`*_n~9v5&LP(w8CW-Z4_zOMkmBZWC|^ z=|cOl1nKELOLi-k?i<@I8##HhX`8#=e|eSYAa zEd8AQ!h3_`^_lN2>D_PosU~ zFz(xLI;X%TZWGm6n){~WlyPDSe%=$LhO0vZMiw9X;40)i_`XXMlPIb3zjVX#h zyhGE|dd-8Ryw<0B+P=`xRXn*(d)_Bf!OuN75^y_Su{(r}`KTi+z{r*81N+ELGecgX?y8FK2ng#Y$UJ- zpXbW5CkQy}aHc=_4-cx{OxDTU+Ds8^Ep+x7o_N5WEVEv_m9F%kn6?Uq;C7}4gU5Cj z^}sKiNXAhui=1-NPxR6{llR|wwppLf+^n>>{(pIpS-LR$D7MJkhcZzmwO#OQY0#o> zZbI;v3D)xP|HFfJv2hI7JSdT`ez!99=AM8C?`@$v7dt~wL6=3d@NdscgZI9@z$OTJ zdR+e78m_H&B;ekeE)$XLwO(JLHvzJgQa?JkA}$Bfd88Zxe+kk+XJYOeaU9l zCn6`~JO$n-j{aqhCzI@_6DMya#zao1q?f$gEY1{o{qBoVdqU;V{2gE1o4QRVXDM`r z8SYjk2J63%UmvV^F1XXkf@?ELD$lpd(7rEe_PNn|2LI^Ryyj9<&Cdzdl!H!p*EhF{ zKLn&`b*bNZY9FE{Y)16Z(%!hF`>G5npx{(yDj-V3&_g%U4N{T@AT5m|NC-%YASoas^WrbAzV7?F@Aa&A zt@m9Ie*){6eIDPv_vhG$qDVtiiBH*1Nl?k$(k|coK@a$XOjfg{B&)oid^KNxN7iup zr7+P693R{nXQyRB?|!J(fv3&y(?621)ESC4hxQ!1ml&XeaKJDz@u z<=a~)mfW;w%E}v=NsYkUt_oxHgJ;lVV23uNtl|2L?6dj!i^)B?+z-b__fKh_bqwP}z2D3Sv#Zs?g z=*0|1w9Zmglaz2_IP2=eM#pIn^cfTdJJl2YpczH(n<^@tNCVle^hQ4d?nGzc_Lh%w zu{16(YVFP7>Ty=YLbB@V?HOx3DBO{vkMuo9Y_ zC~926=}{v;BaUTt1($W*La$cF5ZJZ`ye3m_{X~G646LPWVECN!8KantD?kBNNgj-> zatU)A3l_0 z{bI^kBvB0PWiG6eOFP~(>g0AZRuvUx>$SPM&B@P011Y0Y0P=%az| zGvm1v8#K+kJzv7y z$3FB>fSxGzy4mvc(9x2CX($CjiHT8iSFFTo;#w@SM=4r$>s;j>14?!R35locR5z^Vh&y{Q<#zWH5L<wpbL zO`JD+vB&Zidw17DzjNBLsb$f?7H`hEp*USK*`nX%=ZcB3PGFe(24&I~0H(te#mzBq zZNAln&^mI2o~4~!1k>K+TD)TGWm1AJv#Uut(I@F7`PwVENOHpjBr}uCuu9@rf^u1k zW&KmWx)e#&euyO({8pQC8kaj9B-1WiQ_C8TZ?*yAfuJ)h3cDjsDSyll7Wx|^@pJSU z%BZIr#@IjxiGt^f3qE!$=(p@YN1S53V@Ahn(>LHHGE=qtbUzL@YUf#4X3O$eRzu8A zz|Gb2kZtS@B15}xCw9IYhVs;VHLHCX-m)_VLhsKbc%1rdB&PAyrlX%d2`OrLwN5$N zF(~%6INm2%v6>HoFH3YB*8k8hFP|-NtHTH$xrS%A3J&b(@{q`A8Kx0z@0@rrae_zq zd>j3Zj(`u*X$j5Gog;Q^_QJ9E)`5Ax-Vp@UAz3w68TkUEyG~m%OkkV z3x=Znt^JR!mSnWUVN3J5`KG)eql%Om^p?5 z@pi6|q(im;2*bs0O-S%Ospr!!As+az11vE#08I^J|KAuEI`Vk6rjmhp%71BCMw-jt zWbyyKVX2&a@>j!>#%1*v!=mx5<~PH#Jo>Em{ZpIQadet(bqg)O53uSN+r54buzneq zza3!FYjrfPy+)Ay8erWR79@*iq1L}KEQYn#<6Y0byuUFlh1%V1`->gEH-@FV{orFy z1U`e#-x(IN<)3LpFD_Mf=UTFKqOJZ*!}9aT#mSeCldoQ(P-r)}t;s5Z^Q!S2Z55;O zR6xsW1fasP7D;O2ycR|2SoO=W+zhZ7qdC?Qu*^RV3y;NJm3aO^j!!oOEay*&;#>b< zSOS-nRGNoXZw6SOH`4S}I5*RcO#bBntHS@peRf%)JzwG@pXcCjhQ(#OAYkj)0BhIW z=GOr09}LS*X$X2LURk=yD3xF)UV8k4X@}np%WhR!rt5BXRfXg`(VDj1-P*>%4X!VB zt#htl>N`q6P7Tk0Gc4HLdyW0%ZhK9`Y_)qDtUTShGDI1l_FCVXxa~iicdXrSTe|;c zzg=w&?Q6$IrrX!for>D8U3+a`zIGoDKAe_5o^v~Raj{i<@bc>H%fTx&9G=6!F)V)! zu-qwAq1{;81NZjh1a4E@<6-5ZkNMrO_@q984eXB4s=v80EKfxc@|uH7?*^tm)SXO7 zpYNSaGIU~`26Gd87*8ua`B6D-%sYNMTg6&XKdWcz@$H?3!mPlY<%9ii?`>ju&*t4+ zXuaV7vGBjf{r^(<+kX{)n~Bc)p&xkx#}&WqDs?ETtAm*JJ$TwssBaz zHM=!+zYG86UxdHawTs_%7UxXy@|W-*)Wd%le)E{6B8|mG^6O^#54W|F@R^AE@R3rKEX&NjmsHk+j&P=rXULX3oj}e z0#~96*XNsB{vSyC*wz z4f(QxeTMa@PO5N;cQ%v+ev%$;JrzP^CI!LDbZrN@k4Vc_P9rBtF$wjrBKLMZ5X?S1Vv@ zQ-w0o0E!5#CU8b&+4L z=d7-}CUL2$yG|n#zHUWHa%Fsmv3x-aS{n?LiR+sul(#zuxi)6YUh3BMGk0qLZPoq2 z=K2hA9QKEU{@q9%-*xW1R~X-ryI9rjnfvwSL&xXG>mb4xgl)HP>D6i=4qpE5pbc+6 zANT2_Y_S4oi&+iw0Yx$u5oq%;( zBz~B|#?d6dtv#p>{)WIvU1yO09K?&VNo|X}zaen8Y-)u{Q}gXP+}G!m@@Bdy3}YbT zr;BrPz_p5pBh;N*X{^fc^`Q|6mm!5n8ser9>Zm-XcyTd?8Q z(~-WLf7z?*q}#4G1CqNK)z_2zOqFM&>6`Q=MX0qkz*??p3WnB$ZEMn(M(<<(2EcV zU<}049ixN_i|X^bla3zXh4*LnA0^K)*~?#mp>mHmkqE6g>?n$1*-Wg|6w5ef+`i#t z@fq&yF$aCNYGrY2gLLzS4Y=gz6x|QR8AeQ-G-&}EW=R4}b$bZzPN>Fh4CTxWcL!b^ z(V>ZroAKK423@Cu`g@oVZ0c}uPR&?rg4R%A5)}K=t8J`aSV&|cy;QqvB*Tr3E#CuO zK3>g~L;%~D^W#|D;r(!(?z(7s-YC2`t zLZaPTt09*8UJPt7C}rlAgMr!WOXYAGmpc0&&{|0qle)ItI!BSN_v1llEpyWY4`KM4 zGI$$gWybNBaPD;dv046h%7H$!A7#XIq8?40Is5PxCZuZ{D~)MvMYMl4eAnQt9?v5%54ysj$u6^X}D6J-^qQE5~il-CH7)mMJaar%Bji z1*|0xD;2x8Cd6PzFK#{9DgJoi&hqN#i-{micZhQjvIop(Ccv1tiY;}~3nVp0vP+FC z69)9n1oB0TE%BU=a#6jp0!Mh*eN2M4_2V=hAcUB6NJut%FsY)@gxKNf4t&16*m&Ol zwMfQreHb%MBbvyGealidhiai(Dk^L}u`Yn+Zyy-Hf9kIP$(E^2-7w_;M?Q5`iYeTA ze|+jp7pvcj6IZ?Xqgmu`hOjEFXpMRHt65ZEK{9V#B$dm3aBLKR+_2Jdv+bM|Ys;_q zG8!_W{i|7&1iMZF1lI51#L+1s^f;LNUqP6eA6~0=`t|~OYncznel?4zM1&G}kfQrx z)toCt&#BvPnnhO0d>u6BcV{|cDJZhL88<(qemW!4dC~Q)qtyS3hR-{qhiJ8v1SPFA zB{j^4x_^{^ojhKd+Hf8Ai4xWI^T*RX8q!;O@yg^N5q$vPjW!ZPO+5Rk5bn;@9}Qom zC1sK?Pw2-~nY;QFC-neK`Xq?#u^*FsuvUP25HV31{bQcCDpJ>#fz^+40$_&~g-Y;+ zZWz>s8JP^iI=&hEjGt_8cHj2V%F&%J_J$OdNH8Q znrT)aa5|G3qQGz9$=kLs4aOP9Y{=&)L`w88VmdqKyRXjN46(5bxKeuFC{rvr4&d^n zb_kE}roFya;W^UGvdXjo7co7!Ic}Q}dgN3+;y4Y4nO1nI22Fqjbh&kJ9vJUsgePCh zKT9wi#cUejAG>K5wJ1wbWK2v6`x>22sWfcq{xCs;&L)otabpx4&r? zIb?a9EjTNZ-fTO!@1HGt4LvHz#3tlDUk>2LY7z@PN9~`_{LDW&UyU3pHx`HDTHkCt zn?{K%h2i`8eM&d|YFG)ndF`xb-{vC|_L3Ls+&0mE#3-MVtR|@;erIEp7@r*y)tIOjo(iBy3Gfq_- z1*G$^mZP9~*_b193)lUzoT~)&aYK!LhO)W}(e{yX&!CgMRf0rUg0L zGYXkb#ap7ER_jc*D8l_V3$c3HW!GX+nPC^3e6&j7iL=g$@TxSv2!bkAQVnDfUt|Ez zW}Fo>^(4XzS&U2NEK58i_~PvV-3ekxg^Eiz3D2+WqFc>MBWNbELYV1buTt)ydNRIj z_6e3pwTyS%l-KuDhIolGB|*Q2M;rb2%`n&L+l!R7t&X5rsUBT<7_jmlCRin_{ zY_Jr>y6Kw@Kep5=RZ+{fH&oCDvLbHd+nKa@W*BDE4YT2<7QejqXH>%CJp_kKwBRW< zk>v2Gq#GwS;=!6A@?{%%>x9Rs{V#mTs68iQpT**d`+Tb$gQ9Whg#8#9&1>nq5}Tq~ z@2cjIOGf*V^Us!^FAzOhw44?VemAwdULR!!<>U|9Uhb7IR@4CwZWWt>IWkQ>H}#Ug z`^0JAvo2Qf48FDU+-$@XW4-h8?(KWQcl)~cFiP`-_nca&R-bCeK$;l$;DGxWmFWDv z-4~6~opTx2p1w3_D_$QI_Oz#pP5M2$>%?BLT9OWV(2TBgiOcWVbtJyaE9ml098$5n zcNQ>Yi_aZ(i-#wwV@||%-P}}5yuPJSB;Q`91WvAubSB#vF`!pVt0Tx`#>}aESAI8| zs;@spYxT`r16{2r4;_3*dr=w%i|HBH0UyGzY(hD%z2n=8?S+3VIIU0~XYnwzCFhI7 zw60r9GzGW9V7c*}-Ur5n%%39BxI&%vrc7Ur0xVS)k|+vEk-uRypag}Hrv?cT(n2cN6L39w9e;pE+WzN9GM;5tE89-dZ}}ycX4%B<^Vl>ySCl~v4(b^V zo$z74dmdRe8(2*^%Z_Iz5n3eT7Skl_tB;nvvR?=oKQI-*kb6^GuK3nza>orE!jPFt z^3^Qw%}4W@q){PSHriI4Ppk^oDLb)Uvh8YCRg2>mx^B6}V&A3tJ{nd~UNH1{jwnyM zT*eN(<;Zk;v}us6dhVE4dXVpQ7$cXmMskOFh#_v7?Y>u(^JAnS)1$n7D%Y^+%S&dR zuEn7_vKd3eABz-usJZuFif>}sX;0P|3vV&+ue#6uAYJ>ZRHSD7J@1Lz>>5{48G(=d z;7gc|9f{2h76U1ykBtN(_~<-F#MM+9GOf^;%E65$^H@Qi$~#mE9n98$Z`=`b*d7{U z58FI^Xy60THCsi@(Tnw3VVOZY483rfl=*DE84+TH91z!IXG#uWwH+Wk4%wI^p;|XN zu)QnBVJ~(8g$Oc?n?}f%hU!p8^`hHbv;=anN0}iQ7_y@*FKC{|Rqm`-P7-kd4j>@K(X-e*`D74X zsIgY0vI(l0P;0}RV>;3kETU@=6Q>5bVQq<1@1R!ia${g&do#Ucjvxr5ubD-lY!sHt zncD2&pJD*URZyxL@bH4zAUcL#|MoZISgSNn^bRSK-mv6M!Fq&Ky&4e8A8m!?w3?0> z{zeQsBi8J}Y|oa$IKp(d!R}rKz2~saF#sRwJC+!GU^01vrWMfV$)d%f9y{?@#}9a& zv1de?OAJ6H9BKur98`UL!F@RF*lgJ?_+0@vTc!yZ`b=%0HoZ6 zcLN9DgsM7)$Av6?KGnU%F_cwjw~0_3C+!La zl{Mke;EYY1O=Z%?j8XAP#qPDy#U|oDFO-u-Vizd*Nsh!J0rYEhrPH zdaCRVo97R#0``QNz6sM^y_NjhNJ#oEqt;f)z9JK+DFY;s!C#(PQ?BDKuZO(=Z6DIV z!^uF~YVIkK#-=PiJ8iFM6hkJ6*Y^58MBn!)B~m~YI1o^3@XE0ki-}YM+U#v_8~K*% zF+73a*D=k-(#Lbz2z|CAh1H}Sa;lmQ4oTg?BNKd-V2+nHlFKCsv@@!7EaZZU@262I zH3{Xo;c#FI;B^<}*Gb?p8Q|3h0^g3Ln4me-sq_a-Dum8bhv!oAr+IT!5GGV#fi`JA z#acG#X6<7sHFN$LIEHiXEjm`Yx($X3mnOWgn`-qAFOat2stZK4N;8j&g37R8VyRgHh!T zn_|`WtAeL%Hh%?Qqm=jyoLzR#>oCdny&Iu)qwTB*%8OW-7R$#>V7 zG$+W4B%qvTyXaa;bzCJ>U5&k_hYgWk)l*z($qXtZMK5<*%JR@hz;>W$8K0sd%w<}Pv zGHIsf4x|gw$LpSAl3q;b`$Uwm%9_*MdY_tvGP{xn!nCg{nU|jMpxA7l%l7BUR z2I{3`eRx}ZwH+TD#KuYTbRCnaKqvs>i=|I3z^NF4B@kv~01{GSjS}?2w0(YG6qty9 zZs;hN6P|RlTupu$YQ+!dSCf#rKrWHj%Z=Xh@8!loHG#6agfYclV3j95&UmuW0hniU zz=4M0X?7~eijWtCvt~0sx8W&X1uf}QCP%` z)XsybE()oA7IhZy9T+kssuLlKXxB09rFMseta)HPOS=i`yd^KoTqpeVQkXEWm&+28 zZ66{;`=IUNYvz>4T;vY6zHL_u>6$6T_j--7YhP2W5%N9z33a}nh0VV}BhzY!*3uzdKohZ`GNQZAZ#p3L5q|SzmL9n=$;Lb zwu%kb$HpnfW3UC8Obz3$VS<{5K(?3!-UN68SoZwbC>tz@H#R8j4YU|9g$FR<8Ksmb zK=I??K?tYyfT22|OAF{hBcRhCp(q~F5UR6k!S{@Zju4Qv=>g8PkREAZWDko@0Q)XK zcAGSi1_wfT0Exy4@)R5tasstFxGfEFwE^00&S7{Dzu^ZAkiak!(xC^@O@N3dXkM<4 zn0n*CuY>S<0qBHp@C0y6_yJozpc4roYyfn+DQxtcy9273Jn$Xi~R|4BUMakKIW!QunXX-BG-AvnvoG~7v%Xi(8FBs<@s1vWU(mse3! z%b=!Jc3V91wME>pk0`w*ko$tBP{g~0A@B)rdWpXH6M_EntHdQtZ*WT}w2Oav zVr-$1Wt>O8AHSCfSlhs08awbJ4UvI%@Na;MaZonz_#Q)_BS*h3Ey~K`p9cdImOx1& zz>@@Y9b{46Pqvq)ZJ{klsSMKtmV- zx590_YvSWXs5Swl&vFp<1isbG! zalZCCg?I14JAA6*dFUC5@dPA46kq!BCZ_imR_~&i_gIP!@YEV8OPGfVOu5?Zdapq5 zf8A)Z#mX@V^^S4IzYA#beRuY1qV*)AV;1+ za|21yHF2Ni0mc#jtzzu&NvQ+mU$6b%bI7}IKO!r$28_HQ=yD$u3L#ewfZ9uvl$VF> z@(UwGv{_oB+()}j<0RQ&nilv6TzO0=>JSh(!t_3no!cCN{Ko zV5}QL&5xc^D}sFw+Fb~Vfp0^@@VV|lC_^AlFHT=PKgI7o2J0X76d#4#qCsur@btkJ z3=nXSNJJyiqgWCqj0ba}NNV{pIa%P5?7ET;U?2zW0Y;`DLz-pJd8YUI>5uq&*MlYj z{2`T7`*|p8_WPXS^W{k92{+CnS|}Ke>ThHnrLtQsu?uFwbAC7}-mJ z5n(pHJ`jyB;JI<3Yp;rAQl`&8C)Ptx!sE}pGQ}u=4aNS9E6hK$rJXO~Z*5fR$v$C+ z>iG$?NOzVE>sM%lUI@Ty;#~m)VT*UWa#kw6P0#U;({xM;@A%a%kbUB75tAs3%1){u zXuP*czBvF?_ts&Gakb)-mG*}lZJYQ>&VQOH#_FWLHb47l8p$m#dJ)ruMl*;(;^YYa zylT~XA%3S-?8B)|MU3iYJ;%4A)!Fvr$vlJY~MR?5cY8{;`pd zI$K`9%TRQ-5EwuPOA^xJ&VK0aMDrNcCDfrcKyaDEF(Awk^}QORc)hFYHx)pfQ`v{5 z)MMdgl|FeGb^&cd0ii_r7(DDNXhZ?Utz9edqSoRtjU~}i5iBP-PO3g$JKst;5xo_B z%7%ZvuoBeFK$`4Us9R<8ya;7^Hc-c%gc`RpKd+F~?VH{LG0|cU9=hfEdQ_bhus^8|E zuZOg4m=*DYV_TVe4e(7@y*Ht4s!$hW?~=yz9*iG{6`NjjOV#kx93O7HNFqwG>{}5uskEEBR-Nmhc}hkTHnUadIV2xc zc^GodTczg3OXzY?Vj^0Sao8`}I=d<`h(W=#UC8wR$ ze4>xBQgiXIhFe1D`K^l@S0>aK6OSDQlQr7LpEFIMdY74()!C#)XZ-C(zHMqhTv$Iw zjl={G#vNhj-*yi=V`MDZ?zkU)eRjpXvT#htw7RAgP|jj~Nf6KzAwJ2G<(AckCsg)D z0QPXPR_&Y;L$c)Rme5*vEK1DvwJaza>EB(H2j*6Yn>*zVXf`t`w8krwCog&Re!>Vea5~;@LN`0XU z@%%p4>&_`cq`rATfKp_}{-8wGTZfp@Qp{j^IgpX^MxC@caKzUql~*$|mtR+XtbCjm zuH6zVI2#C8w2hWHx`c@L=qn?Oh}hq`L*v>w+dT1LvG>rirDjX;b+Bd%6E%Bf?yb|| z{ZvfH`5X=pU<5F4&c6|fAfyQGpNMwghM);mU{P2wSCHK;As1O=HHZS7F4f8;nPT94 z>`}IOkwse6IiOE4Iz+%p`J0F3YWZr0C2-uLEhaKWS+3N)iiXFXBuFvBnfSyy(gfat zVFUY7DtiTEzHA4mBQ>+0+`=(CY?LKtR}g%M+}}X;r=9}Jc5Yqd9ov?Xo2?M0>FexM z?KL9?wSEf&RI@dxF-P4^4QbxhpKecqCG74b#PUAw?LeyU20a(fYqQ9WZ*C8bwePUH z^>`;KWfndg5p~qOo#Jj^sVuLgmw~VtszH{k1nq}#;itY?FY^={@K9a~8{E?P!XWf! zOs^ZmGGXW4?cj_uWj6e0^B*ExruV_lBX5w^)aC73eSGbanxt7tTjfLb8-d)8ACZSb zo~JSh041Qkjv+AK!;K6{+VSny;n$vkTZbFNi>w8U(5 zM8{}{%F(?DEACZ#vTPQdvqr_#lr?e+4Mu#Oj|y5;mbH2T{NyU!>nkEFly?w=^4Bqs z6Z<~)2B3@?<6%~@o!^VleN_g0q`r692VwaDh@5Pk# z5>kYt8lS!W=fpA56uO0|68vK_4VDd=xcdX9(pEbL!FID`xX@1|CoDKD2W(qqYMF$Le&Dg?iz2c56WpTA`^GQY1?qS0ki#G>p zTiOd&+%;*sc(Biv8?6B6vhyB8W3$)M2|!yos0h#DNV|a-8T8;bBUFKfI^uXg#l7ww z3VV7`1>;3p#QFJ)rxI1Mnm;7AVYCJ#OCw>d>E*7sq{67*Kaf98{2EPr@rf?URkbZl zo#WkJk7v$jqEf4EWCvvHurN@^)zfFjh$!Mg8@Q2s#yTv4cQQh;NNLj_S!`zS?+p3O z*P~B>t3N@r8I7nr-X~0%*5*?A;s=8hIx9$k(jf;R=@vY8N*@8 z6-v8V#avM;nn(3@e7l}-F_uQ~bT@Kphx3D;z#y%IN%>EPMXwk!m6atAy*kG7Diq_HDuuk8olt{pKRY{=K?2RO57FgAT`-w=3>ZJ<7KQi1z2R6#UoZgBea zs2cM~yQP`aHM!A?d^kD0llGvn`8vNi83_QqLxOmk&6U#CdHoRIVi)oCM>6t=VvkeSL@@c7 zG&KNhssu8E!ptgn!yzB$cL_A5=WBd4!O(-<080bOjg(q?J=nA~b17~x*Nf-4d za_E&vu^|mveWiWbJETX>lOkRA1y2te3)vu;X~vDw?0w~$#7djUzJ%}y6+rQBbcsd^ zx4vnJHUlB~6-W#egxveK)D#oc@x}w)j{MFw*p=94bYKSGn?o zC%GdNM-$FE5Eq?M@{QnONNl(R!n_K*YA>bC0be}sNwMEBGN2|Tpw}m%DNGoD8lEg% z(vaBmY#J@G;WUkYY-E;$Sa2uK13`|fh2JQMh#q0Htpn>_#F~)Cs&asB0Yu3jp6>;Y zRc(Fv>PRbr;NgWvQnn_NLn~}GR2WU@kxLHE7d+gz>LtTt+4cxo0723bt7V}TI)>Z6 ztCZA(^Q7o?2<3QmfM$taWNlIO`{#ru!$6#b3Ze=lk3Rw>&!ILzz!7spfnR8b!8KDt zg=Uo+tA!Fh#lg>|lNW^Ig2lBEdO8JgocNxJl-1D?{x{(Pncj`#A;_^#U^3ckj4hD* z_DPDL4Q;+(Qn4@?Q#~OS-hW1mtAfb#>%e+AA14`+aOtjt*{nho8yo-m&DI5B306;_%hSS=9(w`Pi-UfavCWWK}wNm zx}?g}adu!#N@4|lO}P=y7@G=Z%`oD6eda{MiO6Je2XJgJgx+?N-kToaK(KQea6Kcm z$ceGON(Sn|kK-Uj`uc8E2J{j$Tv0V5Zz*;a!6+Q{89@(2F~V4)fu}rVNL+ESV~PN~ zIyUa~fWdtTS6f)C^-d zc%c_D5#AB;0f7}=w6HTO?p*%?Xr`!6Z18wy(GZnaH^7@9^R#~7LgDhm4xZU@kV&)E z?Y7MXKE`whkK43%0qYhsfRvvrF*m$NUHVJ+pnJp(U7#Fu(`pS-^N zzC#9K;1xwJk8fTgaM6u;dSQ6_J@S~pc~k^pz=1vauBVL?E52#UCJIs1m{l1YPfZnN z*8vI*Pq0EuzTL5`9TBbTOk)as2S<@l^{kl3^`;sUZ1;yl_ozkE}jT+hR2#q zWlB)<)_P$m_N9#!Mw9rQ=o;7+R*a&oakdhg8?Gu%p2>Jp7G}R#8jwvjIXT}&JPc$j)|U!z9LTku#A0kk=^hO&B2|c3ElbN(;xD>GLf@)bf0|t zAeQ~;{J@*|W%$SB&>}%>~ zgyNBAkFMb^by z;+7!p7sEN$2MtX?I%Cbl1AIY8@iqjdltUe9T&V)0Ow#t{OBI8nT}L+wj`rg_Y(tU_ zn^^Z9#z&8bxh0Q}l0yl~7;)5x6~c96*DteP+w?!p0{tC4KyXuij{jBlQH%(t|M&2K zVU6W~1`kxvR_gt4;er1hek=RJ;Scf=xGDKfdbOI_s4kup2gmYGW{PE>-$6rTYWH&5{>AGdSM#4)Z%>jNFlea{1rw>8Fm z)^Dg8F*3rrl=%RoJf%gbLv)5Ik2j|=<>jY+H;8#8kBaGYZX}f)0qC>Q@dW5hNo|a(nx@Cw)Z*zr)9k@l{opaw>O=5*UWhG9hwQFF*WkC6QI|}N zC60#YyVJZ38700Q{c{2MUif!vZmGQ0k}`2ab1)78Kdvq0`<9iCCyQYI5UA@>Y0Sio;G21;cy`;8 zPl3ztv-y7Pjck@|Nw#iZ>vMNpmBzrEcP7kPeCUpK4_={2o*(8MLErXc#6CCW$Ecp3 zI;I_;F*zU{urEE8?@grGLYbkG`S5H*D9>@HnULqciXl($yv+)Eq57d^;9B8u_NS!P zh7iLgv_$sx-T~v+AF8Nz(I(V*`D54BNCmIyI1EQvNRWBa5-mp-8gm&LMlTi*PgS<$ z6{c^tW)b%Lxt<=lUO2AAs*;{JKv1I`4K`<56sM#hrUyk1y#^DazS${|UMoi3|HNd; zs0O9HWDUb}u(k;1fUZbrp1;j&mYRt=mlW4l{l z+L56PsVw<5X*q*pski$Ec{Qeae6(2Ta1>PDFX?LqUJcU^y&vSrRgWzp z5UEVZ>)cfGG0!qi9Y8|?J)w4=$<^&rBcV)emODl23M(FuL(#X;!!C%8Egh0;RyS`$ zvz6tNs=SHyhwQAUq3RX4SXN0ZZ&!6{x@D@@9Zv?D_!eEW5b%>=Bt-JHG7);2u!YJR+MqF`TnjxxNt*z18x5k_Lsl1C?A@T;>)* zfM%*66Pwi=m1H~`aoKGOQ*S5MpPYj+*O&|j1vc{Y>SQ(4N;k*Qb|EAP6F%#hdIN}? z+QR+Pd--Gu59;L{;i3;ggJe&hZp%vhlp^w3tJ6MYcM*xH|-^Q$ANwuEuI&wL4k= z@QS?3H0j90NKy*uFfnOJ6!vs)AW!!`y3arQ2+xcCi)D1?vqFrNuWk_?b2a>8MXv|& zJqM&9vq|k7i*yJtmqpXV!f!8ZB@S?BYc&R<-zT}o-8$@wpFXHKeRc6O?lG>V!498j zuN9>6*g8%F^B%O%+T2Tld&v-W9g#-45*L{hq>fJv>SvmZi~gKvR*DB2;3WD2gN)j( z=aLNyw2j7TF46SW64+){~+>o=|NAZ>+-N{=Fp zZ-2x@i4Pgu9^>FNuQAuYf2}4;<)0R0JsQ-GdP9+!_pm!0AN!8d*!HJw6?i=PQU%r6 zNXx#iUbm4HcjTm#_x=-g6_({er77uNA?5GgB!M6LCw;pG)iB0M6>#29;mWs~lh#A? zDqJjO!cKKy`vw|c`e#IXTRj?c=ydKx^&^%YX%eu4u1XxQAQq>ZL&M#CVOY z3auVX*?YC`{`RSfcen{Rng}@W8z&L&S)E97&o~l1^MCEW0Z2uft(~>L;DtCdur9?@ ziFI$w&}rjuk}WI~@ONb03;KvdbJEuS3Wtb{>y_y$NcC+u8egn+0Jf6^wyNO7S+bvB zG!^c9HAzo_j)cV+2)&rz3k99y)V!+=j$Yl)QByvKk*QMVO*5f4fio==^X^+aRDtqs zk-o>zuNbZb>L(uR{FWD@rrR}>_1G^*=;voWUBqmuj(?5ri(RwHuDSLJ|IzHOFE;G) zy_^Pd-``y^T1)+a*I5B})dhRtChSGrnU;2z{ykLV57sR)r|2;mCluNTxT7fWY?hkE zog#P{%jZ@fzQflThN};Yh3BK~Am@RAj8z)D@`)!K2T8YZr?z6WyTu+K^EBPxaJfkx z((ONKSWun63;l-_w2=QM1x@nbq@XPvtt)5#U8qRs5$oTCibyz){T~+#OL-lW| z_{Luf8c1uy{|FWFkJJABP4x-S^0ySUfRmbms`jV9rJ()qT+M$QD)Lt((vW=Q_Y^eL zeH7$^^IxQ({fR{W5i0WcZ>pj@iwP!4B45i|wIu>=IRNoh>0hBDC0II3dgQYtU}m93DP?wWjqMBd=L zDlas?)Z>o}5jRwR7`;hBYuO!7~ zGkub~wRfr8!dgG|8GnkXby+U!Dh)&Ry)p6B6Hqxb1gfgsXQ(l4XZdK9M| zKbN5xIRd~eaRvsI^w!lZYp21hJwbW?Cw`hU7O&J!HQa_gIG67K=4#SVB4YLzXENE` zc3D1eI2e!iQn(ZF-v-#WgI`j|`m45%Z)OuK1#YDZy}cFwGx~aRn?9DW3tsl15D@NF zHj_{by>?jt9QpG`^EKNIKfcN>+hUJD$GLTN*iUiq>S&n#>DBR=*nzae?Umf4lehSk zkG{!S%hgJC*dH)JN8Dazkj+$=#GUWpb3VRoq4%{B-b;=oxWcjvkIi1KQj``rIzI^U z=o+4tx>oH8A7-XNT|~gW^x?BS7y|hhF+A7u2rN;g@T_>;JSbdjnE#N$(pimLwmGgB z^qT1$ZXlcA*EJfmRA3!YxYW*H)-y55Llh85$Y=w@W{>n8=^3B?M| zJ5)4mpcv({?D?emYzh+vrXDnG5($I8VWp%0hrPFci?Uzaz6AytdS+-OW`^!AgB&`f zBqXF;1*D~hZWubGyCkJl1Oy}`MM;$oL8Ju)<{9-`*X_09Uhlf^XM2BmxA$-Op2v0` zvG1SVQqNi)LgLmDXzdzPI35DALv^<4DcbL5F49~@RV2*2PNyq zS7gvjmRh=Vg3Rvd2)#+67ZHbR^s^*Oj!?W66P?g_ZTL3T5_4y@EeOBP2%wZoQI@Q4 z2ut;=OWgzMw89I~q#kRbX(;Via!a%*AI*|n0a{j6C1t7-UwF=r2=IomrB(%0g!P-c z&deKTtuPP;oHA}u!Ju$#MADtrCzSAdZDeAiCQC_pg}h>In>eFcY0;#}vdma3*t|)o z^c_!ykdgKE2x47+fe%1zi*C)ZRy}ytiGzhTSK@=LDEW~t2U%cSmTK<9@+;RJETLH- zZ#R!%T-G6F4Y_xLhN%xMHPn^JgJT*8b!)gBO0@WCs2iG_o?y#zEAM3`HFu=e-p}Ng zyTaY|EZ(Mub=-*`*Y54$$x%)$<+gn8XNvx$o;n;XZp-?@=@9Gp_2zrq$}DPcpZ6XK zq^E8;ygi$Kjvk@NDN;~#Ac4ydUkh#2(5_K7jG0@aKe{fNs^TUqL_~9$T4*|BW$}P} zcKwv9Rl{}1vY;yU1C=NAk*TJ{+mH_>B2yJiADrFERWWEN5}wz?@ehUzP2akSbAMDI zaIJcldIDFWI$$008B*0Oc1eXzy!(3Z;rFyN(&AWLsX-)p(x+rli{x_LEuCzkg*3%! z4s!iSlCZ0gF(C;!EFd}r7klvv`hDjRYow(LaT3vb@5_=Id%MAylxZsKLdr!!ZHV*Y z+!{GTpE)iuLC1m!&xczhp2wTpXJw_Blv6-0+cwRFf{7f^PqFBx75PS=IYF$~l1(+` z?f0df!BBMTZK*wt={I10GQb;hm4jRT^fSkUwCwI{{aIhtX2ox{J?$H)i^i@2Unc^L zmcJv+lipgFo(mnPqdiRiK%abZXO<`aGQvpw%dl#oFOQREy)X0frfvf3WD%*bkj4iY zb$Ors%KMGtWTqwtR=DqDhYzZ&=_ zHQH70?tIU)G5^Ds<1OG^+0yY?=gqG{EhA0^*hKDgrMuXIehWlBLaQ<)odA`O#@LGf0M-6yi>K6*DmZq5-cRnEC0gpjc=0KQ`=;TV|0B3) zjP+-5(f^YBM%VN-u-5o*+&AWZ3-as#l>7EyEn1_~pP<@_e-5hsZ(Fqg3;3&_pxVD% zd;bgW+Yf5RA8T*h&H0A$Y&}7_GJ_ys&ua!RH-%B@f%5~qykDT&JK`Kp|C47v``vRF!G=<=VvPA)@lOZc)n4!(W3bax=w7a$*y*x%bqsd;(d*5RKd{pQheyW; zTY*u5XoZMNT#kjm4qX31HOn2*8MJzcM9W@UH^s;l_MT8+OD#>#L#f{Nk3m>Bk5yDY zYNgys)Y5NZRw)_t2$K<4#Ke$+_Vhvb&G?!C_YK?}CUI@|Q<&wODHW`kr#CP$WWSTY?)EO=4VPlD)1S*NQsqBa5U?GSxs*3M-gOvg zLvvO6tC8x>R=0Mfo7A-Zq&Ttx^!}tc@-%4h{S9V!jbL3jaMCtg-@?st>x+pZq27AS zO47UH`-^E+e1_dMTYN?zqUpAt9ln3YVMB%R$apc?fYgd(4SM@Tx$5+pmf$(xoU<8_ zJf&NDk1h6dx9QWUaYBgQV?Om$Hg|5>4jUdJ^{d?0GyUi7b(u0W>&0EN{1M{(gT$oS zx;-1BO2W$1SvChFGREJ!rAu{MtrBE$sP2}xuzV5GSe6>59?>!I-%eRMCe}|C7yZ`w zZcB>bc-oqIWw;Zs?Z9KkU7KOU?dKXQt~Up&grpb= z2`)s;Tae@#YI9txVvI6bBry;e4;A6IMC#$fcrRZuY+RvZnuhifX1Yqpf|j&;l&am# zL9)a3uwch^6htqSM3@`vjH@AIOMB3gNUj*UVVLY@zr#dxV5JznTc$-4p^JxKh+u>dwkpnPm1(&1&`I$>5#YBtEBSCElf>e5Ua{$d1Pv zQWMzHwf0Zqx`nNa93IMxT!lgsE+CP{{eR$pIeWD{9XmXMW;vEh-2_MIrn;Wk8| z`h(RH5LyT)x7fH1OZY3$04*VeBv?-*2O-HJ3y@7tc*2qT)sbE98XdVh4GRf*Te%FL zDv1BwDu>3x0kOTCfY?sH#rFXS2PSDFBbS)2r2@I#x^<0-LusU+zGb;#Nf1BLLym3@ zmzgwAELQYju?a#MJP0PD^pQ{YoOmTY*iI>jP%IT6z%@aSM+dsxR@fXX(mr%WS&-^N z{d(i?p!c*YTwr2&rW+cES`l6y!ennu^aX2#r@Q5BDx63LnZzK(&5i)-wA(zlcA`=A z$6R%BE}Ay9I=6G7R@FBOUfFsHO%w7`P$%Zhe z7*FDNDy-$DeGCVDWo2YvdV=J7E=*FD^lI4#)D7QG@s9#9u|VoIY%wC4#68ZByWs(& z4~^TCR>(NQ)e{99m_JCIV2pX7k$C4)#;MyZ4gRC?IZGC*tM6}UUhX9_G}^?N#|@y) z_;trr@_6Jk`?fP13L97wuR8UhQw1#q6+x~c*jT5+rt$h57uIWuh_*fgm7NoMd7wV- zDy45*qusu9t^NiU6WcK%9punXCZuu{rtNM=Gu%FJI4hWO31ldKd=-aVd6>?3-&u|X zqIuB!jvOoc@oie8nYwo7qO?)UUgGBKsw)iRZBLcVYp5*)ufbVHV(P?`wyc~SXXpjR z%!(OM?)mL$X%|FI!V}x4DJ?PvPqfGileQaE9?inr#QbeZ3SIfF@oyW3$%1a?J$zO* z$mrIhuwhF6i0njgazslqA9k1g%L3E>al*@?rzC!jKx!hTTRqZ3dzpY6^4qrBIZ-;U zweF9ZnY_e|Y+Z8`1CKeV(XX}o6aYkJP4w)Q6r`*IS-$%+3SqI#O{da|+cMCY4UC?80&c~nr~gb?X<{c3p;y^%C~B+6#lJ#^{@ zM*@aYKO#k4`?iFBG3`!QZ};(Z>kAH*ma##Fk;G>Qrd-0(svj(tzZI;Q!k*`jcB11^ z-NGMPT+`n#@>2MmozU@ykU_ef9)Ho-FoV7iI#^anI>p={4%JVe%V-7;jcgSkO6@By zb+zu9R0Q~t!5>EVBPpg|Z8%GkE#6D&^F6pRbM&?CLa}%!!yUN`PtAMZEZamvyYpDW zWT-!E&g!$!ccs&qm`k*mb*%5q6`Sv0u>+A~U%5YdmCo7iDen4+5{8;~oV&E&+_G!- zdZWYPaPO^G~&GRU%v08htn2T2>W04Q<^u$zQiu!Biys6d9$ zH|vbCPIx*os64Q9}=WwS$Bm9(GR^Q0X|Oy5xfS!2=$$Y zg4*riQIU{$=!Lu7t`ZO+QOVB55W!RvYM=F3qY| zL@W;M>?Bc3JCRP3O2rdO@4y6%5}G$89e^VI68&)?;S4iLcwi>x&7=fNl#o$!#;c^S zJ>cXo1<5-}u>dSm2?vbrB3VaJ6$hMBd`z}g4kc7c!B-JepG?7Xlic1&N$O74ci<4o zOf``dhs~r$?58q^q?#M0*$$*$ho_z>k9FefPbzJkr5{S zb4>pSJUgAVhwR^t=~vAL{!5+KR4x~(_*-NDKZ8sDuP!Ov)C0rMzL`Ae2qAj#$mxHY zC-a|-=>@Kx_cj*q%sC${Mmqe^hgf?=E=B*mqzJ&9bYewRapfdeCx77C<P zzw%^$jp>!?BYYaa@azQX*T<%{iSZ5mbMjM5LJud925-+T@u9Fr$Az%eu{ zKRCD8`6trXWR*({L#ZB!-QIp!b8!9>E_v^8{0DJsLS6i(af9Y4p5|m@>H*g~L-9i^ z+e+Z1>iZi1i(CEXwRq|?6tq=FQ}(}zTQy+juzu57O}{q|-|WMj;~74{F{sD}=>Tc= zlaD0~e2IMR(4CXzM5Q13!u%~Ns~P-9nrqozou@VRE6>{eCIMR`e0L|DNm{#CPaiI} zk-*2GZX4B}E_WOqc>40b?R@{*iGR!PbNBC;l=FizCH{-UNrML$N7Ht-7spRuTsw;C z^%HvgZprKRrOdiK$y3jxX@{s7Z>|mf`X^@O!JQfg*o>vX)y6K&yq)nynh@!X$n7e|42|U zIe6Q14~OXXi&uLtvWy@jp`VdYjnaf>ZRHlwwcs@A9|;Oa?^u)2sg|#7nVCW=2>2|# zXP;uAcu~jU=cBm@83P94`5YJLoyeXLwY*%a+DRSEc<-C59WUcF`6KcPJ@dqL9~1fX z)%hmd#}_zrwV2sk>Qgml_Wm@=#50EBzjEZ;&6E5TkZxq#V)(C6bz-SVlsrWLzfDm1 zaqRrp2?|`mIHf_bHN9dBP~;9um-9-AX3K4N7KB2I$a@Y}1#0bky5Xm=^gs`oR+1%V zlO`8J1E6IFNn&MYI?;v6=G@?IOQcom4PSTXx==~U4K7svDdGAV`Lt2QluAzduc_N^ zaYs!4GBweTE@Sj)f{#@qvTGzi1f*3e3)`Q!B2q41&DZu^kB8Nw_7rMOIx*fTH-xcUjk~M zKIj6r*zi|quwf#=bJ7%V*MVx)1lt5YcnZoj>eKIZg?jL45m@^;FLnnZ)sqHU_A0aq z+3rRP`S(tWAmv7KX%9wl7tM8sAz^(Fd-;j=4y6Uw0o}4{Q&YQRFvl3((DOE;Bc+SJ z)T4JMpH@i6^xCw9qBW#uMD28nJ`c_~zje2ti;7Ft6f}NyPh7#ni%_iNK`r_zk>crB8K>Sv>T~{x03S)l2uLvz;CO9GtI}FQ9eLa-FAW>5 zxHERnWLG=xGktED+S#0~pOcyrt&b=(l4 z#*OI#!!+nc+^&i}6%wj_-}NQHY}K~|3m+3||1!sXEZE}Iyrdsr z?&-E%VA@$WP)&mJL4IPGz={g~Oj3K{o8>UU?KiVhOVNykJrORP3Y=)g@y?9a!bozM zE~&BnV@f&et6H&3GFcqGO@{K;!mdk@bx4(=Kj4P>IX;y*q)#B=H5BLV0F7Wn-`Vc{ z@OsB(^=ecfXK-2kHAx7ghIQZYc2$BJido)v8op$~!Z5ZQ1rJ-|qwn6JnrjV%w)6}0 z>H{TNb!Kh=2?V9;ERwh?VS#*DgV~d{#yiT4k-oUkiSLoIsATsu6ygUvSOA32ncsbr zYEmO%sAF5W!uTdD9L1Cvt4~=CvR3th9$nyQ&|}HjTNV{30J@*023s=MY2$`TR@}L zaFZg_BPT0l3As9Mpb|^Fd&uxMdl2zliFptJ5n7;1;W;7d#(78;lSP#(RwzMMkOy-x zv|m?vFQH5oK_xlhWQuDU(*}2}qpv{7uAC)RLgb;I0+Dw~)F9lTsCqH1zE^L$bVi{| zOzrt#qX&gCuuTnD9sFyDDTTg?yf$|G z_{gy|u56I9K(J0k+y-{tRTM5aUc+2|u^97$z(yk#m{E=<9cH3SJSjhSBEKL!VT*wFB3xw6!J-*# zS8R>#GQXK8ywO0AO9>cqRa+I7zW1xxtRUc8b7#TOH}{5 z&_P#*Uuy5}QOx~avgqsO>i`_3;V71vKqlbkhScDwEWnlSOFoB|f!cfN29J>Im0Jv0 zkJL*q3o5H3w{HxqyuBbHuYUlbxgIf;OK$C%H4X8X#L1JHohlN`zscLHF!<@=s`@+P zr^-lv{SOv!+2GwA=&k$bwn0Hu#?-~s6DrTO@jg|cX%Q1uk~etgr6|nKtw5UJV%N~v z56TI0xLor_KSx>ayKP(U;N3ZIv-^5f>8;0Ef0yiQn5CQY!+wVL2gkdQ9zf#1#~jeB zhDRpa+iPq(y;0;(>nJENU3Kh|ZTBE-9(yzOggXd4X$ND34-;sW@G&I6-%8_k0=23q z8gQ4)rzYo<*Pjvv!GK@}Zx8``3LI)~o(r=@%S2Iu1<=sQTH!EZB+*MuKI|T0;RzuS z7F}oV^VT)GvOJm%89mYqnn?%mAOc8qNu(`(Sqlu&&<9M~RFc!8kL@8LG!cWrVT>p* zfdZj;7s?T(ST=AhE0!^y1vbo;X18aQ85iZ#)bOAAkyEw7?6IVS6qCnO=y+UnChegnWRKUyTGGZ-6&;$?&5@SB_)y zLt;Boq!cTH;1hOID8p{Qu+?JR<8lJ>5!#QS8_0T)%8C;O(G$|V9 zPud_0668+AA_c6KGvdxLVG$(TS|mwfLTz5bWF(>Vk|Hw5edEzm4n zaAp%HC|8WI7y1|~m<{e?tt7|>)Qgo4WMt`(0lvKw7UoMi#A@1uja`Z-g z-hfAiQ+W%!Az^2qESJ;ykf&++wj{>>vEgOYD2#OCYbQ(FPiVr8nwx0Op4j>-yM9ST z8N7|l<5Gsl^w^=B5GR5|@%+Ax)vH=8xEAM>>I$}Ohb+aOjO~I?FJwO;Wlx%cytr27 zqF&Y2nM=D#NWmb*xvv3zNH@_yth=&5)KHT+{+j%QI;exWo?C11%_Z@nhYWC~Tt-gX z9i?R8OuW>r{K#5y058X6f=^SLw`hc2!&&T!P3=z}HVMtFPuHD^L^tW%gljI3K-ys#p-gH@1OR zCq0#|EVDgRaXCgTZdvw@XkFu&eQ~z){F~W(23v~d?|;=fVy5#J7$8(NN!?1u*zIRMN^_s9tEfI1T?O$@Mp!m=6qD$a1kBFimK(4{t(_-Ha7os?tw7>yes1F3IC`8)aJ8;tp`@Sz zR_q>O`HoH@mKT#my!SW}uS~kJ&{{GY&Mjr8eG>vN)zz__lZG%K5X-=uA)yD78TY70 zv-*41WP(((@9%GBGZB>CbyOL_wp~Lp{xFlE&>*AvMEJftA+=#)kKV@+<(X)pgra1U z<(Ul;(djw@do~5x9-67}8C#cTkiyRsk$3*45Wy;^U1~_difgJfRf?k>7Apd0H62tj z3Lal_b!UlFkCwv8dL6gY#OJQ;ARJ|f=x$1hNk5O^k}8bWo9&G`r2ib>F$MB?vsy;$ z(<|$;I3Bl=907B1Gz;cRbjDU?(7zbgCQ6!oBSTrVoieA9K5;uPs41`0@U!&n=fn&y zn^IO-75kLR^ER%MCv*-Da{FAM4zZqE6)YYV9RdT;L@J9HWrZ2T5U3xn(7+6?HsXz; z=&?oA11+}AR4OUQqC*>wSTNI)G3pJPu_rIv=AORERmeCbt*L~Uz>%!*&In&mqtYU; zvN`v=kF;W(O(l`{NP_43KBsXFUPL~~CNe2Rq{Cl{_DjcAfmY~PpD|iI;qs6l{(N4@ z_9>^|U@sbcK!_W|J+mXdJN_ZuMZ4+_!2?+{Lh$!Xz>8W2pyFxF(9V?%t^-O3|FYn_ zDI4iscA2w!vsZIYgcLdfuw8lLZF0aj{VubfIwOfUEKh=2yIY2VQ5JG@gn5XU7yd+% z1IFfGg?f616kJ{AIT@?zaQh}x>l3a9BcM)z*OF_IsJMn@WM?5wV@)ye=&^DzJD^6k+M`Y$v%R+E%z|=vtzb5(`7G%SNf> z#Ac78`|z!CVI`3d=9EwAm?IdK3$fZa(w?`$kF<{}MctfYwXzcGsO(F_wl^%YS*NJ< zz99T(HbSn=@w`qu_COb!Io*APSvx6t_%P61_ofo+Jj9ni2GS zRS7qJsu@!Zl!X#Qa1U3seS_|Q`H(Ui2pT6WdnR1Ob_T1SIyvOSPU>c38iKRofU>* z!$RG>=w%H-2w=DX772PG9K9Pxf@F91hNviqvm(Q|g@x56!W}jssb1lti(!^DU=QW1 z-(5mP|Jo1YWBS2w|4l!*clZDIz5G|0?(Z_JC9wn>GO^aTVnwM|ao0KT>1vu?B?OR4 zC>k*@V#0Jcz!DUsfWoL>*P5SUy4QLY|2|9?P^$ccCE)WhT2+iTU0hS~lqy9>KZ+_< z-&T?;`Hp7=W!mZ9;%bIbl*(GBR%Vo~6Wt9Fv+R+IwzbEK1GMQke}?HEk43!&Ow4SS z-7^tm-^f2rrJgB>TsrP{ixuPAR7Yt#ZmOnAg)WvnR!ympccbW$FDqs{#I%BL?v9n! zI!%t4FkLhkw^}Xf%zAZeY4c{#)3wh|hy%n)RrL$fA7Q$Ldd>|O9{tW$7za!uYGR}( z1cleu@IhM3zG=JhOWaQfj4Q?ggD||O!X0~t-Hb!2{YHAVCZDVIN;jt#wF2ff|)QSPNDR zatRfYB}f4DHcOIC;@;Gb__ix;ODu9EUlIi9EwX%){d8EA-2FhMMTS8vUagkm+lTU5 z^zgS&LHK+p#y-inTa~(V0s9M@ZAqS-EwR`WV~lNyO|){hNhC{dvduwx_BOhe%u^!9eVh;DdtvIuzuHLs%lGp8wWb>B zu)Q?xbu0h)BRj!m?GAwHGEiAh`chwCxh$G%y*6{;VC!|@wjjRZ^6IrjSWzG{qq8UD zL>z=IltAMo6mdbt!E8$WnSnTVLEah_6L>BQZB2waPQwxvpyA}`#9nD2QkJCDmQG0} zTju;YgZMIph{T~+NnbF-Tw;Y$b1*{J*?^Y1dzXn15lM`ewvrDsN#qaKU0ln6^yHlg zgbP;}$iJa(K9gLFlF{o2-PG*~rAI}xs%2ANU4Es?0ifxehAY|A&dND3H;wn(KrzZ9b)-OJ zO426Vm+k?StDIu?n^U%|={B&}wYA4D5XslFgwaHzH2ctU`$EKaR4Q+EPuTNV{GW7cIx6^BB*>i$FD!~D)g8CO+fDG|yi6853l*cvTEr=9pTcO98^u*z2oiMA z(`rwOBm7##Ez6>J=Aanb?+(8vs?MMnJ<`RyC&b5V$AItlI_^qHx}0d4Mhj=rC(+O> zUxLqyT)^Z2u9Y%~5w0GQupYAWp(c}?rVf$Nz`I09o?Z=&COP>;D4?kZ-o#;K@0iqZ zxm+Ea?PO_^1u|Li$bWR3fWOR9?)l`qvT#aeGpnrWloQIjxEdl0Fk}V<_vG>89k9&y zX)n@WtH1JP+nNbmzbrJ3C7nUpL>Ob5Lo*_t=HU)GDGALM;%U}J$bM0Ftuh$nN-IAt z{$lLxln8zD9#(U2@V>&t+zJ(b)hF#r^(LYuBO2kStY4zxyfO1LzF*tM?+toAxN24- zFx7x#vF)~!nS03B`|NHQr&Dj$`2NQdXY;Q6p1DqGM`A56jC}Z2E1f>p0H;+NpaMp_ zP6lz)>1_9G`n{=RO;%@nJFyFj!ca+zp&TbH@9vTMFBiQl)tWA2mQszP)als_XK7{} z>5t9BUJ8sf>>gtk^de|5x*EdL>*M3%U^tse#H7%Q3v*C-d?SUVh^CEDRC`KU-lphr zYpCwW!E}YIcXezfB?qn#4u34oruOz?4DFLfn>wUh=)C+z`nr@3uD_)bauT04NZzvyorkI`rN4S zFU6cSNIa0n-kBNyyA|o8<=i2p7^}}9!FgB$Un_^-FIYW<*Az~uWdB; z)v>SJO&(FhlvZBv#|Ol!oGZRK*=?N(z8cvkU9$XQC%!W7*|pd7MLfQ>>1gVcr+TXf zyl}K;;Hv%$l^6x=m?Row(a-kTRU_ydGST-7ns1smJapw}mtm`yF^_ZRzpBuI9BNTl-kGU* zO#gh3ZS8JL4dm^F!+p7^wpcsO@f4!fWw9of47^5U*WIsKKUm+chntNOPOC5<-*{;XZKv7qR|`mMdYiLF5_MgFO)9HhUTI*$ z;&|q}ruR4v<1P?@92JhdSl;leV+2Q&s2UmFRD-Q-RSnyN6*);qff^YIu3ac zcE^G=;0xuKhhBxKX&k#VwKJlH?KYTB{2`|qNsxMCGq%V(NzPy~t zLtp&t2ZOM^F@0PR9u4(NJ;@Y#NqkfgpeqQcfCJZ60K@^NJ?8N?@nMDHSn1o?%4U}3{xX6;~MZ%A~5 zaJVP(rZS_TcBrgth#(DEUV{F}B}n-9_D%T;{uOrY)ql~x;ir8U7cA_J`Nwr02Xp)P z7NL@%-&FLv%^2hQ-%wCZiz9`~e~4|el?d8>|44E@j;(xJHT5IjPPE_S*Cjv4$nysZ zYI%@#$n6Qn@OJR|!_fa*w&mYEG3bBgi5YkihnHRrS9Mz0rp%Z5xDoN`U!$PtJ%8Ai z|9+kKmrH)ea1lmqb1Xp?FN4R4u`T~XLCwr2sJ8bj%1VoHuBQCyi7~r$-ds&LcU#vq zwn%gMLu|vdWKNPzUK;dDn(w)TR*H*dD+w9S9*R9Uj05l&vCVy3JXtlGCbgoYMIZc< z6H}yJF^=gtYw32Tg`HeNFxRJ`QjFLJ6A9lA&#W3ohg4QcX{65a<&9xbQ2s9@9V6$? z$+vAbH_|i(pR-kM|DNRfLu@l`TlL2!f6NC7XkAJB+a-UydF#JWP+x7^=c~SUoFC7D zaxd{7S7TwXD<;SQsH^!MT+j@r3`T*4yydTciw7- z;CW2uYG{bs>3@$Iaym7;aMuj<<~5kI3{3M-wMf69nsKxu5i5{VnmU>p@B1=8=pK{e zIp#Q}%$#4upW`egAgiJ8vgJM857InrqgNs?vZj6{w+g> zL8ZQPnE`R8>4OPzMgL!7n|ZIV7vB~l_%BaZ(jQ!&ZeUPQaXZ`ln`bVj*wSV{oiGP; zKbf{Y?(l5Ti^H;H#p z8$_o5OF!rEGn_d+;f^P!q-QZ`fDG%um2~Wll@{AjZkf~IY+TAcw($200%HtbjQx>v zYKK4gN`Dw*%59xNE1|NzJgRv)=v~;Rzn0e7hK%XhL(WIeV^LZ~d425WKlO8!)s+&Z z&O49MvyQh~Ry>C|8rLeh@191T6^LTu@G`5Prs51g>Hlv_`Zu~;Y1K~qpCw(;`o`(& zi<7lEP|4YG3=j6@(prMd6@2I2!U14$XIGQ|M5i>aut_!+WJuAqm&R`UD;3>PpX)6$ zuj;AS@MnrbS`@~MH{9QcLZ*XVS8|@{&Rxb=l<>9%Hm5^v0+AA_K z+?h}f>~nvcbCcxAR%fd!#;Hr4H;YcXIlW9f$pE6ua^C7t(lZ}_Eqh-=v5FnSyPQPNVni<9q%?kv%&?|XRtbt1LfH%Cd0b_}9Ob-!31Jb9#1CCut~ zyW=I`d^dQ!%g=g*tm*t9P~n!p)%&{`{oFq(>9g&-nIpU=&16$~`=4+lW&C!H0!?$- zwm1JQY2VdAJoT75Rc4->th+p&OS+t97g;_jD2k7aXkHtFOidWa&Yh zoEXKOFrx1ff2PV%k$HLy6wi{+ex?yq)U3g2TZkHRMQrIz+M2!iuJ<@dfphRPj3`z= z@bRUc#=?!LuqS4-9AeDYE7Of%b6!lQYJ5UBJN)=h`}w@^9j>}N8t~j8)AQ}rJq$F> zf7ImbPh_J>ItZ&gM;Z;X&kqE$d7E4#yj&Rx0b#R`ha7;W7s6ZonenA8(AvNCV=-dpq_E+ZP@Ou>-I}5AE8tpQDcT` zDTnjytUmJK2ltMB?q;&2?oUyY#ks}g=}Lxg-09V$G?g8`***p_HEY|@riS(`l!%5^ zjJYXv4Q%?3#`&g{mDfFS}&PdgkFf{p(+vaiUs4wdf6_q)>dLWm1t99l(xGuL%CI!2(O;j`DjGJMxfC zLXldfV_)X`fpgwOsoL+KbF;Sq+0~g_8-mMsU-9c4C%AwNZZO~fXjHuy*LrW;HRj{u z>0Up_j@teJ<66xAAWsV>hF0J;kMM1@(4*ZSiR;3HqF?=%JftMI560?bOL@nY?F^hJ z)E^wr4{IpJ(MTw%-4nHlBLy72qP_bfS6uHAub9QLe##w$pRcu>a#GyQhZUv<#pSZb z2`h8+1U9^1Zv23zefxC#S)BGLa+6n!5Dnlv8AA7&QkK)y)JaTuJk)Dbq2ibnlzpqQ z1tbkmQ2zQJ6`RaLHj0Q;YadY%~Rsn72DnhB#w1hHGNm`Mq4@T zMjz!)65(tau4VRHwN&&*C_cE*QM>zf_Tf5q?Zr1s94_x;TLRx?Z>|uu&+O-xuTrY! z+=mO_>p1Uj?H>L>Ho7d1Vivx{`AROJYgkGB0b;6HwkCB?muYvA3^RQjmoPz~B@lTn zX;PNMwu^LUMJx0GI)nIuY!qOD>FLjCW>o6X?nrQ2FPZ^tQ99m3l<~>^d`~(-^VKda zPs~H{&?JS|Vq|VX4e}{eCiKgKBNJGL$H`Bv`}5$c#lRa)s(XNa`QY6}T;uBg)bofW zPMtjoC*gias2l5X7l6Z$11k3h)OTpHLTKO!hYWl7-LrWUM<=NP77&s}X29Ho=m+>t zrj;6dK#~rjD%6cSDViXc)^xxT4u$1bKXF_FXengokF$g{@$>sTDOb0r`TqRLWNA(8Yj%ed$}dQOlR_MP42b@9HF1@!** z%Ga;yB;MC6w$I#9gJ!`~_=XDIEGuO5wY7sq$|T_)oN~?jZI8ptjHQP+6+_P68e_jJ zYdNgYp%$8qd#)z?J~FQZYD0n?IILh_

    =xb~|T2g=bESQ?7eYA-bL9bV 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

    -m*E_u&jRwH#Wmuys@8t;$?6IfuyCt&gy zz)woPFx3K#f+OCwifw@+5=+Wf8|#o=3lm{-<$|F$8qM_fF$b8nQqeed2R>?Zjh1^U zq>Z8HZ9$+`0&52~9_T$njMi@+zF|VVKm0^dzG#V>S|0!Xb3W7G-tawY5~xV$wTaN0 zPv`Gd>h+eta+Ue;W|V(&l_-%dF`CnNU61qf{>oJ){fI%u)9~_9Znn=@riG<>n($cIg#WE%9XR zk`nKeSM3up?W5uAQB(yxw)H`~+eG1bU$%PHPk=uzebUVRSONHjGW~k0k7LUEpO5Jn zj_Fvc0;b{v=MA zhF(<+yhWf7Ix!AZcBNA>_C=TyhqY6&h|{RGlYz$aVlt^y#D@)kv3;}~z>Xh!J~l+n z=Pr0Lyt+J`o7p57k0NC8j=vVabnHn|#Xu=qzjQpB%4WBK%P7^(NOfj+wK$~3cWmS& z#jvG)(6?&T@pT2w_-lc2h2I4RA*t&ncunQwPf9NbeR_x&Ux?^sTS z+7XTubP3bkSra7JGa~o>f7WIm8NHWqPXYItK zYt-KBGgk|ZR<9e?D;u3Mz(kLG-Z|7Nj@IJ)E)mKM=C#!diw!xATZS_>GHgkMbOEAw zH4GxP6kr+RfLZ9)oYeiIm)MFUH#BnAZpCH=b9#kHMGP-sib;!B`5tVux>Umju9&Hd z-XUx_t*gpXOpsU{Fza=AzZP+#i>@_0GT!q>g!c018kO5>($?CX#K7v*{6pqC0fGR> z;5@tkbluNlI#^(>rWU_|U~*l83I!gwO|o_pH4kH0*{!5FtsHYz>-%HY|80EY{{4Cg zc=Ojf>8;r&Ri=i7+ShHGX^H6d?aVbC$t^C?4U(8{NAxjrCvrfNhTzK<)7lo)oE### zjitKDlD(~BK@$YU9xdPIrQ4auTH}A{owH;V%cCs73MX1R$#BxfAEjdhAx2VlSuE#@_GP7q{l`Hl^=sAeWQ)2(Q z#aH`{eWva`r>bEW$piN)CVBG%uk3@@E(bmn*+P{}owjq#bW}kb2NGL&lTHqYfFs0l zgL_oyE#(&k@|Go6doZJ98eOIWTqkE)fz1LS4{>0g@Q5peYGqsj+d zuBOGRvtwe}?lBH<$pb9|2rO?zYo=Cu+0j%bopcyo!NYeVC3D1wdSGovgxEY$p#5*W z5+-B>xBTBDIKlOXo|0ZJi%YbGt{|y1vu{*xuG#7hlakG_pS0Q@jAIlqFK%_7nC1-QhG}?{XdeHd!-u!<(VB|APTdsD$G;{ZZ%HNWA*sYF_8R zgiO9(-%6(u_v0JR%>k=T_vqM8(EC3(NFmeTyb=^Fw#|QdC1_Y4aY6)7N};kK1-3i| zA_m>B(hGMw6Vhkac7PxMJoys*G|BF(|6XD9R=8y4;DYo~AKT9VLBwDk3ooi?5GM8b zMdV4-y0aUnIl>l~sKP^Om850EZY^T$vCNoa9um%&Dk1u6KMhgwnb?yv{jgYpV_yt}|Dx?98IGk%d zXx*mYLN^$TY8SrQFn4`0E`Hsfi;D9+x+(tE0vw;Gc_5inr(D*1DIxTj!CHf_jf4>y z!7Vua%Yb&BQ7xTwSI{j59n-=9q+`}X}`B8GpeP5i@c zg~)0X*XK=GH-1Wx&-4G zSr$D~yu0W-JWr;LF%oktErP@;UQpKtW9L_F=p3ULYAQDl7crmw_;=XGe{dlGK18TxiJ+Fh% z4GpDLP8Hn1hYPSthE1!SNxw6E&?yRz^}1|y)j%jzEotaVesBLs^asO;;3EN&4o(us z@l|9DwX9CMjA@GZds_|$t713alRZ0(@LWuxp>B{9mJ8(!R4c*-Q~GY_>7!OTm}xBr z0mmN5%sDg?!;+A-*^>OJ^HZG{sl`XQ7&HdW^6ZG#oR^h74ewG4QI1dX3)5mh zGn8eos14?R=ndXG2Y`&aVE>=-gwoCUH}*>;0&#F6spdXNuHk^0-6M9X8*KVE=TQ1! zOy4@45dcw(PlK|J=Oezw9DImYlDb6?-;{x|w7@*$FtOJY@W?C}WWyYHh}Aa(h+Cyc zSQ7TI?R7_ZS~ZF5ob7NOQE9YIUEmCNL4-5JLy-Lg5{0>4ygnL5;_4w1Ps3Ukn?pQs zN=@|dHLXPT8XM6{8CA&#bZj0Z1L;c|Vyxyx1)9SO8H9N_04|$^DAn6-9@A;~&3ZQ@>(4@+c;e4WW9UXJ!Z+8dnv5|4VAw3RkvjX#9Ba1~-9M$g$ zD_&rKBDOaB$qeTo+NvH5$4%YZa4kCu9=^ z8;czkC!&!ymo0)poufd0tri5joeHfK$xD+CEM|dwakPl*w3)^QMm8kCY)394IPb30JEo{L03fWe7I&a0tV72m0-gG5g-^RKD!QP#=6>kyeZHDp6KTcn#3U~Wl>l$ z!+9pNJ4PbLA)%7|ud!x2!wYX7{V2VX+Ngb{UVt$x0ScEo+&T)Rm}I%&#t8&7w5Al^ z3c0eqk~b%emscr%a&Q~%-DPDEn;5%54s($7xVN`_m$ay>)TkyMry8F4!gc?*dWbsV zww~bB7?Bc>f8+RRvHPD@6c(}?deuHZhgjVijy zj$gEIVu$`P;WrGHJ0vlMPW{=$A&PmLI}RzQ+N~OxXq3aFOCpbB$_S>z>->J7Ab{Y0tpH`;RD>*!<1G zFX@>M;wata9J4Mg8w+TISbSz>F2CkB!;hGj&*v`q!G&+mYfOF-3|pYNRTEQ961g-# zm&F9tuv$xSdgGYc!+G2;RF5o#@K(MX?1Xi#!%(T^#V$9mk9_Xv@LQT(vZoD;ZE^~<_77-WTq0Q@1!-KKzr^sp(bzgVN(XvY2DEFF%ph zCUOtNAIWeD#EnSc{Y3_XIYX-R_u})UfO^FJc`QOR{k`qw2;$-9D^Ac4L` zTVzLrB$#XtH>4yQY^3gdcq(3i;bTu8VFh$Bya(G%EhWfE(T-&ic(DXQ5hk^z2+_3# zBD;-V8cMtphFpS!LIQ#*EP|6PLZ7(!uy%*$AB4UIL*59J9Px%hWJ2x#j^Lt}!eS6N zVR01U3BuutOOzpo;i;+N>CNGpOX1l!;kgtM`N9$ZQEf_%C~uCaSc=FB2&F9P3*A4=9rVEn6sN0iy?w@;n*v~SbF4nUeois zIreTT_TeTLg%YVzh_vU8!(tD`E(`aFi`z29Cb*5GijM+B;z^9+$-Lu_S>q{M;%S!S zk+Xd$Wdegp0u!YZGj7zlR{}DELoW^G`a6Q#x5dQZ!{Yb;D2zz^$g&+Nx*R6H{85@R zQC1{To;@b)$H%9`fi_M$w~3mRN!lVwx<*O*-bsdONyaTnU2nrp(*oQ4qnd7# ztc{Xwj{L2>lkHoQ9m8XIM9fi2V_%D;xQmc@Eg9Gtq=;YGcBBya+@|1R5E_ z)8Ophsq*p3!AYt6OL!5KX|%hk34c|a($a2{H*T;}m(#Lu({hJnS+1UWcER(!(@QC% zqtenVmeZ>!{o;S6;~yr~wR~7`=57r4|Ev_yGW?-oIirg*vqvPe&nR=i`@cnS*UgDR zr0P>55Kp75zF!$j%RW3;UNf&^0%o$_W6wLSQbPXwdH(;uq%G{9x50n13_moK{NLUN zNLH|6z5V*X@LsekO@HIP{+>N(5}`k9Zfth=OV)ZZ^3UwS;pts3iXoIJ=qVnj`B-x; zQqsnVzQHL{);5wV;IQF%!GAEBD`$Jn_1EmdSE&Fwd-%EJUiH6b5C6}0WV!MO+i2ut z#g||=hM45NE1VKTrgv5fpG+4dze=Dxq-H>ZG4bTjUddJnjRzZz3S9^VRTx6p8mz*S zyfh`p?sJtB!7xCPCGmJc(JB%#_9H*apnIS|bqj;tS{RQIiT9$G7>X0xxI1LBJNo-P zUpzaJWZ0y$mu%|6UL>Pzxi^t&o&3rs1TU+MB@I$kzn@OrQD&3jnoVh&^>QYhHA6#E zUp+NHfblRlkY({jPS~}#U4GQxvj-ksRX@F39RlHa8|6f~%Iki(5O(kNX6KZ$2 zf4@eKB2aQZC3ZeYwrRDIYOSpL5tv|%Bi{yG7k{w~`TB4r)+Sgf=@{yA?*-{`hrx_aH9yWq~3Co9RA^i7v5K%8{Q^KSO3&RM3~6szu27rj;G zANqOH?m~zpLmA@0*~dD5_;XL7nA|YEITbxaFWo9GjI-CS&*3*xuOGc#CVfh?Kfj?$ ziY6~pr27;q^HSrdTz;FW%kb=Hb-`QdlsmV|Q{^-Zg$?}<-ubh%K}j#2l*Dl?-6Eo3 zsLQY?;pgt>Vvjl;B-P)%_gXdV6_bNQjuOE!a^SrmA8-HOO>kia?W+g z?0o7>yua+6l=wqkOZ4T>*{2-U(g2c&fD-RTE5$oVDcK!!pE<5|vIkwC@UTUM-?!!= z#8pbMM06QvR80t~23ptm{#Q_DCK6nc!&Hc@bua@ew&)ZeK$VZK!A_D`{>2vLmb;^| zsTm3&ItFDK_Tz$nP)DpdYs>UMjt{V=#-XYbh-Hnjrpvvgu;CDbaLQ^y>3s)ZN_r*m zZE&)yBo1A0>^$Yw4~~2u9457)#UQ*;tK(IXDQ05>Q5WntBAZ4bLH^{#kneiFfIKCX z@HkxPCA1(?@oHB`&_|a$Hk)BrBf&8FF+|?wynC0CmNZYB?lf`r&oECqQ=*nR#`E+! zf|N0ch{jBCl3dw}>J!%gXWj?CmOd&>Bdzf;<@Yol5knQ-`B-=wbj)}sL zbVF2$BCCDt5?OsgW4d6m>-$?TEk<>;p%@j>$1+HY{P!{j{aVg%`Wz4mHg$6 zNbvtm4^;Fq2o+acu-dYP{VX9GY zr4Z|-P%1UdK%rxl^a`|_2J0?YC|(wdS+gWD-ors-w4AS$zM`MJ>ytq6EkriGl>-qd zn@Y&)U4~UNqGdX~ZnBAMb(OEyOVhPb#x-0aA!E2#;!-3rp(%9@E*4h8wi4Q2?7T}5 zo-*Wke)BtabYi4dim=k|naavc`gKF8TBW_g=*mLt^?%2EA!V%=c!Wb|*Ppwd&hbc7 zo4%=PY#vgpa!nas+ZSET9rCJjFHu=PHvZMRM2IU{CpK~Fx|p$XeBhJ&wqaD=!u!Qsu&@*Oj?Zx`&&ANk{?Hli6uJ$`xanh4uZ=Jm+a_zf>F50m@1<&0 zGk-H6?4?IsVeLJM6UYC$Ew#y=RJFFQ~msni~XE*U4(fd-DoRzu9 z{Z3@q?4d3-fGd(O<95tW-7UDcV*g27YSXw=OuaOZi-<^0C{aoM54Sg4FC;t%>QjH# z=Zc-I%DgL?3{h{$&k@{unt@5){9=TOwj)bcC%wu4OGB}Vn>L}6;NaOIN31}{(P5kq zw3X|pn`rUzVT}*qn#%IYMD3gL>hFc-iN>k`rL`IP(&@G)!pAw^K8LCK9r*dQY zeEj_r(WE1ijE5h{%+d+L+XsIN5>6tc6r!S`77r073#VRF?^9S z5XboSG)`L~Gl)&WiTSX4v#G2~+B^YC>O3ki;NW?6D_0N_AWl=9jI-Tvx+)4MAyC!E z?0$r01hjWef9sM3C?y^Y!qi5Yhv=Kkhgcah=JBm|HYUufis9{qGYGfE8o@-r1v-6O z^FkUK1+=X5JC}pa!?T+o1*G5vSL}X-rcFF0;smndVQ`ZFHIRUX8M(rbhUS?-T6c-O z32jQZLXc7mz|ZdW0Fu<1Y!~N`84vVC&{iH=dU09UZ6jkoibN^l?VDIB2C~9}D8v%u zZk+7?9w_b!OByV}g7%2X`xzCR?cruYSXR zRbEdiNS!0SLa2-3i(b1WFPa1NDUjEb>h{?pfSXCyWzc2pJ6!hWU^lX)%Skp6+@w$X zv#`u}sUL|Z5SjjyKa-?piQd&MbC7vV3yhA=Ni)uTtFdNhGP+GQJSgXmD1>T)J-JZdxNq@HmoFl6 zGpbrP$F7I%(B9lnLJg)I++>!kI;y&D@R{0( zWY|8=I@)5Q?ql+LoO=kkN?{c-tJiY);It2If%3buXn0{j)TY5q zZCTU{X&B|)3nSGYW^g_{h*4zWhw34~<6Wex8DJ6rWibsO(#M3yxg~AdELS}gCjOM; zqZGo@3rr2_3&RrG#5ZatKE&Pi&f!y!DOP-%rg;&e7lljqIZQsu`9HD5?5-?|=F4M= zF*u|QEf>+wQ3W*XUR@lgrO~R6ijz;YLD$|pvELaK>9LiDWO%CM!c+?x$cKj{>FVO+ zqL>)z*@u;lFB3M03Rxb7E2;TFbXIGyP;ye~oZSJ|g0TV_@gt>Y54~1Q-FbkgSDg3K zWpeXa5wdBgVlH`<(&hf_(O*rwWB$}3l48D;;W1Mdfz)wF7XE_aFE%S9r+xbe<(;c4G6Y{k}bH9XH;px2HpGOo7>2}p#s;`X;O zeVQ^gz3bp49NN8Hyb&%3M~*yTzEF`v5pdRg{PB}Dn<5ks9G%}+fQ`zoB)>zp1 z{_(DlfBGxt`*NA2hb9t-rTl`3wPLd-Os&F_d~|6*^tv`q&k;A`0vF#-8lCfe@X4Kh z^n}kswfoN#+aJe9fnoX$I9s3WWK0&P>&Sdc-g3XPdS@Jn*i33#@Zxq9%UJYd+WFk| zKKHrKJClvGw~bR>l`f~oQTt?aEk&60?r!+TyONWwQ*601rPHCFB!whxah$470rjE5 z`|XSYId`Bs^Qbw7#~tqoi8x*a{9gI><>o(0onJ_Su$KtbKo3R6$EWK!fjjvdevfau zK_I-~t9JJdf0PRO9gFyn4a<-wp7QJSPYh&K1?$Az7GI8F7VqWV-YEb@C*5Xf1X71=5%`S_O>9HB#qM8BHa*2=onSAbMH3Lto z0LeU4S+i|oMUk$E&4pf$&W^5vNcRheXB|VT6o&XUaJ)S^$?Q2lX!^$y!1I5;T#mx)4sf`XBk{ zxg=kCduj1%gMXa%;0|JqkRH2*b2cNXyN6@8GlZ?V{^-~_&|kDXT31u-Ve0~0qCOF4 znOBB8`wNPwBWC;=+FW@gnyeZ=C|hWzMDSTgqd&4;d71QFFu{eT4XI?!x4G^E1nQ|1 z;AIrhljfZ_vb6^GV<t}K>yS_RpsTMh-7C29hOjsM-A4Fb2 z+@=|@8RwLR$uE7#w6n{!>CB{t16uG`zah~Oup`C048RT0GAa#Hj8`t03pxbD#RWp1 zG{dECf@D&IjF+@*mqMJpLIQUc6)j!SVNTSjOw&cIKNkYfEl>yf9)s-&Q#M1<^Pok5 zTS}Z5Op*Kp&bw8Fi%^mP5IIDiCFlSZAhIQug=xWU(LmF+guX*SGP+R2Ga(EMk)oSG zY>N*Ve0&Lf${s@yD6v646L2U1C<-g9&OB3OleRBk$3?n~7q2JBpA8dZVa-(0oMZ|a=|IXq=@-wR05Mo((7+MH$ zegn86(D~R}NI!U~m-V^W!W_4?fh|k!j9OAfqtMmDL?vBYWX#v33$A4ma}7|}H&WLg z0;oto5?CZD+o^K|sCOjl{y>^mbe(ZrUUSArWAdRA*#cYWXjGeMQYbj+TGV-3a$Y(i zDU5)OO*Gb4gpm^Yt!64widYUH!S*w07o5~2PgH=FX2NH0tK|GcI_-)#%>%}iWkIAH zNUbo(C0z#W8m4z%MXRGFHD5WwE)pnTqM`}VqOe(Gp`vyO0GM_tXsBo#hXDH&)yOue z?20NH#x!_|YcG;+UCP2$=2+;>cU~9!y)CiU3`rK+-n z+6bWP!^N_d++Yo)q=8vS%zrO~hoNAoKoa5{4Wm1o3Y7^@H}6#*|4~!XI3@Y_GT<Vo-DLq7lv-sFQs9N1JlJ)Ig2WKRNVeUcGD1&&TrXJ$~Y%GISg z06CG8i#9x~gz#jG5|5M1quc z+8o!;2oS}KgDM7g_OL3lAgb^Izlb27Hzvubt$qS|+`-6uv?<{}U5R^wEH+rBOC(i|U?TM+#`vgOgZ&+?Bl5n8r8vry+YV2exI9wV@Q9Q^AU!T*$lU9c4k2a2GO$`S)n+4;Oa!+H3iIwyy09b-OKJy_^o zool;0Du&cWGbmmxr@LulmHE6^jT(|qOCA@5FaWpg;4Nizm|s@lyF#Zjh+BvyUpRa* z%Wq#w7Q@Br^pMT_CdTz>`15rei~7@7>8qV?XziRo6H+<5a2$b9WeSIkuFf{fyLUie z1{hD)6$jNDBY@WL2r?R94`1>kL=^{R>YV;ABb9LnZ&Go5^ zv&BwSRbzq0yYkaf zbDFUy4LIv6RD7vTQZyvpNc#I#c2gD!dUys48(^&7PhXwr5QD1!Y$j>csaEVX?)PX(w*?9HzzWDSQEs zua{D7=3C<=KAB;gGk*1UgQK_uUTx9JQ{|LjxP-@-q2c43XT3DeX@<4sAH3c^qAd--U&@wPDvMsq zBM~h)Ev!6Uf=+YPjV~^)tzv_h2~pRQXwBkI0TX+VMXr_3(`6j36^gC3H15LH#K3Cq zH)iEroQ1OJ@pPddO%D-U>%gvm>gn>9#LzmS;o*CLl0+ggYs0552;>18Wc>vK8jn{odeICQi6`0Zv5ljy3nHg}S;zwXFOuh&E`3Ay+9>;r=r&h~qz4fRped*{3SIvY&ib9orlMY4b;iGQYe~!O>{p7bx;(bprpuoj zaE=1kwJld`%e6tz!eETCs5nXOEcGrOo4noMK0wgs{I`E{D0xc0hB89 zh(&4Qj3GY0sHZCaBioOGRj~h&pOBBLDEB>Qas=Eb0w?di=Zk{XMU+w-BOQ&nq=4X-IB+2%iY9Y`eo?=PpGpaP zj^5pa1@3E>?ITG9^kVWUB`TY?NIrpiI(E*PWa;?odCK^dhYPZaV7%~=UbKK8HbmgS zl%ENmfS%AN701cm9!=+r?I7K5*;19h#4?$`pD+Fsm^Anq*rl$nNDqNrJlul`I)L1g6U z>=2pvToy!BhxbB!2eaQhQF$I%uJqPpAK1GcswN*W0Fv^j=Nmz}nFOhGh)6Vnb8KvDP3x<$UEPJ&h1%|EY zXUrR!CBkPraaP!_a7SlaIk&SZt&qDbo0n-8vd2k>DJ8jnfttjom2p>-pB5oji3oAZ ztJiCcaN4!|$+fP=L(~1pclb2T+7@ndq+DLxc|nz8<+mvgw=&C^+( zH{BN$(yA(7u_>$GLMu4(J@0wM+{HkotM29SFDky#ifVJNIs$lAAf)_cY&3j0jO({; zc0kzLrE1a#fkXMIC9cCVtQ5#4{ktadUg!bM2ryo#2zy$5&!ilwAuaKx6T33!Oxptz*kGW>P9F6i~e*9yluyp*_*TLr3c2g>^_3&u; zP#17{-kj*&SX5(}ADTd6HsPD)sao^TBgGVyE#dp!OIAq`mRsw@Jd9#e(BjI5rnkemA(@c;YznjyvB7P zTF@;ZGXLtqD4AWLHrm*}sFW^@k77BF*DUR_HV?}2#5gZZlusKygDmoQ>stNv!wrL7 zV74uJv)jjO(MY>%C6cIz0lVISIh(Q*D|GTqw$b6hGPm@Fw?mtitnl*X{7Y|EMgii% z*Ia`cfsA^lJa}*2rflnSVfl>4nby&AuBct@LP8I$Z9J2cOTR=jj+a* z-i07glnn9mfFp^*cLJ}F#Wys`@UZW%tX;#u`EEZ6m&TVypNU6cv2H)k-X)~@+(n~O zwJ%+!1;Cq!pZ($G=kg&?c@}95dj_Qd;|`1oC%PIEcJg=TZ}tYJ5UUWq_v#yR46;rzGW?*2KQ?w}28_UP2w7^D~y!nTjoMSL__9#91l#431>5JM)xQUJ;G(?>Pa+#QT zr76YCC$v5$_rV__EL6plU1Hi$uzA;UhF&lb$8zuN8G0ZnRRzf0pfgO?Jf}he<_Wy% z=(WtmVz;sSmOr8vuPjfSy@xo;XQNK8^7%%G0Pr^(mpAOas~SH1Ol;g$cRvlQ9+-YSGT_^Kqw53n-AVH_L{}&|7MU>lf#-zbsQV zR{#h&n7E+P_!0tj(|((}7PMw%lvUO<&xrgRE>3QA!t#q3N~%_LN#RG2 zcX+v5qVt^kaQHrQK=zgt{y*N{!Y}IeTiB){LXXE=bZO_|Ap`8zSnoHb+2_@FQd+4YFO4(qOczX z$rHyLI=#9eV@k-3fJT<-5@#&4Y2-#8)&Lv7%C3*=Qm-MWOu0|!$n&C$&j~zw@oe-7 z9Har~dyH1&SHe+A#ty3FowXYCQ6XjblN(C<(tr>#oQzhZvwTv$X4QFO)lC5 zqgB1dQ@-c28T%%#7~H@x%Fb`wGh-U{8qBj)%7r`$_1~m^xftpXI*Bw|yy%9{R7ztA z_d;(jsF4C@N>$v1R)Fz7+Aa0$fHwuGbj($p*-OXV9{Wt+m~ z6j)KqN8W!;2~G)l>G(sD&M7#7oxm#NeMGJR(+>hWA72^>C&&*2y!+}tYyu67Pl?Gd z`N79+CR&tINVmVu2q(7XOXNLU$wq4w)69vGLu%xO=3QyRWhQq)&wPSh>udnpoNj5; z!(~f5X-m=hAs?-@Y2yUoo(PloCi!EYBvVz1w!(^s(K%QTdykxD-$RQ%it2N0q*SkG zd`dIRoMNr{KQ2oI)MP2qckMz-4QcmWBfVGYN9E|nIIrRGlK(t4(augC7|2*^x{43j7`k!L1R@?lvR$nnh6Id7aTdpdR18?xrX2dW~T z?>i*~+I;!uVzLVQ4;u_JL%CBWC~UR$GzadIB$=gkOVn zrr0hs`QY?eq+lVc6BXaHLy(9E`k{sUy^^4@x3pNEp@1$;L&dj{r^F}3*Y)kg^4TjE zDnLvRt4eg~A3ePb-l6qB*8tE;tFte$S_ZA!tbQt_@0+8|J`CP}kHkjLUG1JSWKd`R zadb5X^D?A7S2OrQgoN-GNwhHvSN8Z1DKf3iq2w5Lo}D)TvhFHv{~L3%V)$o!<+(@E zKkt7fJH~heao}yG`}stQ1)ZMdOO-8Znyqo<5aejb{N42oRoe{Y=txnE`M{8=u^Z6+?=#U4kP=>lY1thSIe)DOtF=OeVv+kKHIn50|COnuk0C7nNlS1iSVUqh-XC4U~bBhXG7nM3iu1D^^7^z_Wao zS~5r`5~d4}Y{fB8KeX#UE9RK*%kesZgj`fQUBqN1w&>u12{IO*Rpmu_dti_pWDrYx z3m|e9#jzbJpgU;7itef;qIZlzi55%jn`ML~k5oYP(-C`UR1UKpOE8JtwLVq#4c?yH zP(H6H7HxW9Ysc5#winBT!R14+3^^p);h!soR;USZw>&7Blo64bsyK~gD{3obn8c6Z z2OB!A4n$0j6lsj&>9$-N84l^2G~WnXcX7qqOKMoFBubzBw<|$&21V^Fg_dNXk1@XP zKDH?bc+5cFFD9KI4G+^U!YzhM^}5+fsTbOm5dg#XA|h~ot^|)>P1Nl#F5}BXM#cQ_3C9=- zKCmpqE5(XLCT&Z}T(Of5ON8qu7f+MH@XETv{Or>RP9@(kSLHa!Zhb*k2vT|b-Ne}ywAHr9+lVMP-HF2s&w30~NDLOYS zzS&yB5CA<2L-JBfpk1q6aOtypDcO*2+ItZuW_2;wFpEW=uWn?1W9q?X+!*)xlGsDd z5~spgLakiUZ4+@Z33J2lX@mq#2eQXEu66w`-0BItiRKtlar43M-p*=m%}{@xR_SM^1Sew-pYUpoJL z7PO)xQ@+~k@3Wu?uQiG?j7*qrTfEih^tadg4%Nhl!tm}Fs_9J=s+wyIy=oqA`k)$# zqh^Dl&n2}L)PE)*bu^-3Z{CVi?8Es{5_y;ju+%kJwjeo89N#n%fMSjHwm0OHE*>Fj zpD>2?)6So@8IXEZOlgj$AJjz)9JwiB_85Qx>i&Uf_&2{OhIp=b#KVGs&zRe!C^%Tg zh4agD*ITDwrBqk9!UFGE(aZ#rXwho2!mGk(7>jmvrviWqfDOR2cdxYoWK*F9(nOER zv~9T&uImT}Y%ghPs^PaEm1!CnEF!sw7Z;2vz052X?|VfQ0_cj#v_<*4Shf=6NJNOW zE6*@uZ9@={4C@qjY1{2ogDFpa=~I3mJyEl;tLbEPDET8T`L?3nEC&l^n>54# z39}C86av-6@GkPt0g}H^P5)tlBn4yWF#^>j`o4rRQWnUc6~^}J$s7RfOL=+e7rhF2 z&MSo1+6zAPN~r!1uQi)x)aqx7lmt{(AE4)yiPbbGHxKD7vF?k_O6@MTANvni#z3`+M!{@7rmo3|AU+Dlhe>Y+3yNAyCOx86ul`Kiv?rSvJ?8SlfF#|KNYx%6k< zAM@@p_c5g!Vx$wL1Q3wsL zd7&^1OO?|Ql@(z@(Y{tsvZu0es6ii2)DBU0ojJOA#!GB^c}RSdChf{%7(blNHulbI zor<$5m@qoXM4}a`X(ZHssU~+tK1d#qr*ojpm1We)_+6@&#ZHQ9pM!R9!Mmc% zfmbh3Dm8qL32YMXa8X%8Z(H7mF<5d~m|zPdwaYr36_UOs3%`jOFY_HtW%ainf0DYn z&@8&GizSw0PWrX7kN3Pj%l)a`sv9O6xfN*#lA>0kd!(4bnB@u!<~;_#WbLCql@v}3 zi%<(ZzQK5!^j4S@5{S15?n(06yAHz+u@Co7^aTIOF{dVtj#03`Omh$tzCCq+_#%QW zHPjkH-t8Vf$z5nF(*_&i(u!hteMa30vpt*;H}ETU9@gXS|cDkEPRgD ze!Kl-*W?@3gW1ppsuH60&;4B3v*@F(bo}cq(geOJAKyQJpL!zyP@hpdP6k#APzSoY zqi=F}Vhs_`NlZ(vu>m2Aw*A891CAE5vWL7o_~HgKU6MN38@FiE*^v*O$hC}%T0=nI zt|&e^K;FO%WJ)Q!TCGFG?jeUcmcJ62m3A95G3h2|`Z}~hMF&QO8j5FPHVA%8oTX9R zO96jwpgHK5&2>Wqw`7GRXou2$6kGp4Wuy~V#fEA?EN5&gZ`n+NqnvLqzm(Pe55*GQMU~y>d9_P=#eeY{@ zuqdd0PT<}2_^ojLvue0o@- z2Xgn32z+{9_cO)tmp{BsxKI#H{}v{8^MOXj4ZvBcdyjB%0H>kKEMP0a-sfhEhCl=X zgLhAGVX#Vq$KghZc6G-X%1+|ALFMXx5X9hw?YxT{Whr|B9z4L~oiK?=r;_tb!7Fwt zHOwA9D8dh3_ot7EZhu>*{{5B4i@<*RSu||z>>~XS0r*(Y7vU{!^$QPa7bPPPSIw#G zr3O)OeyrLzTa}swsWjYsAJ29w$?#Z8WvwWabe%ld0uyM(FSsO{;yRgr9EU+c!aUZ@ z+~YWI)P0iC)49txeKn~QKs#dj^xQzpaKCE^p_@lst**!7_w z{KxvYZn!}q?0GroOGgalpNg9`ZD_~@j3i1IGEw=OLr4{~C7H#?DF*fdh>Wx;&O^aPWHKKCaqm1Noy`6Cknvkb`3^qE zHSmeKH-vLH@-7R5HWYwI2BEhYF0c6#P{^Nyp$S(YA#d$?5s&yw9~Q(e*)CrqStcVm zGZChp7Zn>_prmP@ze@@JKdx5pvqb zWc~U$kw5mt3{w<*Q4W$U@lEtesIJn2MKb7! zGOW#`VVN0GZ5c7F-sp*;vE)=?NDJBr|GHz0V3% zl8}X2=tgOo1>RE0%+L@^%qjw=vby1biewLy;wX->SVstJd#t2Wr zQjjsA+O`r&AH^h=912e^F-VU47$KelYm+f6oe|8DJ1N4OlxIXh8{~PEZh7m3-zB}x>PJXLd6mlyfH)c$^8zBBWbl?%l-y3@pZ%mgW4yLZeI%nQRtWz+OR?1mqGg?fd=D zN&s%9*vO@T`qD!(NIep`EUY+;4cv3ZTw|6U4yQ1@5dtbMm($M{Cq1r6dmLkp%`D|m z;muQ&eqE8zreE--py)X$`*Fns29~@m#!8B0y(5C!$FCkjIm+0W2V5%$Xez@k%2I-H z;X$|)KyWeyIKY+?8O2PgQ)$$PhL%_rE!%uD)~TF`wpPtag3-HPZo@D{i@1@+T0P$7%1~nTgo#7H;#wtzTt~xDkBZy`(rT18uTNVCFC2kZSXuY-OC0O* z;X$?YGEG*;4Tkkiz{JLQftpNg7T^yc#R`joSVjsq^Nj<$B|B^NtMJdsples4W(T1& ze~f`!1CDw9Lu6d_KCG(zN)Xgf0h!zpht%h5OZ41p1}z@(Y+`F*4SHhJ-ZHRuo#?q^ zYhYoOqMXgK<{M{joEXti-CHH-F@*-97sfm%fB@!B2RY@w|PZc^4Y z+c-NdXYp$WcyO1c7T)^~V(-3A_x{QqKkx?}+6VOAj3{jCIHW-;OdojU;(zUr3Z>R65CTCl^Xf? z36L2@x}jFToz_6AFJ_oGP+E=rwih404;cvJ9=igD)M23&;8Q*YIVqB>RDp%WL86p= z=}JJr(EvsPk_HFmw;3SaSQLKWQ28u?zcVFt5%?lP$XAWw;uGW@J9s_6TH+YZpJy1X zH5?6hYqZ`-L)$0|)0Q$=B6>a!GPyB`5l>Rnj+-TE5WWFMRgqnn|SJtcoxi}^QgFCT7jjbFiJ09}V6%A2&6HwICYv4uFa>{Bs2lMuHzb1_o}?voN5 zo%+W#xN1|o9Kb`hshRz;7s1eL~GaZqvy3CyzS8+A2W$YM`I86S-6C_fEoPL15esaY!X;Tr>q0JahH=@(8}y#Sgzm9OW&eI?bMhBW?Amd5qSf_=Uy@hg4`XcBOW+Fy_`*J^MS$}gmF?a!W78_K!96=X6#q+*( z6(xjjMXpeVuxq)mZ^ad5;9lQYq#Kr-18_=$iWYH1kXJ|XXvYMw@htE(;JF`M#vZ_O zyIt-tcqpfQXaPK}c>28wvs1L@~Gby}&`u?k`giO~=OAg8S2g3G; zbP324vYZu(2a9z`IPsj|THr>WSS?s`LjsDE{A2w-6o)cy1MJ*w;Pf$)ckQa4abDup z?qtSy&P`bVyLFloQP4Ua?bgez&4-EjrM_&kuL$Q|v&75^O&hj2sF*Q5c#YYJ6b}i% z9Dw*uD@uQ?CBN>ss>j>=KxEHIoB3{AoQFa-CrK{cX`b|h0^+B`=dX$%+y6B+CUaXF zS@S7Ad+svvy-R|J#vallfD#^ndqN;?-;%f+cjVAV8Q*&raiOUq5gPEN5p>#V*=|YO zTd@6eM*P*!vW1`bWcHMzK4f(7tq>$QDe$tO#w*i(#Cp z+idMz2ZKK}cQDuIu?XI#9Mdo6Z+vx?`I0O7%EV#?jKa_%x$lh3z-&s_RI@)rb-=rU zcS{)(j6yzgpLwi?eMNH1=$OLhXn*eZiDM>P{2@4fGY?w0N9Xy~#O+XN`9Sn|_gd>P zNS-Ap>0nDePsY#Nlp3HnlFJeA-X~yf%JL{ro)fS{ z(}YPgLC6G~InMiVMnisgotOOo2lh0t3QG%q0_pn7!Ret3Q`r~jHAcmo?0LUZWO z#xDgNyEGAa0KBrzp5ew0)x(VxI1{Aim3RQGd}P#8heMGb*o}U|(X%8t9Z&x91b&1y zQk|tAPs0?5m4J!Y*n5n_a~f)sLm7exUq7e#Wb{zuMEVJk)CJfrf<4lC-u3>%F_06U zf0mXS=|An*d43Vuj-%^x>HP$FC3F$u0u*<-Am6}lnYko{ryW6n|6E|lMQptt|K57L zVYbNc3jzGO^#*@qZ1LHJmL}rP+h^xW^*6ZHAAI?pVIY^~Eo%6&4ru~=%~68#9M-?Q zeMP)DbU%G}o+Lug!KTJ{TW`Ocmh9uyHdJBvh2PQO*ACqtn6onT|54VEKlp z{AxWo7z0#3r^CT%kOaL~I+`1S65Ed%%{Y5D&57cxQ=qUznOYPjKIQ!+e$y9qJ3)?# z$tp2-si@dqs)6W{yuU!?c8WMcCMt2rvOS%qshw#eVityFqhOI{zZ-{@TVaz0FSobJ zW^40h%Jfig-c?*5ZN-xjquMjb3)&si%n!Da`&LjW4QeGwG;(fs$qZw+ zG;UtmQPdu8-tIh`T6t7X@vgF1`FL)ZEBy@y$5AiZowrZ7EBdH^Emt5T99Qw_+dK>> z%i|#uqoR5NGI+@G2n{>h$tbD5+Q}FR{rSK+hftHwW4wZ!dIescr|x5q-{qVr3JW#W z4+9JvHK(b*#<(jmL{py3@Xh;s%E)2jHOVUQHJw?#zDa%!@p<->z|_$8;Jb>Y$M*Ln z)OWlE5E>-GJ{f)rn4l?O^5=IMPimOg2k#Jg_4kj~r@7urUYIrBAHCV-ELWw-^RyQO zWx0H}gP0`~w$kZqX4bHFB-$1u2;#gyXW5zx?-g1~0tMh1>jO>q8cH~oEO*&zWL|Ft zR1bby3m_-##C(lI)aU4(FhL-pf2 zwo-cO*|@)_R{qYuOi#UVm^`p^ia?F17jtO{S3PJ+3T2cOla4{w&3uAj)Po)x!R>$V z3W_z~BGcnJ`rsof3>{7O&UTD0JIl%S&kFB@FLhkSskl>+G5yisjuweTiTW_C~6a^`YoE6yXp_UT_7|Gjc1f7!)xn z*H1(7e{3`AvnJo#Taik@CJAW9R7N`SSY{e}rMuI|W{TYyx{N2wy{;xgxEs?v1 zdM?={i$Zln(S3P47MMSiY-Y)5OVb3`Sb3B+%C7}>;mHbh@A3J( zYa(TQkG??3+5U8F^g_1T5|!qS*wE(~S$c-ZuEting{@~&-}wo&D#st4YQC!a0f>8hmLY~K95Y&afe z9jTgON$raz;HU<_oH6%{jvf#75% z2hWY@R&G>=yI&x4Ym{Qp#C|8WM|lt6zpYF!ks4 z#Q@E&iMc)nVu9U2Xljt-g+@*)1@E9ahoCdih;9@Ci* zO?S7zJ~eKee7C@ENUs{;o?PU=jYUmPfyv}dR$K+{)XbcaUmHpft;FCigDF^YHL!R3 zNqAl^!r#ycV&;eq?5>X)=4LStxN$kQw9nVXILlwl$WDrjfAAu&ATSG=r% z*Yly8(set8?fUjjFZu50)|($R1o}Za1wJb5Aq=xuzAGbH!=eXT2wjF^$g8syrlGS7^=_pkKe&XG`?74nJK&)!S>tQP_8$ zz9#raanZi=lXoi&r}JA)V%X`=Z}Ns>>xLd@)C9MJHA@|yxzv?nNI)g*<%2Izr0m}G+>SV7lP{Ttb3fA zqrovr8PM>Rg&2Chf{Zo@0u-qaJ_VU*JtOkDgIiIwLiIpU%FpCuX_N@WRbD;1;xRT$ zxf=#FSYIxQ#G zj<25|zW3?0*0L}N_wot%$q0WQVW?zqf4UJbbuN4oA|pb^4Pp!nLp08w zokBw6mAFN~VaYy`8Kd;U8Ie63wBBYgL)yq3;d}YQzr1~a!mVm9qw4P9Rw1s!!e_68 zGLwOl2BNTYEQwYToMWukQ30&k=pl$u##S_DB8lN^si{#R8Yn5_Ma-Zb(0(?$Edn?_ z3c-cNa4`ViUJ}SMc&*yS<`fZsv-X1@MXzLlrWQjMTI2Y5<9+#JHzSBWQRBN?d1pN)wij2g87N#A+u9PO z7r!mkvtEWt?XMlDLQN;~oZ83^R$4v2u1$8deEazCv)RLI?C)O}4TC49x;ifYM74Ha zoqnFLd@|kL_2c4bYvQFwPxrq?wE~#R*Mo5dKd*-X6<9VNm)y`KJR`I*Sm1&ipJ-rH zgc+m@(52aJMzWNaZ$@!6f8LDd9{#teRzx;Sg1|8;F;#3QsR{0FCu=GoY)Q6D3OlI= zHWha`roEkXvoO}(I~>z)rd?^pf5LE3Z^8?I@t;1y|F%%?mHx_R^LcmTaSGBLzEqT# z{zA2WK|H|^r?GvlYWm{|{?(rrY7F-MzuS@ozwS4BF`*wc^`yxjcpu&*AG}`m`nR^E z!;YijJ6lpI%)ILo?BLXWbN=;P4-)2|*({M$I0Dt`(&jW!bbxa_2vv^Bu_LmrI{uAn zW#7S;pnalXk@C;N3y3JZ4*t)h>S|UI2BcTXQTmfA{1!bRc9QMzn`m6Bz^QgAVT+$ABT?c@J0Izb?0Wuv3+Ew0xC@kK6Bf)B)*X z-Wb6in;mVZpJs}F3DZ>q-#0pbL>yH)*LB3S2&t5t|7GHCmmkN-zd5Q>KPL{+?x@1; zgj!G(D%g53GTk6eFuqLr8mu7GcEcBB5-}4>K{Z$qdM`LbJ3MwwapQk7$OouOzaQ!7{*D2$dGRBHQ_i_zFs$J}>W9DVX?_a9;}(Uh#P zTQb|<tiE+*lht|-roN-axI`*l>|Dl05wV=6l6&98JF zYdYQK9cz1?Upv-uEH&cP)8j?&H>j{q>^I7b;2_9hdX5LL7i}sJn*VWBots3qS9J6zkX7CaJ~{>je60? zXp8-U9Tl1HLR&xz9)2O!z8_1}JEC&Y!u1>{KzF76eod=ZCC}l#&G>tazAjJ#nCgy{ zeBS+?rRSE*JMGp}SDMB1UOt-k;@yi$2TN&8VKcT54Uth=ncj2vO|M`U`DMBKC<@jb+op&iu3ftT)77}+ex+gFtm7EFVm+k! z#T`2ZCJ2Q%Q1Qt5~aHK=i6#@HJrK1}yWEOowgv?X+UYTijzvt(NO zA0vE(yps{)3^_DE6opfLt6a3La=v&*K*$V**IH* zo%;Vef@-k_DP__N>)EtfxJz-&qT7|+ScXAViS8n3gO;WUTFI{nx)TTSP_d?QPzS+w zvj3q*WYO}bq_E13`o&&UVB33osOzv-lEHheb@#KkFG)rKo63vR5a99h(kM2)v!=TU zng##$vsYPR9$QjLMsI(Wf{38*p^kNf&fgvDIj$Nh8tCpLBB;t;1eI4-{dW=cuOe?NYAc_h@iiI_TtNV z%WHebkP_Gxfy)Y5+`V-LsnfmnrrW>9m3Kkhuu>4yaTf|7%<*WQnYlS_S|nyTo0(qb zD;rgMQfWWGIEN$9e8?cHA*A!f5l?i?O5$K1*)N%U(Ue}bxyK@1!f6@vWp(pQa~M%s z-_sr^?>GlFlZy}j7jTJmk$?;AODShe=gW<0`*z&T9sH@wtpuqu^=-238;#kS0^S6+ z6c_YK&n$R|>L-KDsuPcbt$vX`{e#$cNRg1l-@HBQe4uOQ66=m9+TbV}%U0u7 z)1gPHmKhqJMQ`wD7gpe^nZpfFuMm$0jK!ZEMJJ+^xZ%$TD(n0#mY=Jt0N;Cbfa>}v z9D-X2goi5)(oukfEQJff>43&X#Z%O49w2%1Dfs4tG z{Qyc*=GtMD9z__)_HtAxItZ^yp^XEpylIwJ(ygVfAl{xbVgGz~l5VN!SOvsKG%=Pf zwMdmyNz)}*w^RnLl#Mt-zIuG_<*n5#dMv=-iF({ft|(pqXNp@_FcAKa6gOk`@8XRA zfxDJ%*vX*L;`MJhZu_-R+Qwp|)xaGb_rI>j{z`GhXCKib;JAUFWxwARey6xPm4}a= z5dpgH{|?8!eT4c~iu=9fw83#Y;9u34sYmF^U)31n!9nnUuNs39MLmK+Goiql+q8&lwF#99{-6;EA zIVCSy%}m?oJ)n0^8>_9{UXaMAT>O$#0tuh<3Qf_sHv@0zail!eJ~OCyCT()RV-iPMK53v3`v{X?u{ch+q7vd6nvn zvsKzlY=(=Kj zN$=dXLf>who1|QlKFsO6bg3T_c}OU~ZOObj$3sW^`<0YLT*> zo#Rav^JSX9A(#J)2FYJn^6t5B;*MAS%Vl$?lYV%|t1g#qXTO*>`La-V(Gd5rquF=? zzQ43KDdyB}arzvGTD`ONcxyD7{mE44zp73|gXE8@)4RJQ008ei>FT@+OusD=o?a7+ zKmTtwNS4#pVMKa%8==HYTa%C(;2ARc>aV38@NdCx~{?Q=Gy@g}2mxoVX+h;X9vmvhJ`IoP?z5i;E zkfij3Zy74frMQIec-8;D>im^69p34r|7?)_&1LhC2FZWSnf|Cc|GbjT5?H*dt+#wT zQbnG2nWKj(!tv~IB357#eK^C3bl;&XXgdhRw-CpX%nbQdy{vv0{H@C34GKSa;I zRXdHFVT?VFC$msX6nOvQvvIK3lf*E|X=4m)pzvPPPkAS3ScQ$04=c z=N=zsrNzg+J#FE5_X~qb@i*=zfC3KIj4-&Wx55-nA0)$Z=|&=xY-_G|bG#3)KK-GS zzH`}NHpGg`{IiXNf@tI1{Jv04%yoahpC=SZ1mpeZZ5(>)LbVF_Uvj9w+Bjjba-G7S zJf=XD`zlR;Ed9tYtLJORo(7%#>;3%I%>5Zptog{_OFv`ozyER83SC(5zs|Y4-7Wp3 z+X28RvNwd3GIATe=wf74$MG;T7z1T=#ErqA)I7$s?~Ow;?Zud=ig=1swo}9SyM* zJte&`rwOOy50Hc^}m|?50=L4yw z-oCY#hTP~BO0(Z=17CCmgsXkkLU7ZZ$HR)goP z1Tq6SE0*>q^9;>E$#mP5dO&j(9|DK40P$IK&Vpqys9+)C7@?U7q2f35<Hh~;H_wXm@a9Z6@ z{+P!q)Cz;FZ?~o1adiUy;A*L}ZD0#D9?$dL%Hv_WgUTguaHim5Z|~La3Lwx#(ZuX0 z_WN4M&nRQ^_VfoIxbBpOKOCMc z)QkevVUY&c4sz(jj8c3yU4;%WtKC~(uWh4CcTzQb|B!gQAzJU0q<<=Rc=`DVfh5|Q zB&JIILdvOrZCcpFC6?nba@cY}xD)HGP{j<(m&xq;si{hc=OTXG_{-0Z_{W{jC_eak z_Ez8J=l4b1+MnmE-rs&+tcO3iy_^iEPq+fRof32cN>WL#gxj8^$)8j{()wvR8{e}> zbTAEjE_&d#RN#Eq!Tt6kMA;A{ED-h)uzuMTT1d zIDUkGCT&AYbra67FxpSzwHrvrlZ(Nc#Nq>;2^D^0?m1`<99~zTJvy1egFh=rOynen zGVKFitcM2CZ*Ci5u`0-In*muV&~#>8`=!duf$R#OHI=W!iE?}8!ytAJ(@(c$rqR4%R?EyH^&N$i*b6dtfrAS(?Ta zbq;u<>f_yQ%upTuPW<3>#h(Mj>NM|UQVbKOW0a%mM2$%)eSr?x#i~_k>B*GV2@ciq z>FL^|1o`zi&Lna=-Qtm)QoQ`<6}-8`U_h!$)364o71o+-LWGPpoJLSQN(R4Qm#33e z(fJ`-tU82IVQmjh(X>zt-#wI7zF5<-f1c?ODSQvf5P!;$A(YfaSr-Fq!lhq}W+z7p zL1V!4TMY)uh-lH_X|voyxA3{x207Wl;|x5Fw57Pl(Sn2BIf5QQtc`(|d@X=jSQ9;Y z?5N08pGb+XHm&-LFUt8DVi{JcB{*)`dLR2myt%(p;z_g52B!fN;)wMF{n-7-zi1^Y zZPmD2W69{2wYN%^c&jDfGb5lQLeLHF3(I03o=_ymv2CZpKaux4v+akMRbLFfmXjSVvqP z@va7XdX{EXD=DTM>4$J%v`t$BJpFJ!>`f81$$C0JYnk})g~Ge-5IG>3VU%c`r-ICiN6^t zPBA67E|KYKpA?$WWj|SS-Cp6QS&kdoK$Nyw7$J^XukX$v6Na(6=9^6-Odm$hxcaDC z_s6S|hmC=Zr9q4+aa2EAN3gGsQ=}0r%(2p@_*jr+qeCQLesvjh6uz$o%zvx!l zjYw>H^DfhMX;kFOJ<&1=zuq0@2DpmJ#N_QP3H5@vjL5Ei*{o_6h6`DEg*E1`+=$LL zTTgFIc*tfoF5bU)J!2=^R7i?P7#OrQM9s=y#GE@NCRN4GDml}&E5E01%XlAVs>E9} z^omPpkRsh0kd)PFg;BpuQyqX4P4_*XdJzGD*Za)sG3jV@@6`g)@iUnON3d>k+`IZX zK!f6iLrVVgBE|7Ai?g|$>PsssY59mGBJF5y3OVHuhk`9|+CxhchShCTKs#%b)3Y)^ z84PofDUDe&*3H1$d9xBan`Q!bF;?7xLW}ea=aEN`Sj+*HPy0mmQLvEz2GF}ze$fdW zOl?d<1aEF*lynZ0?W7}`ZtcUNa`%Y#r=x@m`6w;FIEfdM+hipLym?TQ$VP_7AtsP3 zwWbVK65~ms_%fzT&g-c$J!;PljKIQpxW6wg{b1B8c~&+{I}hPo`ZS9ZSbGu|9CQSZ z$A2jRhNv>G`bbIR<7Ql6 zqrCh&i`66NSLxdMgtWw`ep)xjKCP?f6j&l1MvM4_V~z|PO*YZHE!z0{Keq^rrm<5| zo&W=ND~U>gHhM%aNy7^o4@KGrxVU@L7q(wiGD2s-dgI(n`a{|6HaPbhmd2}ArR6Dv zKI;_+B*^3_#Xn+&ZODfbGay^&g=xHiKYH41!`TD-ZqycBT9a^7&Cw@IQqrx5|GK)` z>`NJX*ErXW@>ocd6uUK@2s-kEu{x5YU_T{VQjtPs; zy~aZ<(6#WFh7g6Ln4YS*vp`@s_kU0^v$2d?O`=Z8O-eh*W>d}?qE(*}f6Y4HeNg60!KACLMy)#3qK zK9gY96@AOHqRAun3`7D34WGG$Sp^eqpgismqqHQIvI7S&n+A+L`iNqMf~+cePT)@l zg${(b&N1xFIDZTdz)p0*NDM=VZ*UjZTVrm}mF^<(H3P9TBC!f0v0F*L97Vpg14-+H zlNRYL1g%Oh!QIayK{_HYGJsKJ;6!>N5+MW zmH7m8OtgMvbx$_p8#MO*tS4okzpR0U(o#x(zdegDs51k&f=NxLt%#|RhN(EMdd~$VIEYA3QD6D%_TO)%Hsc#VR$NiVq$Ig3rA7aJA9M_ z-solZS{gCb<0x!<;GZx}WdJkBfS88Ba|U3dG6<>DPE%?mX(m zaeEuYU^EhpYY6%*K6ZOPP8K2Vbv|&JC{}@x!Xj5OShA~v@GXY$nD299w;W7!w%AeN zJb84%0th(6^1X~_*Bn~SuB z+RMJ?v-)I3R*yGvLfg0`n4w@^#{@jkR1y`%M(oTOEfX4Dml#u&(zz8W92X9u5_KTq z`?PtZJ;S+e1A;y1UUFmOXya1J(2^{ZvyTHmT@tXd&=X+C2+-yenU^y05SW^mJDw7x z!F?q%4Ov=f0vA8wU$#(cJr^ZL4r46H;x8zwpDl2wRrzA);NU^GGK~4uOwKmrK`nAv z(i!aHS8l)nY4x`aEv*baD)F=^qKUvIDb?ThF=lDuMYbwq|R3g>qi~V3dkC{_Dvt@ zayX zXAW3_Yq*&-T0rB)uOInI$CHDHTV8_AwahY!QdO8(Xyq{)GA*)gtnX>7MS^hLh0*Lm zx_*T65Q1#`44!(cVSP|>!z*vUV6ql))gYUVE9RIDct;Z;74(`J8j~!XYbebo($Tur zsN==cHZkxz7pGkvW;_~YczMxM{-eECmW>I!gG8=5_q@3&yTd{lJp35wTiMY&p=~Y% zS`_L4fmGg`wZ~%rKa9PHBOL15^{os>AH75yy~iNB=#1Vw(FM_oC`q)z=%bfJqLT>z z&oVCHH!nckde98o00h8B;2kb-&=5n>wlXQHlJ%^s)ezw?&oE8u2t2`ATvL0~kR}U8fw{>2ocu zW!48@lnO)gTE8sljMk8TF&UaD)$eh_2NfiavWWCqBWjQ3cX5gLe9K^JQ%zpgM`_cn z(p5q?n<-%9NGaC1QXNN|&if%c?A z*pl4rskm{Yl#5}*_+zRS)=bB)?hj4y)Vzm_Jn_d{^xsi&&kYJJD~@111AlGNpSuoR z&=@M?)?JQM{&F2S?h2Z28OG%soo*Szk<)p2C9ygvaMyh)fq2fCY{2V(zJFfsELd02?1HUZN$u0 zn=pXXMRSt$sKk&^UbA)D5D;MYZJIuG`Z;$be=?LJg)`cgkfLx(>s&7WTS+PVCz1Gx zQlrQNdqO?mIYjwXBH^6Dw~~X@S*>`G5$jwLJvbp}P8a}nwVWk@sE&%vjE=cLlcyOo zp}OD<^QGv`x@fGUAVmUrm-*t`FQ43O9uL}C9FT>o8y+&?c~M+`Y(i2H4E76 zO)RB8C7rqiZ*_2=AP~K)O`did%HO0cbCq~*tV4emaUy|LaEGPCIb)pfRZd)kQ4!2opiXj>(kC-^dQZVj!F>L*w;)r6kI z5KKJ`4Ilp1&!adb9|&wva*^H8J^x+L7Pc-R4`Uyj#J7PTL^NKT*;O|}2X)|kM%0C^ z+sP}kp_z^$-%ZEy0GtoXpDeW7CP*1Ub^xQG+nqNUJu!Do1KXIM5qxL|A-#yf6@#0F?Rq&8fBW6ckcBy8mn4QmpcToO?bNlFMV zvhpVZ_?$8c3u%3hVS|OAcMgVN;jf)1yg!fmehv=?J*WhIt<))DDV@S3Qd_xb)WeRt zOX6{e4NSsigbc%5(k(TXm_gl1^_Ch- z+>~N6_9r!V_Q)2J2v%}5Fdkk~jbSmYxUP@gQqSc6oGN~fjW2I6h9wy14B2jXkPGF% zqWWegT!8WZmt-JhULhYCeN4v=(PvM`W--z{*SC2UOaw08Yam>d3gr)~a9Hr;gVuXZ zq>%v+UN;4A?Jxi`1P@k%&(Me;1d-X)mf}XRKfM%&jP;VfVDGuCWrn~$+&HI0@$)#M za+pOlTfCFy-%%7WJG$(HnM0dclfNN%;t$;x8y|PqHXpohjM>@8UZy8?_u8cCzE zu-)v&3D>IUt-O>QB>pvezOB0UN*Z|__nxyaAqNJxE+ggv@%k0dY+|OpN-B}Je}LRg zgiih#50<)8L4ppbSYidg$P_cL@8?M`m%-B*rNi`cEdcYI{qgqE zoJp`Rk4TtVKkvh-zYAoafRS9{$FUm9Go)j*%DZ^_Q1~fCmkA$la9{pgcw+2cD$FTA~QYMYQE!P%wr zI4E!Ey2axu3)^3NhOn0gZ-$LX)eQ5BOeQ#uNvRypEPRc}1uR3aoe`Gx72!v()of#h zz#QdD2Zn~T16UcRfXgCgvuga>e3`OE><5u^mi2r-cN{a=<3_{J7%lwRG&W)@`9SrC zSn|YqFg{5!b6%NL|E*w3S7Amwl>rWL6LaLDC9&zS6SX&Epwh!!g=2~Whj0;Ed0+DE zEWVb=n(CbbV!^m=q`HG5=k)8fEisq4NFow&S|eppW$H(9xNZI6+hfbRhL7^Nq2At_ zxkfMD1UIWr61)q&ndoZ(p~9=^+i7RxFN&h-%*tq$bEcai5zi-$x66rDd|6-gDz3g} zCiTY18xmDf&l}=Pq5H(@2|qGuIMks8%wQ7}#`k!jmeKfB*~{dU-rVSPXE~zg=(< zzU+mt-*u$F+d^2#JKF`TJQSIiXV!q9gGhcLY{W*b$jZ-2Gw7wWCb3&7! zd-RF~mE!oEfB@ibSLT`ReH;i_{KscPy4D>g_GMisdXj>%v!A%3?{pJQ)N&;j(YrQJ zkL0}R>{%o+lwpOQ4o}WIWyn2M*J*NnI`!)QOn6dizYd#M=nRT3!7sMqanWFHy6nNLxV-T^_aZnOsMyuZGwub~6v zv|rNk-m}#LMRU^hJ3myZKp-rb3qiCrSrT|LFMdR;zUtgJc5UKHvP!^Z#S3wyxEq5Y z!RrCgLKNf+pJ>LNJA#m<-Es^ucl@7|3!Ku8NfT#`ZTkDhqfLPi*<~d*!Nj?R1kQ?R zNu%HCH*;w_@wFC?E8t&>u}HH@1$ZtK2fR*rwdf~ku`Up=pVz&}i-QU+{7Af!?qS@h zh3{3=YG+lzioGKGRh`7x#WxL!kh5j-_t(M_JnxA@BB;pyQ)(cM3W2U!7|Np|5M+}w z<%_U1*MjRKg~U-zK2N1Sk*pXK9A{mo5Ey9L6DOA4(2vb8tso9iM1$TKdO!;$KLqH&T7S50j-MjhfNATD z%V^)>AzoH05AKyM4t_ViXioA-7-Fnk8sd8=d>0@i#!Qny`|w^1$%pmGuKv%qp(!kp z;fRbEct+(`QYa{BJAm~t9#SJlSC_J1|55S`E(*NLc*CvOIO=c|puZkNb%GSTivg)h z{=BofAT?8mHhGkggulge594!T%W=Bp_$D7xp`)LsT6WpklnY0+6=W8w$4EM5^;mq6 z#q97U&5op4cNuj~8Ea)E<`=&0i@QdL2Gb0_cCf;^VSB+nnYXTNk0vPxWR!{WHE{^i zCj-XntMH{op4GChM6$bk7Z&YKQ|j07#;9O!!mCc5HihR|RPN7oZPJnP^a)KhLVPR9 z=@)e*X{P%w7M;Lh;W20!;AZM&c9~}lR`us|?PeG%OjGR<$}qxCKTv#-o+I0%`P9Xa z$CZyKwk59%3cHF zR++kITT;e){!EdDgvSf5q}&r~R;IgwiVWx^8XikYswLm^`-d0cT1)-LQPJ~v<{r=R zJFSdpVOvr*@1_YEfz>8i7QoHBkyQF@zMle>Eq)3StfR;0Bf%ODn&*m zNER(l_Oh(~vOuQ!5T837n0ggOsMy$}55FHM33uWnIlYsSSA-=6l--lTgORV(iiMqL zCd$innaKF%MRi~@EkinjXXQKtW%2CsNdR(7qZLn%W!iV~FY|z)CVv0Gl8B3rm>m}K zV>no&BXhLs6TT=7fzT7+i|p_ccR0}VFo!cvJ`^o_p&O1QXJ%~nq+4#h2b;w{;ffRl z)ISTZ<+M1nE_ZZ^+a@CVnnr>Ra-4~yprXMZtTrcYZVBD9}5+&9V)?0?uGhO5yw{Qsfi9lKBima zk}JRi%s+1S^$PgB91|x(z`5%(abzZ0IMHx~8ZaK2yw%DVqz~_ryabFh^BE%~<15jh zR&@exKDIMKr{*~xadz7tehObL3PxjnG^!#k7><16^Rl%xPaagsN)9)l#VUa`TH9he z#73vZc7Aw79qxqrVfO;jpKZKt*2YBL6Ilu6o#SW^Bvn!}q4$Tg0OEWE2Q^1%+4i|k;p z;z!u2@KPtDmmJ>vZm{_GclPe1?ljL2GDJ= zB%-W1K1c>$jiSrJnIy+m`GpU$ z)y=4eyMESZrk@1%7uoB`d>(6HEgy&*fb#5oaMIRVoV zDVZDF9#q3Ior>ZVv3>(qWlJftrJ^o}Uy%q1Rs)Y{N|LLmfL^rsIRWCU;mQTAlGdt20hXF+* z*n%}#jV80kyow%Dm_!f3k#41V5x*$zA96tQP;J|k4u(L&!}@6YxlZ`(rY9O4D(a{N z3~D88m(|x!V$zOy-(<{H0k01G$U>2xz3Q(Q^9GVxZ=Alh_-pj!Gd9^ z>|5HCJNAV9FWJm^V)RV8L#brn@UfWhn?YL$`2(5CZP9QAn=sKO=lf#b&{+zX(_8fC zSTM{tym@RqP|kaP9n}S zm!c2|sAE<>hdsHI@@)vtAHw6_Y8U}GB8UJZLL)Q`pBiUd>Ip=&8892VO8ba7RwoNNT1}ycR%x-&fNiX9jk58 zY5BQkV?=@39%T7O3BDn_O6s29tZ9SoZAqz|PxW-IPk!x^R&OTI+(Xu!MwwYaNd>fQ z!{kn_RCik}b#mrSM9d8#Bu+?@kU!f#%A{?-SB{1_R$-up`i- zL+521R&Ar5_?Az&B8c|Y3VLXa8}5(L)j*1+k5YiUG&Q!ISdhT^cJb9Uy7ip; z;FApZTtWEeyb$j!Y#JfskQ#E{4V72+vw3XCs+%y*NH0EYm)goFRRUU$)f`v%?`s0( zZZ-rcfmQ%}D_$&P{)l^0L>U1K;j)`QoE1i{?MnmU>S(x5PFr?@Z5(&Ls2Oa*(Cjhq zf3fsmh;%Ja+#i630qy-F%yS*BiydIOD^AaA7GfRRNehf&*azLMcKKg|O}e$6UhEBw zA57f06I#{wTXtTEiYSZftKPopeHaS-+gUR)>g4O-pEs0>rMCY(YyR5}C4ye-twn$J ze@?zMW5G9Dg8?-qe?g`|Q~nH@s;dC$z!d*Rru-vhTDR-|Y9aV!waHw)z5*38&6xlE zw~%R+4;B0U-#3(sRLB~3#IbBY;`WBpw9wwAC6Jr! ziwc>tyC$seFQG!FD|qg=H+``Wi5(`dXKo4d%i=ObUw>OcN$h<|=F^g7P4Cpi3viuJ?YLnb3C9K0`9;UbW8 zViw%~q$E*dQ!zEmC$h6Mfee?5Yck%}&{3vva@Z2zQysDO=n7!%nhZN-ZH8R^o7z~} zqBw&Mxs?c~3?D^O*lUFK(kz*<7ucC0Nl5|~GA)&cZfT!!HqOMGI&vYBk-jv=a@Ir! zU(%eoYZpZptVJEO!H@q_6dv#;!&Yl^B+2G0S7`lYjHnWURH)~?3Vma?g0b1b-a3MQop{TEN?=T5yju;PHN-vdhXzcfazy zQQqp?RoNwfWt$Ca_on%LZ)MxPy`kK@DtiOQa;Q51vGXD@Eh~f2~Kl-)LFB!+m9&|B!|zm#glJ8W~`(5&SvdP<_b*B zGs@5AUF)UECW5op&K6mUG=DDnZ7eghz}C;4@iGpHi)> zSG1)fVI(}Ncqyw#(43%v_K_ampk=7cqeVg*=?}4b9om6jgDl4HLc_yaAS}+41x)Xc zJLSBU{eAEY!Inawlq8ZlOa1kGt1FqMED-NiBUz zz-2S85@;isRtbns#-q7S?xi;fo30L=4Omi*p@;q z)?)OWe8F^ErE&t7iNa@`vKVvBs$<(a@t(Eh=8C1Vpt;nsBHw&IEId`dCWP)j`7?Ap zZf&eEz0+^x(O6!+9QV}_1zLc7K%TQ4d(0REw>Owd$wdu^Sx=ThF;9a+)kJ-OrNT@} zbw3WsM{g@O*?6~{!iLOeQ_Z-^n=D57#Uslj?wRH<6fsPBS(?eF8I8(-3X1Ez7psAw zZWsG<{0m0AIW_T4?~_!764dct5a=^GX?0}IJKcQ}DjORd+G(bIr9~Khy;;w*7jC@P zKe*@Ske7!a@_yL3Ke?0XR{EWJ6m3cSsd*nK`&k8c7LOmi&t&VEvXT2M+^K%uJRCR! zo{FzfE^ba@l=sU%ZX9w0E|j_T#kBxC2ee`u>R1J(5;6M@~GIVYGJGlhW4nl%!t+E7}Ow(|g zeYpt)Uo{B)02+0pdmkZCNq2Ji-7#I~*S-Q)T|4N z$FEM5dC%Cjnz_h?;1c!;u1iNFGEH5waR{$RHVz1~hkfKcaIhUh19$VOM{0^>t~bi% zA%}1q!;*1qZ7k-ghLLqg)E^Il_rG6^ghVR1O*=_RY12q{zwud`fTo$y3QXM`t4!hXp zynQnCsBG2DEdc?Sn9fUhP!>O~79FW53qd>v7S4Wr#ahEfy6?oeFh4u|;X{4=x2L68 z(~FKT%0rnqJUO=foJ8uIW^!I()Lbojxk6gDmwGJ=W2QUNMSTYdHH;WJrcRYLA>%_i zxHxozYQj80dRU$ZhgYjww%9!>Gl!{~7VI}?F*h-KUL|NYi&P$}AJL(Q2&;+BEM$-9 zz?1XCH0`lTekxWUWrh>>w=G{}TZD&CN>6fpsy6Sd0C~c+PV-;b;{duM#xm}+W#7SZ z6uGHyGq%Mk#_65fo~xb0C;PFX@r#vfJZ~b&$0wz3ol$M&QoE_*>_c;_v~;i}C=cDY zu?%ZZ4dTCi=>8;Hql3pP(Qn>P=&bF|bNjygPqPi25Vn-CqnMK7>o5DM9V)MUBidV{ zWGezzu2~LpIBXz-jF;mtmIVrBek~0U(X|D+;w4O8`jNiBti6^Hf2^Ib^@uAd&|Hqm z5VZL%V&l>{xanuz_R<4^)C6flMi_>TbXuVd$!PE!!;>Q@h z2pGi$K5wrcS=deZVa_q`1?*hkPG4i?rWO;O{^r?r(xBN#&GufuYhxNTC}^6Bls<~& z%9!*#BLo~Of#2t5n#`ADNCP;MdBqk&K}}sh=FPTdN>vp^GR>W1kB}*nlwB^MFErWTA!$1K%*lRjAXfYoJ>b)3tsp_sAs70cItVQTmj{p7;?*LAIM4drfFLH|eP^%$FoaUnrQ` zl!SH;LS#xxZbuklO{h{$C_uqdRLRZKNP2gdcDjSE5>Ab6N*Rp=$KKyax_88Ju!@FJ z2~erVz+eIabxyX+oTn_Xkyc{H;1^`hh8)W&o#Z0K(;YFqUb!Ucyj&eL;~o+q-l&74C*Fijv;6Z zhqxFW{=v4`7#%pZeJP=)_t==wh(^&==2C+((21-eVM0lz!kNNexVWbnSh?tLs_}0s z@v(F;UeaW(e<3bnLh~qOdeMW8#+15bLT(UEcgHdCen7HLkL;=ra8*NkfcbPx!RnC9p{R2&lLakob*Zd++vE)8(9|J zuTt$_36dnQ;?f=B-kE8zciOeg#>Fm6s9>gX;Yr&~b4}%>3?1lw%%7o^CvShvu-z({6PLP~sdrfjA6))1 z^@hr|Zh7=)t*n)O-5p4a>w>6;f2P3PsEJVuM!)RAP}8k~u^DNmZvNnwe6zp1+85atZ6Nqx59gBCr zyzu-;V*m-?NW&@wfbS18?JWn2{GF$!};4hszt5X&q51Ff~q%HtefC zwXVyzx)SlzZ}+PQZ!tM21@rF|SkNo!1A?+|_3Vwui4B{ApX_p1i_89}6xjP}w}VID ztgr>oqT9c-Df~{(S1R}N?pJye%J3fbkYC|z_EMK%(>2jN88q!-_#Mnt*>k%MW(q

    ;>Cli(9 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 z


    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%0D+S8GHjz{$m3pA5#63_48l-nu-4e>m{1C`j#Hw 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

    B#DCq5?XWNTzUz_8W7GNCy|r$~w5)z#W&%x? zl5Tf~*xqe6zGRu%9rjS&9>cv%G#k&huG4wlSiGcUn z@iE!_L!E8;-3&-+{j-{0z2(()6YGz!?ZmOUA>$$^wFq+KleP#i-%)W?!h(@pY$Y*n z83Hkr66tVVl()EqDxHIzSZYk1b@{t*EO3G_b>O)}zNuqe`wn(?GBm#!xY;Q_+JQR5 zWbeXc33L{J;hYlOYV;BW1Zw9}elGZuGRafR$5s7d@$74sFLiOGkI6!uVpya}2`dxJ zqI}Zuj%(xVHjy}Bz8~X@ob0i zYbrpU$(TQI$&O^2R(`zBU(~*@&$mYx`hw%nehI9>jm-wEk_Gqs>QmITQ*$FU`VFMi zY=yA-JiU>7M~UdCaGbmgD?^*2bca$Fe=SRfT!gq{;bnRHlZc+ZV#T*92f;uiX~}cV zwu}9kGjz6t-tR}fx|JK3q0_eyVe}>ay_YWU&^)S33O>oP* zc2yR<-;qbw`%oXz6>6?I$$2l@P52Cvz!0&r*^LX7;URYzT&sMuHJ}0#OCjU?_9q|x z(~)Cv_>!8MHzb2Y%&}Sa!Fk`QE zy6kH(400`nB_HcZ%(Td2GA0#dz~hU9vWh@uyq40p;ZcP)uX00#0nqeLV9-Grw%TqR zF?WG>YEvgSw_h6q0|E=S0(iF?(Au}F1XK*25#*<&@=U$%v7xPof(4L#MUa!b zUq=zTt+jxH5@=|M&QfV&12)uOMoY^=|(UeQXT; zxK^N73cUr&QDjK=;3L7rOgpq_n_5QNwOx5}p+dINNAptjB1($fd~xm>Opl4H<<*~! z$3?qt&{LHtDv9zXdM0e<;t=)vfv{d-TII;wT=Dk$y*BOb%a$Xus`Inz^A1HS*|5KJ z*%Sn;2Z<*L7WwxFit9vVcvxl=Vt}C??ppS5dEjk}iUgAJWD0?*`l|12vYL6>czF z8PdnVpUQ9an)PZ5FWE*BJ|_o4tByg6(fe>AsU$9o3q`Tj5{Y1|Zw8LW2U64VE&i;a zkAzJ~*o0rNKc{m!!PH`qEBWEAXNOYnJMnX#<00QxOs?D(aP{dY+k>Eg=e zK-76rehd01WYv0 zujT9Je_~*(=s)T~oB?clv6y7IElvkI(ALueMM~}*M6==(p7y7b7(!fxS%9agov^RS z=MKr}^sR9xt4&XYthD07fvlrl8suZ=C+CkuK87J#pNF3^iazd*Pkp?SAZy4KH04Y) zU=mA`?9s0ab}{N51Qt|h9Sb*#3q)FvRD)ZUy@c&m&U#ySx0_rV7u*QZNI%H!9$zK_ zFhbMkd$%u3orX4EtQ^f@!`xxls`lRonI}wYYF|abvb5|Pq1#d&l3&&Y>|B{vxSC>` z_3sidAY?IJ|zPDj1G}=me8nB&CvPk$!{(I4nOx7yk5tOoqk8 z=>S_q?mJBGnJPM$WWSoNTK;OU>IU6=_lO@j>u3I-o0b^#OU0A+VTBG0X0vmSotNfcF*cQ^by+N;+*YoC~5mn8vY+!94VmhZHqu5 z5TT$RYVk_R-M1VAT?qmLB@;7I6e_A+jGtxiAT1#Mo+NZ;zrQ4fUv9*aji6(qqWD9S zFH_ziyks1^LZ;bIexlvfC*m{DN%I_VOQOrZLs+ot$1-mrs1(U$-9uDXaXR8w0_SedCgL9Z>q-xEWp*j)@wg4TP=V zLSOlpmh_6^U8HJPigm$B8dF>5Z)3jOwk4s!a#1QUm_qeRU>;7bB06Hb3^AcDX=1Fj z8cLWmP9mhGsU+gUG@Wf?V?X(b!!loHrm*vi2Ef=aQt|C5-_1&MEkr}^q;p|01c96g zj>z;z8JX;*kbjZ%^7UX9T?$IT?>$g1rfF^&L=I>Vl>Gz zX<;ZtR;f93R0V`+B`@NKr(hze z!6xT-D`tZSxucY4=P#efiGMlR{bWz>vqzO^WV#EaN7xVVePxKpDvoeo-ec`hRI5;9 zM+|9-Aj{Yu`7{M4=OFKb$2F`NER70xjp#N!5DZF;>Us5+-OF@sF`@~eB)v@HshhZA#@L$!Kc=H(1vbh^)35mDh?Anr| z0+mpczQ(Fhd`ggX1BS;UH&%6U!y$ICU8sgH=6Yyue*sCv-Dk&K26kmt`m|`)`5u2q zW%xn}UqFaYafrXBnj2@*zZVt$Mj!6}zeOMD{$HXG|CyXbS`P*Chl>6~+8uTIAkp&g z(r!_uK&gKuCmlEb$x*CgI{HWSVS#|B?dG4!$-mKu!RtKYAIy|0S>i(KW77KQe0(lf zSX)Srq6R0EW7uDg;>u#qe}rJ8Zc+aSUe$}?fAXqSvi~M0|1AWIpj%l?Z*^QA4Kb#R zZvJy6S;v)d8S@OutMZvoM!ZWG_;Z87YOZ?GhlMYIpch{C$NQpd?t?ZpjUP$TFi7<7TG zx?%P8gka#iR!K+6$|uiB<_n0#2`o&uhj3({urO;)D^6}n8nRvVCAr?$MR_eiWVX|Y zZ?9hXP*=0P{`BbWT%6=TJRrtOT|3zx{phSYl5>G9G9fq#Hqze1YnJx+5Bi3ecU(G!e#6Mzu zRC{pLbkdlA)O<1hm0R(2`=Gq#ZaAXi$Ia!!vHUE%l}20B<&^C=JgS9QbL@Y&9{%-! zn2X!FZl}`y^?17CdZB(a zveiB&Lnv45VwUhDd%-v%Gl1|S023$IEPS=RXYtnX=aoav{udpAn5K3X1ktIL{TAvH zJ?qa^)=7m1?URbKuSRpvTo5FepIh!@aM5A(nrq{q-|YmRSEfAtk?3<$={=Fr|9Bt2 zgh)%feqZc^c;woLKviso+Z@xOaps`HoGUOyWBpP)@Hc38Gv9l2KixhxM8}YR;_Gq5 zh>Er(E2Kzau5}W)a4L_f)hiuUS{cMs+Iw3a-%8&7itLFv{`4npoRK>jSE0cd1PGa$ zj_g%d3>ajH%dA5%d-NT>lq$opdmD_cF(llMH;M#lJ>>R0jWEaCa43B&-*u?|#E3KB zJ%_h5%%CjPwJndb0qsXf>^67mn|10ntWe6vu9%N_1@u7*lzar$LB!%n>meJJ)KOuy zMXG`blOfnit<#xCslb>Zlt{g?hooF;kTuB$oR{gunv1t3scQ(=(GE;>WeeiqvXqb? zmgOlV#_7A}?+7wrOeVQ0ToJ%2aE(ZNj^wK{2`SgNc`}dPE`$LPGjTSsxAA42RH8J(DZEIGxX}D zq0(1R2j+%7FKcq}xx|lI=f{PR=}nu2H^$wPQ`)j+U%wqv&E`e;Atr^aKYOo(bAPPe%CRc)O-mSs zyV9R*a1~SRx(#2ACveNvyr|fwy`gPO1KpE~-P}O^<#lCfSNkyd_-)qXq&Qda4gCG0 z506I;P9t7(W^|5!P-(sHv3!S&J}{!Us5A1&zCf`m$i3$NYP=+_hXwYej_uP<>xzKQ z6##_jH5cM6D&H;ex%3C~&z{Fdc#D%l<8KC|kO*_iDjYuu1!4%Z5u0x(I48^lVl<AY& zxpL!y9lkb+aK?4eZ~EE=Sv`Nd@~Q5J1_L?vF%`oVY4m*Q{jvXh*7>H~yh!{XNG{7B zz3CZWM;UQTm>A0vX^;9IZPsb@*UlB%kmybs^|QG2yG>RxjfQ@G^|ygFUc%fy4TCMh z1E~vM(Sko4h6(v_%(YKRrN}~y47*8`6#`dZtl4=yu*66P0bv{!&t<`?~I~*CMtQC7t zEm?=;GP`%$Llowg58j#a&T0>kr0=}DGI~>FTISo@UH{zYVG}ZE4Tv%dJtVn;L77qU z9?2i15H)&%W8y4!p~fzjMT%^@rt*fymLpk`wx&5RPA^AZkg+_RtaWlB5^6eW9)zoK z8Py*t#y(vI(1%IXAL++R;23puFn8Uf4f{Q$oYFAL8R)2jsUJFWLiet9)WLf^?H$}* z_}b{g(|57!%yLrame5g>Gc@7VyVg)i$o$Tt=GI+4B^3duN226tsX4wt6P{uo_2|-C zCw=5eo1)Lwp>}BKg7BI$yve`?BT9eqlrX481d1i_!~l(n@X57H1m?6?dk;+zCK zB6I?i5hrUSLfi@MG9r?io9F`ijz*m<$(&VYWL?5l_`=MXV%ZddsBG4nYF{q>Q6}^- zmRQ6itEItmC$ZZ?Zk{@UxIvDuM+m!2)-=Emcu2eIZHZwgNvBq_d4wHjm8^)k@Trn( z`D$In`>r9KUhX=)Gj3s;^7mLEf`^wQG%8-&?UE`ds{{mHiQ2uaDhZFOq{=Iu`k1Fv zPPwDr3*Q|wOU_bS^4dY*{*s0Cv*!NO^{MpD?-%`7nog?Q0>(^UQ9+y$l_-9wsD&G~ zI(5{R<%a^C0OALnXVNCmRv=>=Of6Zj-A`n5ZqY4Scc|Sd>0AltUp+RZ(S6PKwb)B%G?6b< zY<00}XtV3$X^h|lM7w{il4L#Vsn=l50L>Zx`gh4a*RigY`@#-y-Z<%=7RgnEUn8v4 zMlK;@0+#4ep;l3(ri$8VuqF8LYkX=v25DN<5V7l3f{P^u)$@9c5N@Xs(?fqk>b!B1 zK`BN*gO?uzc<2TCpTdsrJA95Rd+5VaqV4TAeE2ra*rcz;bA09Li}u!Z5IONJ0oNOX zN>vKHqEjE#d0)lH^{HAuj3+TTZS&hwsJ$s7N`qDv@q;a)fs0y<@ugPY z-AcpG^E>cu)g57IiMm^LP3E>2i+CT~h{rTb#*11cm$?ry@~KFN{yc}}8J2Rli~nf` zW(y1KKj#Tnnl(_|x}tK9#l>eavJx$Yw-@>J_$7)8f9HvWY_zH#aXrdWx4iR$OS=+f z3+0Kl`HLdqmN(GnaT*bC5(>|ALL>r}Cvyn|ixms8wEe#5=61%UgRJbl+ zFKva-e75|?Exz=}@L-#)aa*mz;QXuEuOD?k*2*-bz;}epkLw!t1VjB>aF=hNeP{v| zRk&+Vnc*5=x84t(NUAg~qgLp5o-du)&y>X38@x1}Oji)uMj@aa_&~dt!~1(2W=yM6 z*)3T_`}+`1@G+r-MqJ*SEy#r6)u*9b;oB>JeQN-PLTx-!t1U|Kq~;1Lro63T3YZDt6b-bkao{lHwRN3$fyNc;6p3j~G-(P*pUs8SbG+T7o74!R7pF^t`glPj7MS|rz zLoeBX-b|!-^Jab?+zTzk`ug&Hj_ z8!b}D|R*;UHBr9&sIFC#1ES6Lx$MUEp)Gk+XrZUw%VM^D`@l{}n@ zdcr9y=DParcJcLD-OH(v+BWhkzVMu8%^bp(-D6Fum=&&Iui!!ML_AfC?&{>q*DZ%H zS3K?KA1>=d28Fb{K{h7R)=evU`xc0lIjZc-9*t*b$rX3o6K08>bU)5NkKFC+Aa1`s z{lQT-;(hqh=ceoU2jcpt#=|LD_U{G-pQFzruNg^0$YNqUndP+zY#|j7{L|MoZD0|3 zt7GMCurI2xZxaB`RsiF_GDJFD;-3?gui|v^fXdoITb1}RDEK1o`W?UZc%FXHcsIxw88>k6GZZ+IN1F|2rM^5z=B}%gkZ82cR~~7u=`P6lc10u zMmeL#U=gg2XbTlY(eq^RXyOT}PXxYiL850N>GeoRZ5jF z1uKkxEnFETg3nLZnh06}g&pNk8bKoz+ahw;A|y@;YM=X_ZQ$p(hO?s3w335ML2&dvf~$De(cQRL7v$X1 z>779Jd}dOebN~z;}{3;V}hN-h)_}0if!m!3dR9_AnMkC76Ul z?1>Vts^X@lm<+L_!%`A>43VG5yOKi;uz4B780l{kk75~MRf*O&mhY6_;iVE^Rtb4v zt3SgAF9amE$;bc~i4MXLVM}~f17C~1q&IJf!?6>mK#8!L6a(x;RS^hJSRyTY@;xlX zTZB05P3jvZrYDdjDX>6OWy+I_R2lL#k{iNAGV!moOjm1AIV;HJ7)7NAm>?A_tc|C% zi%&brly2xNvliSxmrCo5%`^8{*eS(XQ39=>$j5;7|gDtZWTNKyBIAby%5*CKa8*-xwv>3v_ z{g$Rq%^6Eg^pej+dkcM`1jrSqf^Sn4FTz+2GpR*#WKpOqdE?sLAoMu#poCmSnCa+f z_AQnZ_iY|QvJxR>jD>7mD3jhoNz|)yZxeB465HV0q^HOtFe2?%1T*n60~V^2EE0UG{?l!25}=!kZTagdHr#l zHnQS_uEHZN>HG8*ApL3o-T5h@C@Mqde96%XYS>%Mdu{YrnnIF1O!@r4A{fo8M|KCI_8FuTM9_7BF-;8EIoWBK8H`H`qKc)U#IgpmDdSy?~LaazUg zZTYkT`$Gd!sZKNp22H7T1gBgji83IALtUDvgfK@z^P|U@hq_!8TTD?&6&6^X5*&}3 zKjb)@|Hl5}QAF!${3@0kwF;f)DXxks&y{Fe}pZi{58Ec>mKvb&SjA_^v@_}T+Pk-ussp>&2?_M;0 zcL72NSr0M!;3iE14k+~)HAxjZ{VH@>7BIx7 zqCAl|rPl*VHKUD{;c4Mf#^RZ>0?2%E2QiuK9`qa`LcStbk6)zvzAb>+0vf#&sVS^P ziK_rN23PkJH7GS~{s9)8nz0W6Db4B+UfzM(94JUB<)dO?v=iEWAmN;1KoSdxn-HyPej{;e#zB$9^g~k_QZz&WH)6opG(3j<*ctM30VwI(-X}ATq#117)A6N&WA7mB4-6T3HdyeHhU8k8PlF zo}Vdq(QH+{*^$V9L@wc2jrUp~&KN=IVt@Hvf8}6503N6{8TjrsP!R(UuAErHN z$}N+sOOxvNlbSSB+7eT`rc=l$r9tMDQOlIc(v;c#lsV0`rNp$A>GUh_X`9UHw=L6l zOVbYb)1UlC9VKR5O=k$9Gwzu)o-H#3EqGq{GZ-*DUy0d3)7ju9k`Laq;VrY;W#1$1 zXCyCX<0R%1Oy`s`XOq;X3Nz+1yvM#|jC~oMgINgDaLrrd6YS?8ujlhcOY_?PgY%Ju zDfYvqQRF1}6A6d&w0>5M_gsZT6W_`f=q(5pI2XI4M%ug=`+reWG%sqw7l&z<#-qkY zC6=bWmoQD2W?PmP?}z6ROG~RX%d2-w>!!=RV`g>s%Ym@v{iWrj``*L*Yi4ceL}qKG4rGoz zjS1itvgI|}WfJO#H6s)}hELuHXQX0 zXy!sf)P`1O#LuA(S#DV=vrWaUO@60M<>gJ&drX8B=G`xMNI{$%t#QGJL`H!6% z&@Kz!`26CIe-_}I2@KZ^&18G;QjG0JKO0V8GIFEd+sN1rbjD(`+spVg_SJy?JMZ4d z%w^kO_`geMA|A+Mm-l#iH{adwxU}q-J|lkrjH3G40Ux{ltI@~hoL*CgNaYSW{e}TF8QYy89v0Sfj95)Kqu8f|-k*d>JErveB!r5}0d!0$N$S3oPAF2-h*+AWODu&5sD-s!pzxzMce%NYI52 zFUGekCGdN^+5tZRJ9Lzr`cl_SI6q<4S85BX4*Y=Uajp?A@tjZ$%AV|V6 zjT(gCjKm#ZKw|U)1JLn%x=DhmqE0WA#>)W82C`bdL@E zcMpRy=}!@q*4Z-XXkHbHB(LqNTF7lPPP?EU8N5mlc2-|&SU>;Mq~_cYv>;7t0%BAy zL&7_D=Q5%joZ2J8P4UCx%!rp7B6|Sl#ki{E!o`H90@v?JUHzq+G71td@E11X?cYda z&3{*$e_s%Zv3DDJNq87N&76<>+QNd*SmpDQ11ICwryIM~yq@M0bq`Uq_Zj4|6v@HvMY2 zqCGll`g3$NGna?XW%0oD59>0Loih5LCbiku6-blX`(y7*Ebk`|*PF@j9&Y}c)XtR2 zUW~6F)sJu)4RYur?libGMlTT8FGog4jIjF`wW251j(tH)y$DJ&so3I3dbEXLiM+a~ zCboAz_f(+exT~1Z#K-_@&d`V3t7g3Y<3M^Qi{H8iKXJ3lZCDnwagWJ<{9lZ{XH*mV zx_3*5B$Ood&_gdGARUyBbZOF6Kzc`!ju1MbCG-xVH<3=L(t8IHu+arn7R3UHhoYCV78N7P^XK@PNtL}F6)z@YN0ZO5 z&Oru=*ORoTj1d^&$(xV8<$*bCaJkK(+Jw~7!w|-etFdW9qAkmd7#9R?v!)y;i7VPO zI*yOteBv8pMW;m?&<=H{d(5c*^r75y!$|rsr4B+S32*@3%mz)S;uRQRyXxrEu{urj z8~(s}b7I0E_%ywQvO?glrKYFDG(y!FVNX1MpSIU2{F-)#Ct0`E@g zm|RYT??4o)2dO`7(hNpCW?T`7!AedVMR`0X<-}G*`3dPeFc7m|3lLE#`;e+13Ryh` z)!SBR_CKMZa!t}@Zq2(doL!jYnEJrd;l8xxh&0HN-~CGs>syO)f3xP|3J;eXiFH{w zepr@`c(~vW*YJPSvhwR|Tjy@<8cxD9iRd5(nxP!amJ?NfEigjgLrCRQ-OR(HuVwk5 zDOsp?fumbM6RNHK{_xHld`azyp>68Hkr%X^;$VdP3IgEx7Fi$AGD_3q3-ETB6UFU?MG}BdHjr|XwrJ1_;AfbP{s6QUj8<*~G|1{lXiQfAnSZFlvgrWCX~7 z7Uwz7dXceJ5VZe~6*Fw;qblm+gayJeQEd2Qo zHg)|Y-7j0@b$baVHNF6Wj7VUz*-zf*V(!(eR|oJ^(ZlafHHKCv@do)W!QFw;gJKDh zrOX|W^JG25{N8heq;M@U%@M3iGX7U4 z3WdmPB(eCl;7Yqw)u{TwLQ7KNw3hYH*^nH9Vm7b7oqg66o_!>++en`1bT6clyXr4< z|L~u88BemAiJ6$9?cnR#P+YSSF}DPm2?3Qw&{-fD0uf9(25oXC;Ct=hq0$^Jv3(#H+_Pd;MA>%@Le-W?&1Jx3*L^<92XOF*sket!(V$ak~v= z7AGwJLyXOMtPfM%6WKUFmsm%OxWJsar}56Zoc1yO&Pld$ID0By6p<6yT+oM9wuDg7 zkcb;6OUPhIsnk!r>jOZy65XL7f)Nl2qKFudi3K-^%*En^Es)R7sXp2wYpdh335ak6 z_+kx_E*2T7mDnDb7#JAWE=$O{K_nVM9I{IU-XO}ug1WR4+ZJM1=BeylsFpX9oX%rC zEm+3(ljg=_y{cn7w5ZP)5wZoJZC9}%M9E*?IhjO-1NS(VnMVGWR6M%6xaMr zM?Rc1a4^>!enKbk<0!CmXIMh1^nGonzm#c+H)AZ$A)HcajsvnutyHuTMvsVj#MeBT zVwjv3vxir-vX8kx%hE&%rjPD2M7d_7m~)+4d~5fp(e1v;o#~>MOfnbQh1y^lOSd$+ zyx47lc$K{B+&qt`X%S)}wHJA)30bi{Gk$R{QbG%sFMmTDku(uGEfT;=oVgg&8!hpp`o~FrfF~u;C}aD)pfsn>+!7ohPnwz0?lC-( zn}do+cxkcd=%UDiTPEOw#wWq3f03a!m*%pw`066<=2K_{AJhrzMJZ0mybGj`;2CrU ze_bIZ%LUUmQdvvTyyQtCL*xgcX@~|~zq?YQ(*#n`qGOJ5Awn^L9f!cWicnsuNLQ)o zda2lDsRXP{Qoc;us!SH91TZT0vzNPbSY}Uj!?4z|sXa5UZ9@ z1rM&Vn;i-eA{mRtT=~Y~l$nKx=- zMz?_m$is}eDwH|y{cOxYRJ*az`h|2*`nFJDn#>v6f+5t;;LU1v9h`PlrtVnqKzjv( z$=Mp5Iz@j&*SXOuQyjrv$xjQ*!XiI|lR^ZAbRigbT7kEnV<(-JiL`?G)Q8i=PDxLVAhpde?;om+J(Ra59|kZ+oRc*iDQx4HEKBx9>|Gi)k0+^V>dF`#ukb zSEnJb80=r23$tGfy}ImvwT9rIk*jdBU31%H& zEExE?$v|E`z}XDELF{MBWXGUJZ(|4oN5uN!koahYraW49WKl zDZU%Jb2X&QI;^TVtZp-`89J<0Fs#!vtoLr%;A+^Ab;MY4#MEZQEOf-8V8p6t#72)f zbA7~?b<|#Q)M1(#ZZqmqFzVJb>hW&W>uQt@LFpsH_^l3`A~qISFqU967OXegvo;pN zIv%Mw9*zG$5jq}QFQ$)ktB?OOnq-4brW^%5O$%Y1a4YD|`Z79^JK;vj<~%)Nc{Nd@ zI9aYZ9BMPEQ=h|AK%_fAP_0MXz&ZKmGY#O^B%at|3#LrO3%lM;^<7PQ50D-bOxx6x z3F%B*dXSGLO`E*~;>4z>S!dMHWQ#U4E0hDXq5l_Rw--9Q&pIrUGplCP{l#YJ^VO{K zm)TRBIbn4B4-ZDVX;@CdoQwwt^_8H>#vI8Dwq~1oio$td?>uVizlhxe-Gjf0UEu<( zcY*c&0{ia;&cBJ>!%Z5T${%8<37Hp4euam2Vr)wix0fUnA1q0SEy>PI1m!PPex{0_ z`kUA#JE#@T^$<>I6fWP=pVN6UU-fR;Fl^x~!|T|$oKM6kp4c{<_P)M{SawaCvzdWr zhO*Ezt~kA4{221z#LkE9&6B^0oqyPyz`u!I==(R%f4_-fTaEmi*clK~l|`&F`+~lS zuf!HI|0kbe$Mczw|K>BY^KYdiaad35_(sgo|3~S5TEO#vxotxQ^be2o)OZ`yrnH$mt%H z&m$!bk6#XVzWRHijTIlICxQq2{8PHmm8ey2EL88#{U=5;5~Cq|&tsx$ZY?hWbxFw|H465HGRTTb^ut7aEX>nX^aWovfem|>W* z=8$8Tx3;6^(tf~~Y1Jp_oC69nDO1{2KBJQjwIBXe96`2Tr4~tz1j`aLTM5YSN*=nD z$=wz@DA(RnKd8twyDy-6%PX4W?$_J0_Y}&r+zxBLmY4mBk>pX=Hs;~v59`0qySdjl zb=7{Z#f}Dle%bZ|dDzemOcZMDhv^5hmTdl49^qpp1nv*moq-n6i2FIz{ivD?VbOCzGt%)2CCR4};5;e~j$dw)}9( zxs}R$^d)sp>88M+qVVst1-YB?tqB@3U;UPJ`SH?NQ~$Zb_Bnw zZmT6(5JZwHUYU^4^y7|+kB1rOhCsy`#F1pMh(@dwo#svaH#GPD{r%s3=J(&F`~T)M zmtQ)RdZnsmBxkwfQl98u-Rz9QDL>e!Rt209sE~>nB8bdviOBUz2)Q`p?9b=57Ogz7P40* zK+E$1NbP*29OV`jF2F?%zJiKV)q_*ttsdr+Z;Ih1v8O8@AAne$MBi?GuU1V+OlwHU z)t*RWWP>3DS@g?>YL&s>jBDO$#4w#%5E7bir21!UTLY%b23jk|?p&UvMieZ|kui^} z!cI{*D|$HN#JC19g>xA@K$S!g<@I`>LF*(=xS&5yp>1F0!9G!GzviTI-YLFxUm^5A z7gd&%H;|ib&R9ZLo7O~Lk0Jk5Qvez+0Z$=GzHzo%Z0R(brT1ba*A8R zGQLt@v?TD2@z1kk3=oTYcGORHi*5^OE;vRbN=V(9RoZ}C9pRlOFuP?l!luaS!6oWz z@g?vKSbR=sMqr)vAZGxUL?16aZHOEqV<1nf_(Crd0_eXUYBIBnrkxCW^9l!dV~i1q zmeOgoYz<~;wYU-U=)pXFuvi`4gV#XWC&E3#euN!3fiHI1KJ?)>Z6MkGK`4^YB7A>^ z^4iDqe*N1w95!}*97Ll|Bz5sJ|#-0i>ca4{mWb8HrfP)GnE}hK> zC(@*>N!TI!>BNU)mqU~r=Y-UYU36OnTja_XL}5))0s$F=fZJ_I1rM2}dWxSMG4b_5 zo^jR2bcjKn8dt{3%Tx}|Pf)?eQQqef%eB}cN~fG!=I7vo2jv6`jcqzy5utIsHiNHL zv=L^yB_AuU&Vl-DPw=-&L?aIg>+YY3WRbHx`eH*!zOv72mNgx#K(L8}%3_I1ZI0Iz z{4l*bYnoM>R`YQy%WOrV0!0CfBH;v=(9$wUF-bE0Z{~H5B%hJOOG%f)b2UGjHcc+< zuFLQH0lu+CW4!+22a(dtLXiyL4Ed>C7?T57U_|6T#>)q`(!n<~4P32l3K<^$lz&72 zUaB$wNn-2_2`g7@O>_2)$L|^9QPIt%?n?cyZq~p4xNX}zX1;nU2+v$b6}QfiQo9#S zS)R$7%N0bGJ&8KAoVGXbTKf7m`0cL`OCMsMZN&ZM%+>vofF`j0GqZOfIK) zl;haPEKiAUoO~*^p8MmrWtx*6tumT(^|9IJq`@z$3{FuUEjt3ZdqI;kjnTmdKcFrn z=c0aNak_?Wtgu$Hq!qZ*;>61)8Pz{{8b4iZRvXZ`{?Od_Xm|& zd%2Etj&}(PDn5oebUi;fI9(`wpBr)yb(c_>Z`|;HP=bj4^7}MrF0?pM7zGvKIttA0 z-n;uUpyRLK91Q2=)DOkLgCL%hO8&4CdzxG8Q+-Dy=yysz=^l9$507~demAl`rjF)W z^S5KGT)&wXxFzvRg4UKqmD2~B0OHYJy$h{mCtA3htd6l_8OIf+N-TYNN@BbXr$Psd zpmtf#;Yw!rAkx1>-^_D+6Kgl<+7vH`E0RBzjRfy?K!`2Esl-DQ>}+UGSSk1MN5(?b zs}WEN1e6KEV1Zx?M8I+otXKrQETX-ViBdr5yDHS@mVAyno%I>(J)=mN-6N?q?)D+0 z*|8vIs7q@`l!Cp@1&W5qmz*mnWS9g(Mv7m}MrvtAvfS5`SLK#UyJhv*NPaNdVBB3^ zOP81)@-v$&<_F~67M#MxXURXRZ%+_FL|a1+b&WTPkbz|Wv=ZioxL0$KjTn28vK5G(bmhvY#|S3Qc4V-H;i{ z9HSu(A^t}RFO@}>TOca~6FkLlg6(K>b2wIhLhfuw*9m~@I%w_2rJS|suACz{PxMJJ z;SMf3$c2#HY9Sg#Smr5`K}BSGHEDKVh!3Mzy$^9b2U`ai4HMpf%?sH8N6Vq5ZXQ2} zay{$O0=JsTZodIfRzv1iQ;sN-r>cbu(jY3T(6R_{11j|rn|d{#3gJ>#JE!TifRg#r z`Z7U|j-{Cgj4fB6r^O)`oQTRK|r92T&k+1wZ}Zi9IW z4p~j36}C))?}0xAM&G1I6Zys+{A3@%eZzMg(Ik*`=982g7ts2xG<8>cNdTmMh16CP zw)m4J);xn_Gu?0v+|ZVi_cViZlX@MI&L@|tGZ6o9g3&?{G75os%p*UG3(+!Xc^qU) z0*pGgWd+u<{4BE{15C=&vWV@%j4iXD2SEZZvRTA4zF>t!4k9CSbNC=R$WA7$)f|)@ zG>JL)`H9GZW^PUpt2`!-BEge(AeSa58l1)$olE~AJGZ(MlAfDazm}KNl-IzV-<-?T zD3{-U0Sybv=P=6eTFd7{=QkVx3(jc_OLF^y3dVzIKl-6Ya|>oK3WS>yX8?uEmW7%F z`761F>j}`+&cY8Dg-d{mVjXxs62+w3d0bLVamh6x-hKk z81_pHC#)1MU&>=u$`@QJkXI_yRVuPxieFTT!OA4$%OtJJq=U<3^UCCPPy}6NcP`76 zVdbjw<$w50aCv=hiB{L2MU}y2Im9Z$P`<*{s=_R|!XmH2Dlc!UvEp!{>`a^Tp?syo zdX}wKrAuC=TUVvWdZpK8r4Ov?2{I3QtI8h<`59CdoENUsRrMTiIJl^4SOexit&Fs) zG!CxL(XLL#rB@^IdOa+#%GZ`#)m8@AR_E2$ zcGcFe*EU?%;;X>T@^!6Nb?w1**u1)~uDYJ}y1vW0epvm#7FAsy-L}R7&1*RPXHj+8a148SD*y7E z)yp5jFVFFds;-w;>o1#B8m_xA_Yf}#e_;r$8!7Vfh3!Vlw~f@l8o?}0P=zKs>n4Vf zCZ_x*Sa%a^h)3{Z6DLbET%p;gOXlHP6^{blkD7YH&joyMo3A^YTisrWS+~F~th1zr ziz&0TE}x+Es)JRzgi7h+Kd(2Os(6@LfS0y+pN0V zY~Hp#{MBa5(r&NN?vU7|Zr$#Z-|p7k?g(q}0LlI5W0Mj8*xdW~V^iz@j|p9j?SGvE z|1$>oZxj0eJO}UD$d5Ry$-`ipdwGbmQ*pL1YuhA6Z8hWGYu zTYsMeKT4c0@r>frUsv`G|9cGJxe>ld#Nze$V^ikk!@%am3&9WyQk3b&o_(4hGw?6v>Uvh>6w%zt zF5~mb9@j5}`*iekc1ShmI%vB^OktpyWAp`^Jr?xWJ-rpG|M4C|q}**`i2{EbUeA8mLzw zZS+@+yokRB8m2mA{nNy8$_2l`o6vpWmiG(%Rs@~ZT!nUd5Ry6B_!fn^qzL=GF ztFK52OAF3U38)5NCse%RX9I)zm_~WNvrD8oz_;bah=bS>ewQA&({`8x50}vP9AwKB2c^03NPA9^5{ z3efMMC!M}3-~l7(8ByuZYJ;Kf~id*hA!eg5es?)vKIU;kGV`qmr_ zxJgQ1Z3Nyr;R*dApmHXSe{8yy5QQEas|=r|_Uw%C@rl@}7*r3^bs5HJ1z`*va!`!T zs2Is6J5WH5YVkKQA=!5rEu5d0S`al`Se2kuyxcvj0J^XMu%kgJ10Jcqv0i zj>cc*LmV3Caiq4lXr!cSv{5?v$0k$Ntt^PXm!1jhnijvz`bmm+c^Uge^q6AaNm5Ap zHY@Q#wF>l9KbGX3GW%v!IOirAsc($(@iVC4(HGjciSf5>j3_ni|4J{((7w5Wx3Kkd zLdlxQ0!2FMqHmL07}_zImY&lEC?5-M#Iu>yqveYXQD}#K4O)-{Qcog=JzNZ(dlyl#}wth-iZ$5aTgKxkp&7e8-oRLY}KwT5yD3UPvQB zqP0v5u^}y>ofc+aUbccIp$2Y}@GBof_d186(OW6-j6s-g!~xABF}3scV-|C!Nt!!$ zRQ&yP6bFLjBvbMkUp$DxN+>tg+g%`^em;St^8uyC+SjeBjRkTC`=b6$%fcddG}ew1}xUZF90v71y(#_tL*FP+|B&*$uh) zsjH+yb@Qvzb6w*S``6W~gfvD8&5OEpLxAoLLdV}_i^35B{9<7T{L}r!U#@K%ar0mA zxwIxLfK$mChQBCDfHHI?P1`F12iOeD%?HyY1yUW=?vh^7uvUNLSS1CjfzZYoEB>|D z0>dH1Q;V5=IaJ@y;~u)_bC&y^@9?QN`9BOi6eDhkY z^Tn9lW|NXX#9@-q(Su2vB`vH+HelauLYG-6Nn2t!IQU{pBX>;H7PKdkFDG7E3d}Vk z;`Qvro6vpyTcoyFKk3HH)GM7=&Cd6;zJaPOB)ul4W=|HnudqT)QjkE%|To9jnx)vkzQH z1+F>{^Y_~ggkwv?`b0;fKnshMXS=WKIwL_|t{&)Cc|inwlg?$d%T4#My*K!@fY-j} zh-e5GC)4=TVn)U#FHfmn6)O2@JKAPUoe3_YLv>lj8!>OqmXBOhd3OVKlqoDD%M0qB zG4hU7J`*t%V)S77jPjJ>+W@0o&1V6UAaLLayT%(;#TD=5p(F=#@wlPoChF7|+xW+( z(#05UWy(vc9k#C@ekyYwp>`x2wto#wD;}$%?6E{eYyMjEIHHAHO1o^T)aEH0(ltAQ zXGvr=0<(#+s8SjSW658%Gn*2rU1H_KdREF&m7LF@Tp9jEjh3_|8VTr<;vWhA06Idxh}Y0Z*Z2lbMY}c ze&JybYn;4lQUcu`H3t{cdHPe5UMjwnbbUVe>iXv#?)sM!F3Uv5U553;hP+{#J+}&X zgApyNESLhL%Y$ZZKt$kBUQBjg*r?#}0sY0wTXZI2mTHo6kjVT~Q4%|71F@szNob8J z*csw~U=9HXLP6)W5&eNV1JCD;JWZA?ZMbf}DnlqkSizeS-OheDP5?I;T!<}rRRv&Q zm7}$CAeI@l8~q_{gd`u;X#28ZYBGFw5>Y1AOcvwxR1&cwCo!55V2}9#5~Ema@o@4J zet5Zj1aF)WJe1KG2-wooXphBk-Jwy5b77*Fw}|J$!~)PP)&k&uoDBF2B0fzvK?)Q9 z4#Ii{=E%S%B-$ms+0-mxLLxU4O7V})9NOoB$XYD2eihkpj%;K~6To zmZ~AURulVjzyN#0*7L;SYGjW^(s*^^Ku*%MMSMl0J=1E^qKm$diyxa~&{9q^iCti+ zMb!Fv@&*t4$b{U+rZ~3i?T;rP$)(5>dZm@kc~OtN500U zewK75kVBDJqR1wg;#QN`0#THm@}M;oc!KS~Hwj{i-r>4O)b?TPITi}bR75- zbzBxY(E_5YW~8$nrSlj1Y}+@F0)M`+RX0b%O=5O&#YwQ2y!~ifK2xx zA#330avagj*>Sm$m25pvIdFC^xa%3{sjCl(ilV@YzqgpGn@vNL2QAT-ELR-rTmj@TxQRPuk)sk34o59Djkdp;w zOIUH_$$k4Sh#jnyN~MeoQc0u(aRRbG40cmO3)%^yNC~OkophC2Zu?k)8=rw-ZsjW6 zkV`I<7J%<90blFW>bT%cXI#R~p~d1`z^bm{G^))EMqj}I`KKm%>09Gfe=Ue_o7Z3_ z!FNn3`=8*ML1mCzjT|A>(MBy~AhNHy`cYdIM-aI75Z$E%>us&DDb4EENlIp_9|p4Z z)ym6Q)LSBIC%Yii>-AhG@%6L`aEao27`?ny^7NR&n>?5o#S0CtR8kehhE>BJ67Mu` zxLe)uMV>Wq{m)}FPw?-@<~r?M@Sn$KwH=kjhQ@NBNus+&S*1m>4lMtxMKzIjOum&FQK;z-xs%_jrvTM`+qwg2GtQ?~ zQD}RVRX2}lHFI~m*WLCo#L4Pc+n>i~2;FtW1E6&~xm{iJIAz#En_C_HJ*>k|;a*ov z+_U_S;O-7i!j4dPBw-^&u#tqin-+h{^KqYsG7fuY8k#hPx({lIc?&`R!X91*WP#Yg zhED1Eoq|T4ZlJCrYr0k0F0zY;%KWaZ5NvIJEA(wwBNMg}#P~tExkaJPyt+#xq}!3D zxv0AvGt^bR(AfiO8{zD-|E%*$xJN0!d*oLSt3=1VLhrJ5?@CDTY6$K46kB*+Z|y*j zChl`yFsM&0zx{&(>s|=gaCcj$U!Q|+U)_lnrls%8=f20FSHdCf8fRV4+`Atoz7o!V zIf!U`vtYdAPQusSyF!>=%Z=J_eFg4$dCf9F@wS()zKJ1p0H!ctDBKUb8pze9A*UD| zzU(Iu?koGua}!<4XjehrR8!nl&W!|{c9j^oK{EVw#d^w(CQAUpLo|psX1zgLqi*wq zNPfaH7p@co9WE;1FcGGb+EHMz&R%M6f8$9DLYYhz%mr_CDJ$)JYuT{6oH0McWdemtL10b`X4-)HowP!JL z^JVeQVj^Zyvu>%AczGbv?w#J!>d=xb8y(Axywq)0{lew$L!dLo>ukb#Q_onf-j{d6 zUYmIw|6$ER3xw#aW&`v?{WL2oco*tNYtpc@1 ziU}hXK2U>F3TguW?Mfxnef*B7)?~?F< z=V|)2URc`LFC-;~SGfy6674uM62f_9+6PYXpZ!t4z0soIUH|$gnrpru)`PF!E#0IX zXbHJUJ6Og?bCnv0e&JZ*A3q-3T_LVX1Z*QDc+Ec|C^Q{2Z4M-vK8BOa<$P45>3qBy z!@y?&j)wgi7I=k`Z^iSB@|3FZXI0~e1?~G=|409r$k8@cnap)NRh8j?RPT1u4K@F+ z-UZQ0zP+MN$+YnYWJx|W1L)`2(qjm6W5Xg%AxxI!XrB~}+!F8!Lpupe1#_mOYINt2 zlV;cJi*!ONipwnF2Nlxu0=Q#4qY}~y@p-mbGoeR}B;q>hkGM4W=5vU?4hpI{8M+BiGmELeiS;%&CEj9OhbAAGUsJQTFUK4!=a#y;k&Uc6g0F^!k*nyorxb$)4}& zqMImBK)sCVDRsN2nZXAC=gO6O$vXGc%WcEt3*QuuMH zF=hQxzy0g*<8%tIemk2M2pDM^Y?8KEieWuvO^;G0r}8sUWxT;5p#l=S$FR@qwUFOEcr;_mN zdjK5eVu!NjpZzmyo}ujfecwVe4Ibw7`?jsWi)O*TOEkFQR~xpAo{ zBsXQ}&gN-J{@1Xe_vfND$Mf&o;ojFjXbV4vU6|J?g@qGglu*CEB`PVM|9tcE)y3{# zuatg)j8siz10WwRjv}5I(-Wcnx)^$gIE%I3o9U?ZkTR%y>*pGnyWQt@35-&+swpv5 zr=SnEKxy+(Y6I_$tA2|BM)O?Wjf%ea zueh3?!GSs#u#lD{1xr6dty&VyC*3nnbB#wdO|gPXhUrkMMN(<-vEZYm*9$(9dq4;cy!5XdmbOd$MjF!yE#tePoVC(|rFzVg`5$jO1T2=K7K$UL%@eWgl zh7ii_UX3j@zd3P2k1R@Ed|OErAt)6VNxpPJ%f*tLX)FmPFIz`4kxH}G$J3T zPtQnHs_tUj%RL(<@+pG`=&yrNQfXc#WS!MP(YNLbb7{GFOvmI`AlPWay9no&9Mwy2Va+OJmd6>q(Id2mUMZXz67XxIEnIGj68HumRtfnwj)|daf#r@3Trv!Rh!YX z;pQMI(yzg|f^QnQBOJWn}gIV*!qGcB*by@uZ$r_fMv zPam=5sTtRAc#_@y4nwKc$~EyxJvcwz+9Rr!5@EsvC`+GOtZD`v3OqH;M}1PwmLT?x z34Z2o`F58?Y?T*?UKK{k*OD8YIL?Oqje<|Qs)-bG^Y8YcKbC|VaSL)bXw*NIjE}BY zC+$P(Cc@QmMUeMACu|uWBAGBVKzZC~Wj=v(~5^sD`51A$G^i=gXl1pO}u4C;qP@kocj5u>>zc?SG;Tw-? z`LZIK7&w^IeVVbZ;v{;neA?+6Oy}=Aog2@L8(o_aEYR@ihYtL#d^-D7Mf4r|bH&{A zlSKA+MRA!w*@owX4O&a_tVdJfasidn`j^cKzkrKF!ktWf_3oTTiuA`~b@z7V-LLcq zX2Yf(B-qFV zqXf|Y6e(EW6Fr%Uui8bk2rJ0|N)7Ko`{W7>?gd=lFEyBru?-b{Sv2!nJiVg+QTgYo zwP1}#KX4#=W#cF6|&fIJ6T&c@J;-i;xvcJ>mgW}(uDLz>(4~x z?PEkom$F!cg6!*CC!X2AK7KMl^_bj&2RUzNs=kMa1-!RdxYl}Sv~|=ho=)c7 zE#JubZuknJx9-iYD@i-?a=%SAkT{O5O58m`26%P!_= zfSvcrKX~Y-w=Ddng^wN_#5Cp?<5v4{svEaOUTqkTtnuskNY$dJf_0vWW-oL=&aDOe z?)*NV3y7^KrDZ93(h$EGqLd>k z@fn&aWoWuGSZ$on%~n`?Fe)uJO0$X5fGL_IEmG(-)M%3yU?L?S$fL3!t;9u2qyl1r z;1fZVx(FaH0tAH6Ff>sC$GKm`(VDk`_Zq1i9#b1ii5Lnn+^ME_i-#Cnz)hJLEP26t z&xGs+Vr9@E@+MMz>&Fj=hy^UfC23LuRT6$03l7^R@Nt3Y2jV%K$n^;mD7X@6n?O2P zIKqWaxGj#%H%@23xy6@)ge%b=6Q9*c&naNyjg`MBp|6T(E2Us4sHX3v;Hj}-(9nw0 zA!KS4U`esy83}xV9Hk~&pdfBa{3cE|ZJbocZ5qM=b=#+IScN;IVE;1QVyZ1l1z;2VA`N#hQH#O&i5JqisQ4ark}yW2LkB{4;WUmQ>R){ zo^}M(g8E=NhO0QfZ2>b}AQOE88>u$$tyP{{WfnF$^s^RptWlc&B7)Bq!Y#)lGyx9R zg2Tt*GK{9S~7juUwYv-th^z!-vv3z{T37GGwFh9LdKo3uLv$sS8G znYj=*<*WxMB>tG30~Mq9 z9O*z9J9$pqH$gkJs@k)b_E?1s=Ti`)lDCB}n3O9d<0@Rfps$Nyo@ZuX5%gH|DHLBQ zyo*btmRf^Pn{&Sfz)iBaH&Fby6F7I75kV91eoH}{AjX3UgxqOSvT9qs)YirSR#z_$-8Ph^VmmZZ}KDASJ3>TV`VU+N|;2w3ZmKwQ|(9vX<4iesWAO`4BaY46iYw{;XY6^SF`Vjr#BoF#Vvqw^I6^ z4x-VGn-$r#jqq8v)Fs!2{5DxC~1GNDNy!J?Fl^jkvQxu zj{d6-H(#r{uL@1neg5b=2p9!Ua&JF5?c#0npC5^k6e%bq{P2C0g3ce(ussu_zPD*$$5FIT-7` z;P0EsmS56!JFE*m3E@1eWB-!>{IA5m#SHw_2U`Q3v(3P(--+BrJ+6ET{Z;|oZyTE! zE&3_+)TnKkD6jgbAp>dc{WlIe*x()HPkQJ?SRxj*7V_D@*LgQIdg9R!%88z#ez5Ox z#7#38tq3F;SWu}B4jSyfwLd^6J|r9Zn1^o&!Zlo&^$@o)P*71%XGGaxNC~4$i*E+S z4-V09+U>c6=RUP_H9he(QzZ4(ygAxgjsSQ2i~z-f-JGMO7?tLg5zYZ8`EN9GV->hY zP*1a3eTE<4ivK7W|`V}a<8!Vf=suqWH&>B9fv zfqyn@^V0p}!>4px9AMWcIF9i539K%jk0@+Z`+9$kMNvK%y0~9b@bf%ByG!snPhhg; z?wG0k2MU)rY;@vjMvg>HZS&8mGfj-dLru#$Rl6y6Qq_(q9A-4ZuyH5?PPVf7VAsO} zZu>R51rGxTU)v{nN>y%Yv()aVrr_RTO;Ugtt0)vbM4S780$+_onuh2>d7A#63x^D2 zQEkUeJ>%$#baO93N2@d0@eO5a`D#qIOC?}e+5LGre)!=J_*7uxrv0h#k+H*EQL^}M zX*4L4d%uK{%55JL$$;E1MT!Qyl%+u3jLK6DbZW{0a<%gn1^$PwrPQ8hbJ8E^&IPN% zu6cr$xQM7)<*HGf(C7Nre=HD}Zl+GE8MFWPz`K8G8vo;gpO*fN_rSB~Gem804l|VC zu-gW$?}*$D8!26_(d|1~?nj;bc`SSQ;Rnke_Fz=lyYpzJTeRnFFR`Kb$K?IB93lkw zaX$&j1~NcKuXsEFl<^SLpjMdn8794_cQOc;DG(b4YuD4sRy@a|s&QY|4clliNvKKU zrX-&_a&cI76@3`+ph9ZN!?jB8Z2B0jOnw7@Wstt&zM#c_V`)LB0*l7#+`Vn%7fG>OroVoS2v)=%53YN>>>YgT{mO8VcQ@iVm5j5Tn@ciO4fj|d zZ-JQYkiuIa&Nt&(`ewG?@_1o4PY=kj8UDYXpKVa(YN%F zJ_8`;4K)wLZ76VI@MUQ;adUOM{8I|Pj$OEDOC&*W8j!6sSi`BeAL z7IoIZ$o*#kcuqWH_7m9?>E83ZJW0niZW}j|Vl1Uv4XX?uGmg)n2*xnCNl?2PK4u+1 zn}Tgh-0e`-7kx%I%ZAp}6u(@M50cd8z-f(hQ5$pb+778l`z07#Yy;ywfZVI7)Xdxg z*akLoeAqZXs(PHwX;nj9UM`E$QA-QIc2`&h!(WKOm0`dfW@J;cWeP94aA!dYX-5Wl zuYxpK*c7B0q__{SzL8(A%_B}kf%sw%s2E2^pBomJ#*;d3h^cXj63&EMaOVXGkm`*_ zMj1Oi&Bx`;NlWY?%aTU&DzK5l4YsjVVdH2AL2~LlzBmIf0|%B5)=nla_EawLeYF*> z3MpgdtkTdWK(~NSoL?Y0W9t){mvr$`3JJ};jm{zsVroXy$3}?dPwI{sZN}9WqtLRz`pk-JbHdadv)-x%smf@FIDDsb=CD@sg;$elq3$`Po( zhAJ(&0jDK*HcIb>A6HS5fDZr7rpN~wTjY}8YZbc zcRKt35cd{dQO9fFF3r$0bT`r|APqxzH>i{n(jp}y&Co-458YjYbax4YNGdHQ4Rgkw z&wk#$pXWVmowd&2@S8RF_m1oO)Vv5xa(Nh%-xf>8P0!Dk7@sC_4N%dDEdstq3Smq+ z!J_s+u0aiC*9UFlAEy_op95B8avW0BYAfYn++(cmz9~fSAT^jORr=#0Q#0__INXj5 zm5xoDrmo$W&92L#Ld^5avXn-SRPNz-0aS_KXs_CQJrMd?oZ1XXlmSa0Ky2C)f3?+` zj0icL3CnB19ipmDBgiM`dcW88%SV1 zv$mNDGeskaVpcuy(3a8f+Yez2vq*89QsP{;6`3aqh671RWfkmYw%BBYf%>T|li9JO zgklN!Oxw~@8qp8~RFvP);x0$9R-|g0#!fiJr=~E4aGq$Qx+o)5?*W zg8(8nYe1hC^#lgsRP4@t%HEYT(i#xsAxg9mAc_yRk__cU+bn{s&^rjO0wcbMjVs8t zSsNmObXyeraCu|9?U~;0UEB+byae=rl*)+z8MZsDI`_7jw z+GNqPsN(+4&GwS+!`05J(fqdGh`1**^U(E!M)|K(UVv{D` zH<9_fi%cFba>8#K+@vmI`utqgKHe_9ocP@^^(!5F1rbv!aj?kM4)V*O8@k##W1uh5QofWyDPy z^oF4RB#fUTJ)$KzB|SYuloZmI6y-yaeM3Rll3tOTY;lvm6`lmcdhWbOg|-`+-$q%O zm(h%jpKKpdH$wSNlzIh;p$aRq4no~1N>zA6K#Uo4drdXcmXUu=(8o?CH=Jb~C@{sI z{V^%C(lT)|!i*aW_eUCa`3a>yye-=sl%o}h(`rFFqmmOGh*w>gbx4uB56M0&qs%FC z`)ZUMG!s^}fzMN%+pivBQxdMXCRK!aA7`j0I-Z$>R449Nc1BzNv_*+% zv9r12JJW?~TBDziz9Qs!Z*rFdSHiChc>Vp<#MYYJp5U%UFyz5#Ht@l~ZX?tMISc_`e}yr~(s{BW zcBFscYqsKl-H|%5Y`hfuQMwf?ak;yNu>U7T%5rdDtj8-qDceTC*L(lik=pMBOTWI+ z5u*j469`(!GQ|j5sS?Mg*cJO2r&|!BnZLszDf^M-@p@oQ)G)bep zBO-`{1t0ZP=coqtbbW_l>QRpe{M_8~K!b%ipj*banlniV{iuL~3a%^y0Jl)<_w&HX z@1{t+L@ZCFy&mx{QOY7xfc+6KZUKIiXzKT%24G9zB3g&r5~WN@vfzMx_Qm|T%L29 zm0bKGSUf}Lm}prM2Uh*HYI%^a%g-a)ZAgi3E&e-BglI9Cq4p#WGla&(bXq#EoR2 z8(-JVFe<}*WYS8%RYF%_OTT4~34P08iS;oRbh~sC#z2jY@0BK(b^;7%Ym0ji-)sOf z;G^-$F+)J_mvYDmXXqbW?)M*(LiCoYS$G%&*PP$egq2K`gqAn~*g3)*PTv3RlK0am~ zi2MxEFzVCKLG};w&h5{~Bk( zGBpkxdvWWYf{4fBa~i3%@omJvHBF>_^A?H%!r^xv-Tr>DYFZ=pa$ur&p zh8y!bI%50qzrs_0@!iA0vnyYteCQPYl*udU2aY9Y(v)N$ng|QbH;E&g7c42sTox56u}#$O z3}${1t=Sp5Cr|ogY1Lt1q%zQ=XMdA_QlY%X3f&xhc9FSdqLB9Cd?@~Yrcrvt$x)|* zlwc9oV6I+00l1Lg5xZ)m+_Rukb2sKzylR-9IQAy=0H&rHlv(}8dJDG-BRR=yC%);& zHk=kcYHyJ}uT>QDDF0N14I@ugP(DO+{~VHaV5G1f7=Wq1j`G>U?zC`4nDnplSio35 zFl96l8>gR=e9$%I+3Gs3*%CdSAzwJl_&U~8k-kOpx=5q(P0|kUenv(9$d~F{lq=pn zPrK@3y~eljs5E*518XDRf5M}9pEfKt$>)nF5UXT2OjV?utt|uP1*+$v5RogRKk5YX zc-_#M2rEPLeny(kqkOdEiOrZB{~^?o4xd2tdHWu{{&mRw4gFhMNY={uPszKsserY& z3Xkf$GF0u;F2|__WnOktINRgU7k~n)4?iRH+v!tD1#@N`Z8cqdr<3sCc!AF}7x~gj{7k7~_*y)E(mX?Sl`!**o zJrDoQbj3Kk8XLOExCL+376EXJGf>ad?RLK5fvzTe^zE!pWSw*ou$h8t2kQ(A#=e8! zTZXe^ZjC2=y>+T&i3;zUH^5b&k+RmPn;=9p%<4<&JyvE9y~aEfY7`HBS5>xn3Ey~6 zdq~H=;)+>Shqm{_XNw6qLPjF?g*dkxmThF4D$ZA(F^+afw*-HW(>Kx8-jD)XeORP^G;9yLz+arnD8lYeVnS4rs z)W>38;1RY2Nr2afB|Y_i%0R1Z5Ru{?k(L&b(HfDpgvf@C$Xn8bTi_vW;cF{tPU+~c zTi~Gy&;z#gLIkBQ5+aE=sq?*~TGOJkm3ayB!y8MZy2+z^y`u`R^@DBb`_iIE*`hH( z;?cvP_uim6zQ43Zb_~kbe8x;c*{FaTTf8EAk>YcLH(o$25wajAAbTEYQ8`9!CiX-m zhSt(&CkXtF76~&KKhaZ)}Pq~l$Lp&-T3gl{Nui2R7T~f6O;&%Llcgr3vb%jLK6F3@-hu*%kA<(3!?jUX8G(Ewq=)iU zgK*H>3wJ2#y0i!%Cv~08f`XJhMFQLdNS+@5OWr8Z0DnQ~#e*#U!z;q|O={Ac1V$_n zCB4W>gV8WpG=z;$T3JesFB$zb7#@%kJR^F60+>mo#!08fZXqhOi#GY8hhg&)FCWO` z7W>Q1cq%@YjGbaVly^!8*GJ9}zyy4A8jZX0JPIp4fg(LgG(E)x0bi$Qq^Bn>69&3R zzoW>=6U`_v$$>8O25dOrpTV9P`S{~M(Ghp=sML&3_J3vE>85m?-6#u`E$;v zvp4pDn{8>|?1>Ich*r{hqePVkkyUzpXie-tsu1$^f7RqUz2iYJyvt-Ay0iI8X6@nB*z)6^; z|CHnLU=z}|>nk^buBT~lT#1B6fwd}bbUMDEQ=!3;G~z(^tz8fg76tt+0ZvjJtx1lx z&|l!|a)W1I3kNOTu|ZjLdx{#{bSMlKqR1Xb4*GKCMf$}cmg!=kWquqg2p^js2diYz z&FzIi0p?83U|o24Q#?sv=+{dlIvsp-S=lB?E+r^!7B2fN7F%OLnKyy54AWB3($fuT z6J*qgGs_HGe@1NC43x+y zu_uAQ4T#oQj9Ld(aR8nu5j-L#{VqtwK1FR~pebAr0>~HK=`d3a0IN92T(Byr>RvI` zsgXk61|fv?*n~5x!0wC`!hB?fNJJwSMpRdg#^6`TUlkY+e8bOnFHGLZ4#ar{BPRyw zCJ>=_0ISJ~*2IV&+CjSug*cobbh6?_Q`-4`d)0nIcWs{RNRI3bGTxtr6rpUuMj}iz zQkT6nO3`1rWCoF#(AoFQ|^D2Xa1L zOIfFSjO3`nPe9DUq@wn*`IHMCAx+$(0)~qEkmi%i=Rxxuhw|%B3^jo9O&BwjEA*Jv z)4UTmBMPE)?+r_3O;PK>Qs8Fqe8OeQBKS#@t_Y>4IOWA` z8o-B9L7-L9G*c0~HgBdC4CV!4gEHQLDw6P1L2U`*wfS`oPd8GH`8{@jx502|p!V&B zf$ek~?Gp@y#Dp4SO6^%l~oo!-p2{FY(1 zPeYuf0N>89RO~?@s>P1Zb@&~l^is=)IP1JP?Y0BxXNU6+75l*fQ#BudMLfuQxhvtU zOHdcLdY$Z|9W;BKrpcQ@624~sxIITw%|wTM28>Genl(feLf>bB*vUh&x;RU2ZYK5^XBvUb^;M`hqx zSu9N;yFC+7h??XHZl53_2O}5I?}U;#z8crEff&?O-GfPdTR)MOqS7w3mh#xdVa5ok*RrTorV56TpI7wuP-i1sXM%mnzO0B}JiQ7na(z6kOMao-ojwtGX?E1Wbzc`G^8vpzw8!$9I`q z>S2~vEaw{(x~s#+^G(KyEb+LMKpo2(Lx)9F#Y1BRdOEbNIN!I zcH0khY%w`^(M{VX(6B+~&)|c~PgSn=5zsm_{dPv7)b@Id%;5(%MYtH7Qg4GW726Sv z_h=?c5jX;WR=^<%1es$GE-g%2v*c}y z%slvmgQhB|4@Hvv+l#v>Thni%O3&wOo^d&Iav8-0l(xG~KgDLPvNd&Dkg9s^SoNCv zgY5wSd<%5mqj;|5sRKEcElcNTO3EI`m;jpRK&6=1dfC8;30}-V{)E)|sBe|_Ue%hu z)&Ai5=oGgHUFwt=V5k$YI&T>j^c81jfEjPGjHyC5#k0aZ8vAr8*4InXaOI}lqP5c^ z&eWzg8%W@}!q5y95soA$Efc`7Ldni^(p$X^wF2g^q;U~WK6|;twqw3u^7lX_wu{OT1WQE=PHW^uIW9=6=vk3k*YP$t-N+_(2dhtE%*0j z?o|-O+9TfT$~OfeFfV)MpZn`VGbu}1;EN#3J50R93@cFA%LLQaw;bd?b1#W<$`@re z`UTv`UJLqI3np-ElJkt;OO}1b-MB>;aU$Bl0B^aEZvK2ORjbjJxXkNy#Ye7Yc3?^O*@T^|>gTWJp$_sP~)Q^60Qn@}|uFfFI-qz=4(Qdg>$ z1Z$nGIv#r`rQut+hvH^j(Wok7&3=9TQ$=b$>Wz%KCLty>=w##BxZYl;)Vj9mVf}IZ zZPD8>St-4?4^8(_8f?7dfTPc}@sr1gJJ2|DtAl0g)&RTd`3u9(j)D^f``gt(iM1mH zt9|6}^}v0wD5>%Nkszr-;6$#g!#@<^$nr`(GC!j(trsYA1}9Wbl=+XBGQL7wfXCs` zRZ`OgZzjd>GS(q`kr0fDU!~sq%XiREQlrqlLk7%#Vr*a{-%jeHjUy7#!(NALh`PMd zAj73V!W&-iWWYbC*g4rEnmNUp`7FpiM8iOCIR30gRy05+u5U&n$Or)7IY+ow&*{nE zHsf&KlT)!?Eayw78UY04D^X5SK&U13wOOer6cOgYP#tW5qa$o2) zQbs&VT7Cb;t@m>%N_UHxFsi3Zg-4MqjUiZcAmA%6FA>>L0`aYoE*CJ}_JRmJNWDQ$ zg*uRf`aHbzr;+-zyYQ)z4Z_?FvMH1@)-!6&fSIPQ88il%j(H7AT_4;PiiURMDGijq z7IMtH!wwX*c{wD{CR?01xSwA%^Nnh;8#B$F;%e)6{4e(7Y0-1>yDNCp@40-20XGeq z?+nMmK#dO%f^PJRavgPk=MQeI;&G3Nvq3egnYLFdIYTS`y{g;-{jS>}=FDBmI)z|qwy;2>eVrp!SeS=DqCdzGNj{~DxOJ2jW zO`ToUI(w$#hVsQKxr%l3&LXof&hmEaVQV{gQ&bCGf#?Lzka0eNUM<#kwMxUX*+G|| zip3IqUq9Oos=sC&ZaeFVg&X^?l^a=z&YHR0Xk>%j^sVcJS}@@Ik^D!MF9!A(Dys8d z%$_HnW?g5j!!Hd;4rn zMD*JcfkuVROL!4q@HIRF&Px5Q-><$Mox1bBju-Yb=Kw1?c>bC-b+bHixL|ry{6rQq z`xHPNKF;7GRra+P$ZVGJW)vJxc%v#C!Ua?A1HJ!>!B;t}=gQ#uNDS!yB<%^~+22+YE04mVV)f_jnH=JMc~>Y$V*}yZ zi5DsyO7*A{3i12;wfsE&J1VLa8JOcDyavA+h`-!iu+bYOCzt7JWdAlaCu95xQ--tG z*XsmV8szH@+=3ZUA60}W%-?;L&olbSILYRlf#+d7y=IEcfQA-z`SjiJTL;rOV;`{0 zf+O8+X8TN zYH~+mL<6q(aQ{jx_`&A-MA%`E>!r?^JzIWQrQ;>GPh>fmAZeu(a!}2Ai1Yi2S#2o1 z>}%C8ztJHK6pRAKLgExQzrq^1dU5VgDpdeV7tL&2vg|(h^<1w7RrWW9@-w^WanoX> z$rHKv_uTyi)GLi17FuuigMMg0u2Ii@eiroVeLtex5dR1nSfjwsUK^Md>p;kzJ}3W( zZXPI&4Fv9#B?VF@`+rFIG%`6d8ci^NAK4DDhuS`qfgF z(=^=T!+!8Zc?wt14*DJ6Eysi08F=kv4ch31>$=OM>Jr80GyuRR$s2 zXvDumwZmxn*KuDa5Mc1U(j!S65tgx&3q-5%#bMv5!VrYgO+v$;xUFlohY$>IJF}{x zF#<(<>p5gukhJ#c^pQLnNWm~j%Rc0&o}9P@7m01@Btr}8r6__8i;i9nCEhX~;8}~O zxAYq(cS#nM6b?lZ>lqUCwMlB5=7zdSSdxT#=KF~dJb4kZz*t5k<4;m38wF#$YX(WG zjn;llxOj=Pk(MAq`wH@VqfqXitzue2AW2|Vfg`3q@?&MaQ2Pbl9)?xsQ&0C4LbNbi zFnb^KSM7M)eg%H`hB{T(Ojr#LGukgWK!bB_2YXHR2OsN_dZ0x^BMOXdJtPQqb-kMk_964lIB;eaJR!>8D5|)Y$@d*X`>}X3|pIJR|mSJ*x zN)OgIRDdJ2f}YS5WHe>sM3PEZcHdDTwFdSY%~g8lN0>IZ@7`-b&yBiBv&RQ0mbqT+ zJ76Sjh$1}C4un=8Le9TIpNgZe%(Aaindd<7s0<0jg;C9M5pl4-`mPou26m@iuo8F} zmS%ZpfMp1&Fx^Sz*3!i&z!)vJO=2+-|0$;yBox&B_5kfUs82*puR5DkRldPT@xrID z_z6f3?PYpA{-Ob&0{;$$9tuAEg1!0e;x6Q6GC=+t@jzvQB$p!L3gzfyp^A8urKqle z@rWXo`Rcm^_u2Ah)+7v0UU6{|Wc2lzi}e_Z*m^ZTzd4M(0yiQh z5c1yq1OYuyOCC$l+XH_Vl(kP5Iui?_6*}PAG@#dQxns#L1a=S8eR)O7KJy+ZE?aED zWFII)ZW*4)voI8K#tS5RtpZMRGv`T;@Bvfx|`; zwvA9cw?epq161u$wig4LFbq9t1NgF_k2>Xq)!A(W7rsSGUF;h2$}fV<_PX!$jgqP& zKUAxgaTuenP6)ZdBA5?uOb??`wn=u>2$i{EP<;YOAQDI&Mxo%-C51q7tSo3#L-H@& zcfWM&$0`V=&`&RgK~Atbt!4WtgA?AGMIv`p#@pB0Y&OVLGJKN2vC)PGs%xIuCufYN zGYBvd0T5J%g47Rw*T;qv+@qh;dhN61HL?bCM5$|VG64mzxvQ-%zJ8V`Pl+R|vt*ghU* z6;+O+1b53sj1cYU&PG+2jHOu~PjHGoo{QZ3l~mf{OghFlA)h=MP6lWFM%}*1#Gce! zQbN37W)44M%|5{a$m%+#c#^NL$f@-=H0tpZ5}pgLE}-4u7|kl|M?PJp?_zE=h_oz` zC@EWeVd5PJpOF>fEFwQ6^=RygCkp}&QE`&}e$B+=y0(}(nV(F@bQM* zLg^rK{+UImu=2;>jkK^|<217*U*);(_|%rEnyxrE{eLmbb53rRZDd=hBD^}C+OIx= zRu$RF8-8;Lbkb}7v~ZJJAO(KAqf3cwhxwuE2VZ2<4+^nA{#6 z3x|q(Wf}uuZt+Llvg;=V_rm=LJrB{6{>5Df`}X@qOT{ut(L_DJZ~@%xV{(gAkNrzw zPDljfTdD3cF-dHgUYrza?2EhFHR4xdLW6Jq;xC1+;mrP94 z8wgD)0J~DA2Z&vPQ|zP%ey#_;YwdpT%S-VI+{-6LWhw)&>j|`wqGrb9VwWyv=&fPL zrs^-Ej|OdhMPDZ@BeVIuh!e%m9IX@4IWNM)`l*EQg3e{PH!J|%c$(LoO|Jj6o!<}L z*cBW8w4a>cPcCelS2RmbGz9RJnhj1xQqxEFyHM_779#8@*WoQL(%b!FwkJfU^W$}$ zOgp+F7a4MYL4{ebII^6oBaw7^Rl;eH_E|lbRY;&~Nat`QessjUHu<-8@*#FOM>x?Q z+MjstLUfZNX63MpC;7K{T<%SZzRg?#v_Mr!q?v86reI1dDqZX5Jb24m|9WD&qrbb0RVEy ztzMKfadk9XC>RKkf(9$y<6>}zVxq|amj?=w%HR3ne48tOGBenfhUwedMc+SI2~mQF z*im=24kh>~SrZch-IOc4m7A^=VSu4n2^Gk+e713RsIqdBM1+%eL#jSFavSGuWMM{@ z3fl}`aDR@B1A{AcgyJjhTcSwPO=bU{gdALQg$^bos4~UZ(lF>~+pJ23I&Rc0r{ozn z?9=FGoIJfjZ+<50D!4{{?3=NwFGG*`tw>W6F+{8|a*nBUy{ENN+`oe!FUTC$)kyx@qe2U5ECI@aA)XniKJLCEHD1|mApyX1jr46y zo**XjOH3MrMPC;J;;A*l_|zBuG}7$UqrmD*l^RfsG55N#tVWGBL9At)ZulDRWC}!n zgKILef5L1AN4vk^X>UTcpk~YPKxcHJMR4KgkjW{&Y8lx<@vj<(+c*g8Da+eh9wsS- zrKT+}K8a6cO-z)L3IOS&|EAqGvV;yo&9@4uEwHH9N+aEba2biz>jL>Fn5Ak&G&a>R zi~=}{v?bh;$1aE=ba!>cXV~aW5VPUwAeqL?q^^&jZo{l2xnLc=3T#m)p}$X44;&w9CoSzXgP0EvQpwPS1VS*`=5c3{bc` z7s2}0g=-)&e>)R+U3c(F7ca3c7&yx&)YjjDj=~~+7bU7_9)p!REzck{MIbp9GL2VG zuf0a8g?5L@^>#LiBaDt}>K=`dv}rCTsy+#z9nzZjc*xI^SW7_zNN!B?g%m1%!wiDX z|6+~_Ktf`;K|{wxf+KMuArT-BOMd+yhb6e1iRdcW>_mEA%8$_v-LEa(5*61Pu>wvjlq0 zeq%Io&QpEk2i$*5a$S+O>uPxOeX_IT>Q}@=#Gk{GPtpZH2F}%|9zUrdnv3N`5%|jw zQD+fraRju#9!rmkSakz_#9Vw=Jgj0}N1zhS--Cft!$2Qg1v|?S!1L0Lz*8qX%P?9i zZ*9b3i83SPNr7=eBv8O$GK#N@O($A#(HLK3-MerrRyd_$D^BKd$vRGi)Pz}91fvZ0 zOzvwdGvZ8{y+}r5@ER6F>0=E|;SD9iPsOynihHN^TZAbEl%D=11HWwfNhV$!1#1?g z5bL6Zw=g-Oyx-%TZH{;BMM-W5KBcbs(Qe3YK~!%Nz6-X&>k`=j6jf-^!^8Gu@v+T5 zrHhR(C8b|M3P)O4adG({tN;FfMJ?jqUf-3njUJJ@$k;Sdoln~TrsnhGXtGd0kHTTy zkT1)j$r$B)Wz8qyrZ;j4xI1XN)%S_qvdhlm+AUwh)PSw)917K~D%l5BMmsqR)!<*G z#5HZW>FdFUzYU-Vf4BM{nvei!SWkN3vXLjfxX%tx`hZGYr=Fdfj&+VA%}}Z<%o92}C9gJWWca z65G>fP}nuj=d?40?C14A4|*&BtU94{rd~&g)WweS*cvMX&v#2CLM4~ddPHc*U(xwL zc$zqRU|)Un{#kW3V4escV0fN|;oM^UIBk8sfkrB_ID)OQe!fPN{t&mFq=mS*M=_}O z(a7}jZ`<)Q5M|zm4PndfmAV+o?^m`Utp2q_u`Od%6Wn)49wUKw$L4{e@spo0dGAk< z2&ABrkK0Q3XXDRaex;fGle+j7to1jmzxT@IAFKaogB8TR{h!pu?{p`qCN+i|J zudjwyB->TkTIkO(KISf=8%c`YA8Ozh%Bi{$sT2LiS|J(7C@ZO2Fyi1g^;%~o*v38RGPav^*w#zpb8lfKzJ1G)c;uh z2RqaAV_c&Pseb~V3HZ7OdyE&-x;&G`*mR2vuz5j;iq!>v;!1193}f`N9aa!Kizj-+ z%xv_kq)vm7>6Gm`)5K}qJM#p_^m`avQ{RY=e@#;HAFF?aaA>{;c_noO=f+}7g?B|t zBeEhF=P=Fl!!yp6*$GdN{IQq53JEo*Mf`0E+&1lDX=8_xPt6L|1J@Zd4DDZgl8lT8?)!MMq9cACfHe)Cjz9aA4t+0YC92-#>BWIOocfqr@yyA;D>n|c(PMbKP`R&Bg0{Vj+L1x=-KUAGy_C%a(_0%@EmWzbULiCU z*&Z2`T>avF#|&%9(7+^+-g-Fd`DtX@=f?#euXh|{m(51KK^oO?yTiv>`@{j#{(7Ip zO6v{`lL^J*n#_erTRF9+Nhx3>4YiYv7>VwzqC!!xi7MyLpp|d8 zmo55x+^$$}Xt)bpsLT{%T*Jp!Q*~09rYzkdV`XEyUC#(^dF-Dvd#D z*Y7Av>Z08U8$YgE4wz7+_e@iX(~n;d$>7&R!_~JLiTYb}>tZ>Xn_^HImxs9=o$OpO zUNIZ`q&me+O26>i;dy^EW`W<3Hl+S~M!sd-&hTgY-1uwj>bB?!S7DOWE%l#bFQbP% zC*P{)_{Hm96Avj#0rGZ!W_?65Wek7B%zrt&8+N}ujoB8WRh0=<@RDZ{W`^KB#j_9c zBWI%9Y8*3vGOj@&@1IKxD8Z^qFeC`|9KsS(5P2Z@=10DW&M%~;`oSS;x_u7)ph?DE z?@j2T)k0)WQ~7uC{ZK!ynU&@6oTr5UV#oQ91zPs~|5Tu#|Nm5=P5*BTbgnarhgV07 zOQ;bVslbg2J)KvFaxqn@`wD$XJ1t$ly{i)lAcbfL;hvGb3;_`DNNe}<8b{zI{w!%n28DJ|$q(B65`Du8m6)WmM91E7? z!I+F!re)VjP~jPYC4eNcisW>IwzdMdzxOK5rr4x7gR$so zoaSCXyKRe`0qycJo3=5kTBOO~_D1MC?wIp~GHL+UxQ?gb5iD zi<}qs$AtVRcBizsJ%ggWx!u$qKmz|+{yG_1K-(4V!=`+x#Q6S!T_tu%pS0oQs7W3C zMNCloxUP(pWZ$3EgI2q)u zF~eUQpf@?+MwpQAFEf9&of8@^cf)~)PDU7H(PTNe&&FN5fl~%f{lrF&rvq@}K+ZvG zufx+JqZpL4Vdm6mQ2mp?*m35z<0Q}LSI))-zc`*FVv#G)Cnc^9&!_$h#F05&%qU;A zY0OG_SpwiP3|>8kG){OH+hO!$X7RoXVIR+(@d|MEp@?uWQ7N++h9ZB$Vy(d|gBev$lXj~7?c znR@cycJn^Ui|wI==7`GWzBK3BuXxk%<-j)Ne0S9Pv-cl`$Q) zuKHR~<+qxds>VC{JpZ#q>8~R4*G1{?tpa zL{hHHE%&EDGt44e>gg>Emt5|_@GmeFpQ;G$B^P=cCmSJGsxC%LDABtEO^h5_DzGse zv0lYgnEzD$(*E^#HDA6adkO}AU<*`2`J;?Qb=pLCT)%8q95fxT7}m-PIh zRnB;45v%3GjMdz$(&lE=<#oa>l=&HMAXaD^X=*bn*cqJEVft;&lhuKy+7q47Jj`Q3 zY!;8pt)+tqVaH*t?&R@F#(5D+9NhlxIp95(mDvqGbH`fQkR8E)Ejyo)43TP(lmB~P zsBbrwEBPMhKlg=fdgcFL^@Wn&1MAsMu3Mv3-u8chgTJP|{R@Zv&%Q9{uW4`p1BdOB z67_${VgLSH*+KM$h_ci3r!V{?Wvh5Oh2XHSPPPzIw$EJ;ch~0!-={u*M!d=~5NU5{ zq~2e!6=ZDIgK&j+*MotIY#SlOhKRC5{<`!ZX>b1tI9Rkpl%2l=2M8$}`!-bm-%8o+ zcan`?m+hpOd+zO|T7|s z|E+3oxYiQ`m+3;1V--(OXGh;BcbslVK~!QtEJKe~OxCz%oJqrx_@zjXWj z8J0^$d&#YTzG{^Apz=qJ?AjeQ1YKWKqP><3*ez{}RKr(8eANRMujD^p)nsdW5`mGd z-TC~5uII(oIe%-vP-{Jw^8v^w-yJ}4yE5_qYIL>n4_!Z(eY!Om@vn6Kzkb!fbo>AN zS0%M<))PbHXbM8YW?ot#0s0G)YeYFQ0SWM5dw=7hUihuYZ)<;@D9GqcVxkzAteh@x zUpA~K=043b9m46hOC-oM*l&fvNbF`LcCOjBp*YAY%3wKgDleczKjmj@~(rjYcb<^$q?R7I=Tcw+dyAEQNsGiS@PGvd&DEpc7|MoFa z{x;Occc@e-Ig%rHgn3`yphgyMDz6_i@in_D7GtC|rUJj@O-;9E*s?(nY33J+K`}8F z$9~dR6AjZ+nTL%O44I5gOa7sbTBI**))mHHBOVhzn-RXc8|?hr^` zEUv6{UT;#(G(R_)-t5>Lz(LUUWPZ=l?~pBPeLiI<7OD<#an)tE1l*a6XPk+j4x**@ zhAWl0bDgQcV;#>lK&;FsBV0r~h{r_b>R%rdueI-kk0<`}n2=s5OV6f6MLZ_ZNMbH# z)t?<*%xNoCCCwW6GW{N=CA>IYDAVcu&Pwxc?DvvQs#EhaUM?b5;r3`;xG63)dd5o9 z;FO+URz0^hbJYiuXEwh(?PXcxgkyC!g=m#s;i0C^692!K{VR15F|5 zB=Bwp0K6~+m#ln1Mi`bLiWZd4V#Q7g4`5VxuOnj!8bzpx0qS1E0$zFnuuBq!^_qRehmrp2G}bkuC%I=|722#M)8b_wI+a1|-Sr zuyDn&j4cz->K)kWb1R9e>HFxCJQ=gu_N2%(GqnxLxZ4?ld5KzK+xg?!1NA7eL|-xP zTMC1p7;0m&+tQ98eO2ipY!w{|M4Getsz1sZyrUtb@>baJPKh>k6Yuy{crvl5E$N8^OunIAa>dbYcFi{}1 zc}m!sQ9R7B=S+L$Kgwq7)ASkGoXXTeK@=&I^PIdI%IYqob2*zVC4l8U`&mn^T*Jsv z#Uf&zL~K!*fRMzXpz30d>O#5c@A9O26!2homWjnXUegB$%ke9Mu87~2c174G6ojpf zk0X^BD(ePw42aR%?`rQN0MXg#mo8seHAOlKcpZr*pHST>AUk&do|3D_aHtkd%{E}T z+c@6lmpino^0k>^aFC;wSo$|EuCg!nW4_Fli&)%m%~?&Q>OAQaVkQ-%+UOSG&6mw7 zgg^Wo-&8LDC}~j4EahZuHeMwSZE0XXmUb1?*}9!_TC?mMN_sDpDNw>+sGaJZUBMVe zd+xN@L}Ag4BQ$=U*g+|4s6TU1=w1Is!;|5ftn|jKpVB>i}Mz?ufx2WaS}nU3WPK0~9>G;qiQ#5}ZX@Y0M9$ z<$avjxAkN0lnfuzuf{?n-~7gP72f^=|E+&snS^UnVX4*e)a~r}4+>7v&*D1H4L=q+ zZ4+8sCOJ?9a(=zF{WVN3_Hpf8PlqdyiV2vJLheS*)m>-_I0_->O? z|9yf_NOBrix%Agu-oH)oNk*+=#?;>a^`-WL7BgEPZKDN&hAQ!QbLl$nfMp<98OVff z2xE>p@&l4rBR_#K5xX_5Mg}hyO#ks4$H2+l{Oq5aPLs&;7h_I}-TObr9F-CrgfYjh z20u~UWA*O)*~U;IVj$}4)PB&HFURWrVW;oM9F8-b0P#}uDPNzec=?O}1NiFjKfToK z)&l`gO8?VK4YJ@(yN0O&F);|Abw9^(PZ!3jHFvsf4A`1n*8l1L$PHWjqA3y~yw9c(Rb8|S zOX~H~E=v2lHT7peP$r#jD5g`Iyf`~dxD5j_rn=U54fuGyO*5};r$v08{)O1HaTTFDWnI2psKixj` z@1nn2&@`f1t&RYK3xjaiPyC+rjN5KQ%yC(#Bk;UR_d%8xi8FaR>z@K693_tDqm)zk z^)mD>3zJhv9!fJ3@|ngLGm1|x!XW#O~^^Fi|yb#(~?2Y!%|2ZIVx?Hv^ z`4g4Ya&)=kI>deTN1yxG3I3WNVnA?(Fy`=FBXW72*I4bA?Y*_fk zNhS$yG%PJG!?HtT$juMkWS2 z{B8kNdD>*CJyTt}W>l|#%Ss+;W=feI#c zXe^99B_Z}fUGFWxgsq?}Cx{~X>hM*6Jr1($fZq+WCBSHj_=Ff53S@lgr-&>D2~hG6 zKmCf_X(?O0TPqftM(Ij zT>!R18=aulHhxAv1lJY?A~(1l&yvo<+2qmokywC%a~+k*-z0tlfF@8QuYA=wJN!Iy zAVHmiP(c-;^%)I=Ox{OwB7QKAJP}E2xU7kTs2Xk==UKbzwx(hfnF4tIh=|%@hj~%@ zZKnq5E7(u-vod%h{mnTYy85;z8po4OCep;|(uU3$;wfe1ks?1}0QYc415DOy+{`jqzM8dRWKkOq=SG;cAVe2=9+Wuwbnjooqb*BFMuD$HO43J`#kqO?k~0x z0W?N4vJdGmSh&hB6j~8vpc98eRYwL+vj^vq) za{(70cXO@@Dc+6Dp1`P9W^02`pTal_Nrw?mxBG8XTJ1Dr&5B`TKxi z0-P1tP;&pr&~%Nfhe0w%)nWkvcd0AR@GBZ|T)Zgpg#ekRlXE1&jj;jz1$8D-;qvi8 zsa}E3buK+ShFDALk5)Fd0a+E2IixKDzm#bGg`Zd*B*#SKn@`!jXOwJ3gH3*Vk(AXp zsy-1w(t6SvIB%CA`9|4dmf@kb9^XC~F#_i)yh&PpvUUkXT(btd=~BFEmG6)RV%z~l z{r=o>X-A0VunN=x?hbRRn!o=tdx>K2b5bcd2>`RprF%WsN6t?aM3!RV{_{z$n7Y+s zz)Bw9zCfh!(~yPqnJtz+AtC@mH(KW@F)?^-KOBY)zVq4hDxn?3zo;nPd%SfIX=Vy6~v{?>hoM(&S;$JMaM@Rr{=8D2Mg@XdPaV%P= zQ~?GGEDFCusH~8O&Knw30^~pdVBjFhk)k}n41gzw3U!gYyom$Rsh|K5%+mJ+^ zv!LH=C3@DaqvUa8uoXZ-@z?UXYy6EDz1)MxuDp*eP3A9HvRXrl zFM|S+7bG4oDqSoAzmszoL&^AKPn;rUlXX644yHUKO;LCp#FB!UbIo~H^yz#(A2Khl zz1*a6+7@c=L-6>V2Ua%=r}lJetx$ura?{$n)?Q z)t)Wc6eUsD9>4n*=}%_iw*eMbz4HP_eM0Y;W-vc=$Y!=^T!awDyhs$yPQ^nnf)E_mX?_Gb{DLZC)HsBn~LHBjXfiJD(fV8G+4Gv>QT6bxb> z*UB|#!+*$0TCQcmfNi=!WgP4zHewf@iELCEzKoGd}L#OPJrUbNeZVcn5_7mdp$CLiOyJ9YLy! z5R{1o!+j%W9aU!G0sqgW!F`s%X1(yGplg!WEStrg)xD;cwC5Tit%XGt^^5 zkwGQmAu}w329b_S5z#Y|@puWwP*sBxTtI>ydWTBAOE>C4cOam1fyHHp#>~neoBm>Lb9ep$C zrV9pfG(fvkf<@=u5q-#|AI7*239eIRxuu$TS(^}k2ZF2NS;axyaL}qiuIxHk&x-ve z4xVNKYrX?zilXPjXC%#dx;^nE+m**U)h0@>v(D2|X=8{U)$r&ax`1>!vwOf810nyz_{KF}XwY4YXsy*!Fb%@U?iSGNm75Okb7G==dy+z zluLZ%0s?fA90ig*s)2!ph*txx$y#|H6Xn6>wcxt$4X(K~IQSzBk^3p-BVG8TO}M8n zksx=zQeKuy1POxy5seV#6ov?mqm<}Mfe~4Jlq7D$Q9jytnQLJ*Zhefv-N%&qKBas{}QmBtmbpBA8 z2=dt^uXq~+6KExMdae?YQrRzDx#*zstyPR*OWMe++AFNunX6(!W<&ML7+n3U*{gw; z87RwYvY=9McQq)lnxwlDkBwaKueftU`L-XCc1Agpj*#xr9?LBzRj5HJNWLMe;nE|V z_G(afEn=KbG^~`l4e<+F2TH7=8n0nst!3duyh7A62w&yit|QT_qhqJ3?u(@L3aTa@ zujx~jlrgPmUVXN+Zf3sL-YRmn#Adez&=MGO~CQemB0-?r1&PR{U-MC;9dE z+bMhwPoCf>MgUn6Cj4?aygz?z3cKHyz+3o*{1}!#eg9{|zqoOmP69}yiqQZwaAP#S z|N4XrfvcQZNK=VP+><`CfZPeA5hQ3=Fya5%gkK`@hRAK@RKp%Je{H3#eRdh68Vz-q$$j7iej!Lz&sUZ|10A`Cp_} z^FLG95|iz!_S3EH>aHmXotj>npC4rg%tQZ>Rg)Lew=EyeCvE$k5Wcp9DaXBzv$el1mbb6P_xAaSh!b;RG>kZ zfpX#X`?X*7@#8+hJ;zS-`!aVYNLY`KKO5Oro=lm&jnbLE=MPJ3vAA~i*{o*H?|UM4 zp07`D!JJd)O%lHLEhZ(isWSj5L6;GvL+TG=)Xp+HZV{Uh&G7deU(j9Hk5HEqX*U6d8JixcZFhQ{q@(P?o|l@yp4b@eQ~xUqC7hPz4^y( z+mrHNf`r8~tusNp!j&LBJ0!mM>uBsx@@vx2`S(f2X1r{aiIdK<%VNZ}6zac{Ujeo7t`q+!JjUMH$AM_XO%%yRp#|AaFO2!dCSi!yH04 ztIg8Ut>j%T4nV8EWJy`2!g{-UQW zkQ7FLzMpTDb4+lAxY6&epD zFS%7l&C0N4!bEKBRzbnJ4vE=`48XJ3a;heWN!SXGtEEMQTIS($ITTu5Q?x@m&+1Z2 z{v^K!hYVT|Q)^-gNFlXh6Yae?F|rw+{;*;D&+n4|)e=rjxWaz@Cs!B&7X6=_qwL3+ z|AQr*>=Va7V9|fVtF6cDIwj);f0?7~ho1prOtL*cz5eu2N{G`bq_S{dw@U({ zVW~{oH#dG9%6}qNi(NcRt>2!mu`bYzMV!dLrI7Y8f*ob;5Ma@GZ4uq@t~)SqJiGSz zyM7(So8O%r?X4#R^bHl3+=bRx(hYe9@%HCR1>h7+6=6w9^`l#200c)4v|E&f@q)$9 zt*{^k3OeG%Sj8~;UR7EEb&raM1{DO&_)Vafuz!I?>#LIzuCUfMn9`X1 zk&eLUXWQQ$=inS4<@`4r*JRdx36}6jVKJ+68g+bo?S|e2o*N*J(Gap5*2LXGL>fB@ ziOS&FH^^qb@>rOpNEQ1W%EcKW|0;!4kB3IfS3eZ|-3|M0m5OyNlq4z}+XwAtIMAS_ zEBrDfq_=J%1rRu+><5HRyD0EmtypMGvvCLvk{G*V$E7sL-E~|WAAgk9*lrj>T|*SQ zDM*nYr;gZF>eef$;J@wnT3!ospS*tm{A0G=UW(!r?Bxdf3l;i$j;aGsIha6jqZ{HQn%cB#*z*I6Zo%zPm_c*#5SfR(h*X`Vr&7U>nWzqpFrR z1v@b5$=1oocRVk0d44LDsC~WtP^ZX$k*rOAmV8L|VqM9Zsgtbsx9-d1PYn9M&!%+@ zE`QDA^YcL&5qE#}%SDb+vXf@3=5p7`g8Aic@WSXjG7uW$NGwfj=pzV!lzxfg`+$^#*x{kOuWb}X zh2XtRoD(UES5j*t#*jWo0|m@K2KobdY!a-1EXr;-_@vM6g*n|=Oj5!TgFH$6vjxdY zz!t3P)llx^eRJUW58hCVz2wS68!pOowP30-qodHgPssWfl_dKls) zSf3>!uR1eorKR%XD0_{cOJ!6I@r=3mrG`SW6giuYi(pacZVIDqkLy^=e6H27fwqOU}=86*|<1;=tv zXyBHoRZyheV`c)5#begnh?4lwF@J``Sjy>C-XDH4&lOBJfzn5U=DgH496 zFaZffX7Dt1#QAYqm*hJ+oIKVv8)kG&ry-RTbEt>hN)d2lHIwc*b-0H>#UOJunJs;_ewov4jjAUfnA;n(lp>Hiri5+ z>-yNsR#>A>&!te}W4ujq$|kq0XUURAmBGp3tiVG`sjh*T!!taT3Hluod1&)_X6p1d zv9hkdML9K%mJ%zUGW4Eyc?y7<7}~=WX(h{tVD4z-d5$)9D$k-4+s~7c%5qwZO}hW? zlD$=h_|{Yv<6MA*9hqk?yjs0Ge{V?O@u-%d?ygJNtEJC8+GCHOWc08nyM`(Ap;V#3 z;Kib20}S{kG+}ft_?^|mRxLe~DAXgvJdRjqrf2Gr)k&&_9$sOM`g4&ld(7XqrkiB* zuEp4X;GYnlPTQSfQfBTmx^p#Jzq&o2>)4pGx72U!Q4wdaFK5nuIZpM|9cn1Z2 zB3Yuoesn|Uwd*Y`Y20WN&^t>sb6s5%(N?D9j@zigGRoQnc{%kZDjax&^r5lBR*X2^ z+lB1Er=aA8K)n2k+*KjO&1p&2Th_yCk1AV2a6|y#GE};pRj;|q9D62Bl#~b02=i~H z_dJ$@1;P_th*)n-M{G)>Xgx;!#WBVM9qHkZu(Nw$%ZR7em%y{yZm)!37CO~5*1N5P z_@Dc~R~d->`kUc-tYH~mm4k^j6U9F?ynM9-|Ts|5Oh^3 zGHC()KxA4S-5iWZ&TcoU@ye;MK{sR(;j#{2x$!zJLx!V=FWP>s3h8(=uMZUa-40_u zK7)?pgN`(zLdl*`zIzkV#aY9^Q7-yd_pr>_(x(! z_P{giT=^^JSP$CJ&*VHmR&6Ov<)GrcIx`y)wQjPfG-mi-r@P1zOQ1Vlqb8*5HXV;Oy&Zz$2* z75I7m^rdCclwI!mXW^gnzg&NYdOQ&ArPanu4JF3I$-lB)ua^hOV=e+z*MILO$o*Pp z!B4^ z1j3#{#;?k91u_I_;g^YMh)rkK0iWIgX>3(!BOK|3U6q2hoDZqpCY7n&{OQK^)2|}0 za@>JfhMhD8tkD|WJUCpSU1A6rDfqo#FH;_u;93lr-Vn1+mh`wQtKfBEr6u`8E0XLBc(4qVb{Z5QHadh<9^mtNN zEX7h3bvL;7kq9wQ0_k|1&%KBTJx)7o+;3}Dpy$>mtRj=+S9$1AHC8Y>V=VpnZT`~R z!s}S_*(81*o-4Bmvu0%!et^bt1wlbCFRXVPSenF~7p^$V-$sqdU+kvg&rHQ+&*qqQm5%okRWMBM1Ib#!W^ zj!g^y(}4tpf~0Z{Ch8XDV=Nn|Buz;qK^x^OekTp-F7Uwe1;>(b*AJ3M7z>+PU0Zr8 zg{bs4k+gSr^`j#*IusLf=ri-r8FESB{Sui?NvTFRVoPQ-feL2b#3s3$nJyyI^am-~ zDu~2BHs)j}XL@JJ0xt?yGg&mN(hRESMxw0MEk0^(WhC=*GqPAMt8p0{W)X8T72m;wn3A7ngY_UGrKpBgar6&>rv(_DZ^Y>W+E2H|kA+W0iIx2DO|nN| zh_ZmfFVR-Qfr$G#-^34B0S6Be;1@X{1-61SEGaQuX3P|R2MA#k(8k`lSbKSXuG&O~ zlEDe{u(9#&lX<)o;;NI}hdjV_C3p%YdQGL`Yb+wqP-6-rfTkBfq$3Xxs`)Ihcp{_Z z&?#Lki-NmQ-Ql*zT%mRuHK&Tyt&76Dt@O>tMdr)GTJ}^N{btD$2)QA8OB2U5Nv~9T z6j>yC!2NY-fwKYwn(wng$tsjwxtN4ls(IF+2vZDL(yLm6(y=oQpOP8!B783>X}!ol zHR{sg*^A%cN?ME&C!46uLDb#8Oa!r1dYMqD3A9QGioRDO$SR=jos6 z(nJN?RT}9kG0YprK*&wa>bBQbE5I(ZlP-Lg&RXU89|s$;?`N zJsrX9{=_u8=!z*#st@;XdVZC=aZHRTR+^XQJ+F6M$?(zC%v(1(C&JkPkJ{vG%J6lP zpEh}hjA_IjG~m&%x{{u}$$wUZOy60~b*HT^Tdr<#w&~3jSsP% z`Nls6?M}|`H_t=$Gw;qmWyH@nEYTFPF%&K$P0`YMUr{hT0`?w(%8*B7{Gmh9qvktApWr8v@5q}RB)bt~^4khq&-yfZ=( zI-r3f*VIxpA;ssyw*q^chG?v@NbL&2I}|0cVOHyX#Zp1&U$`P ze$E+F#6cF~!7l;JY?|jGL|`I;8~pEUU86DyWDt=)^<+34?@Q%YKm@QP3i#D8@Dt_U zPJP4deIg&#^NKz}OG*YKU|Wr2{uW<729fT|yP4ImZaJWcdLJW3!xc<~7jj+21cNPd z6!Zq~AJ}F8LbS#nD;^9pS;D^wp^LKzZ!QfvxR&7e`+q{I)PJHr zjG;nm;Lj;Wc!m3dQb*MEh!P5jr(&sC$VcCZ^yysvKwx8R?wH4$jl}SyDMSYumf$QD z_l$9-H*5PKF}X{ez-z*O}N*1XUydg>aas6<4}VAFa|n! zCsaGiwnT}ufbZc*s4V0U=yqO$ggoQ8isB7|i?Knjrc>Ey|vj9~GJz6=@`db$TIZg*w%XOJ+ z%c%>$3?$LOQC7K3sV*RFviY?u`#qv)YV?V=6{vp6z8}%!u+na>-hA!|BPyIzzcu1m_tmHRp^D&XPEz@`$i04Uf+N~*} zuqg=(@Z&NooFUY(vigl&x3JvH$M?TDAEpowGo_P27;!_dIYk^aXH~^0> z;C9^C&%_^f?Z>x+XTRmn-umF~sZYfTUKD-*6|wYnbp+)DgZp+6T}KY^R8d!t@Q=f$ z;t^>2*n1T_2=1F4qNDuBT_5(kh-G2uXSzee@zl)6->CCH(k@Z%bxpI#FBr9cgN=W? zR!6Sst#PgkqN*VhG@lnbuw%$sxuzhh8P9*Ui#T_mQg@YsNDzJ1d;qJ(Ii_p>hKiHJ zX>AIb?|bu_!6?G$k}YO!CEzC79nVZ3<4jM>C@ zZRTvl?BS^U&#CoaZsz#&P04TC2EA0V%zOwR$r3~qSMjDVqF;2PpQde6b#KP(^2lbR z<+`?xhp66+jwZ-%Hyv&XjQeu9lZBUVJ9TfP^Pu*3Emi-U zT|?u&u~Di^F~nE6pW_e4-<{0(`2DiFD6+4y{K$iCg@fWc*b{wto9?dQ#BR;#=9LHB zbN&Z!-40*&s0zS1cI6L;@cD%K#6g`uA5H1u3>!*Q9$|&!3F)Ml{)L#z-MJ%z=@!}Y zmOg%8b`*;~&g?#hu^wr&AbFHu>VHHi>+nyRpA0fxF@76i9uyjl3DKXz(!(6f8=TcF4DnBii`?IhaqSYcT> z)t@i>D`Jd=Ya)Qj1p1#)(U6?le^Jq3o4@(eke1~UtupbxXVEKaDLnV83L)AStpP_y zqlLzw*0i!EpK5<-{IsqUS{4Xz+|;tTov)Vqhl<9da98$~c9F^FzfsYgMk2}CcoWTD z`cRuTSqazMf3B*Gdhy+~*;TaF{fBd!Y>W5FuJT3#8%1Xjo*3QUu)EL`MeTgUCy`%p zpxInY%oxkxJ&5NvG+;Ovu`O!7l>q14PcRKDAe6L}?aPf4SE0D=TG9F0~;82Q^T?X<#r1fX9^5*M9_gp+2Hup)pzN#YTLr3;m zF}WK3$RGvq(v?_Rp4Q5lR&0V_RjEQs0Q4^C`s0J>0I9~iiq3PPjsQ|{YskI33qGlA zhVW@(m0G!@ES5+Id5fY=?aOI%UCWgJo-%q8hm90fI?gqaox$Y1+ zooQMT53TaRsG!6h58h56(g^-I1*{(g%mj*CHwu1)4$zQ>)gL;`&1UGOEw8CNbAWhX zJJ&9TbW}{}CGa`46b_sH7~&!IFaP|b^|wP-BeTEvS}{$h_eKGwk@se~_=VSY{dA|A zOVyXl=lc%?qM_p0Gj`lZX#T(*Ekk;8@^{(i$ z7R-Ri@s^AYxka`};E*S`5f&}T5%%f~XA)i88ceMjKs-4sXAn`wIRUPdWGfxuwH>O{d-&JdcnwR-GaZdLGHz!Udnd-dt2y#XM=baMYjH}14hf< z-1$!~PP}{4o6tYf8B=}_WemHhM}R89#d*}N2uSn#=2l*&>VaM5*=lkV9+HpK63KSmj+rlZ~PH8c4!yu0^CV^1VEM&iT` zK%)gr(_80m7;U&)l>uhe{{gsPOQ^ymcgTO zGA~O1z9E(y=eCV=`CA8!?G~4w)yihEcGWMQe^ig9kh0NYtp=GSPnR|{0yQsCj3mO3 zcr?-tS~t~`F5mUbDHA4rg%f!U^Es~xY!GdY&VO@pMymcN`3E zoby)tspqtXTF1RU{I+|r-mQiJhM4b7gkw07{Tr^xGK&ls31vxYb$>P5@#DCZz(y1buGZsqX1VPBs{ztBdN~8?78dUX0^YbRE zemE z&Re?xa?4xwm9A{cdLZ0GO8IWN|5xq`M$q2v`4xX@rM>TPy~a-G6lqhv(~-;@1MGO6 zJu`;Ato9*m4(EL);*Z%!w-iGB%;rqLIqOYfZeqn1zQ^A6Ta%;WJb^#K|58qfS-EL= z5O5uT>x85Ve~BI%k*gfApTkGi0m!@^0_&wP0Nd2g|J}t2EY}6WUJOF8Km3ViA6K zCDS<5Le$EPERr9v6)NnK4(X%%0$$&RJYpk>zS}y%>Z}?g2o@POoI8A>Te>Re5v4J4 zJwMIf-YCIia9FDoF0@Rtws2~s`B3U8V=VUd^}?&ESZW&rueRqFBQA zu*sFdPGL=xAe|W;eFji@vKw3MqspUmT3A3AP*HqS!5CktwzY@-Nl_wC%D_m4I?jb? zl_<7}kvqvAIui96aM7xdM<*2}d<+Uv7I)~m?m{J7c}8dpsk2!oy)Wd@DpfZent0KA zTqG1ns2($D8O2=9LOI|2&*dg4ESQ&G3vrBR~`MzqX z-g`x83tgO42R>C3xlPBR)k1bh2S}s9%wY7jtG4!?w~Yu!V|K{ov@ZQV;l3Q6H7P2u zZ(Ksy5yn&#nWqgUT9pol8dvq66_&pnboBW3S$qb3dAo`GpGzmiL>5HOfSCV!^DZ%V z4;iG(&GzMWZ9#tmMk)G=T3zA4KMhf@E-Ss%{z=pP@fDLR(LX&6|0t6F-@!)xOL)k( zYjwGmF=86)VzSdYnts`+5bh;{9iu}|7|_^ zzb~Eq<7xQMd}RM^c<5h!WdC(N_^e|0+ zn#;E110lcxrnb}DALRyt|F(JO;O&3xSG0f5@7HjH{-4MuJ%lqfN7X1^xS&~ycLuGZfciSmj4GiE|me5KUHn< z?vJS*2Pt^?xA4t(BNTI#w%Mno5k*3x61MWKiM8O**Wqh2!|$A;6m1yIC18X$Gifyi z;R+D>+=B_*@nEY-mk%H%6?l4RXdM;vDA0&tYCqAWN@!CxFPXvQviLk8YBX?-+9rf= z?g;knC#f&}&D4(muG;?LPk!(|&+!%kOp_KrB{}FZ{|8LNvY6w2gdYn5YawLMf-aYb z6TT*OC`X=s+Wr{uvK^1V&I&S;0tWwBQ~*LlnQ!g>Q4$gfdRY7+1hBgQCXnO8Wgng) z{*fQd*Iv#3qa<8Qv5FCT&t0H)ZS)Tzz~HX+-;{*^MhM6#xUO=ww9rfSvR*4$LxqR) z>4a+ih@7rA;p?#bo_A6512+O8Ao*S?!q5gRW|^tTtwbIz`uy~qcfMY{>D(6XFk80W4(c&}O= zc*%>%=ZmWwmJfzLM=;h-g!3u5eUQ>E>3>~lsBrIfxnTL~R!V#`8IRb<2kV!2V(m~T z>oBG1P7kuq?JV4-d=7IBqhw5r!oc`S7dI3E`?+jL}=r%3u zsrYR8_p?QpJ^aXq{4=X-jEjzJj+rBoF|P6@;H}+)zrX_NSJJoi}Gz&e{H3@9Ynl=CAzVpWYcE zKd8_lcda8#2ul<8O1mlr#4_AY%8#)Wi{eX z+Ne&fD3=iLxB6So^Q?dSR6L*4W1Gd(G1|6nIjana4YV=F+$G$)8XjQ2p|YVjz&hTN z>tnnfA}K!yR^x3T75ror$Zm`mHCe0mUH;g-sJm>^^m9UrMx6TXX0Vv@DbzUZ+ko0-PKt>X|fDmIy< zBDY>n}7{KZq97|5-D3@x9g^LeUOVIsXO`u zNhF_94}AtV)^U>`YD)&rd4r0#?@*DJ0|vIeC@yhYT_g-B&kF#{CVj zih`l>Bzc8eiyLVHp$GBvwX0!w4|}mL`a*qQ285sY4rOxb;k>IQOa$zMau|8^#o@QqZ+^k&*;3^F~=~BY$L_OYO z%lp;O7LwBM11*ZCCZ2PiibB9*Kjk7itmR<(qp(5Ie6G4|c!H;1SQmK#rc%3Fk(Zh7 z(z>AU4!4>i@5{s5ySLg)H#BV3KV?rwVFWAfjiJP#OsebN;-5DyZh5I>3{ZI~P^)T( zNzn{?>~qc=l^rAv-F@?Gl9Q>j%wW*%-uk&~Id#(3VJmd%ATXrjp_ZdTofMbh^|E5x zyfTv;9+ScXZXgD-GI0;d>HSD*{mtG;v!4vC9h%3UA)J4KX-27E#m80X&+yI;9oogs zoR>dLEHF;mu_}Zv!33`zj--EhUBV!_^T_mR#Jpy2Jbe7KoxovNI^JRmvtTB0==MCZ zi?>%dg%@ZiFfkhT3&q~ZsVWKhsZj1=@TSV!=?^J)Rl1v= zMImDcnbYis(4zG(2Radjc1{T>E9EbG@C&gOJ%A6g7re=Fd8+o7>M0c~IUwHZf|gaE zYD^5HZ^*zFS_k2&rYulJWlW{AuC|+BVf|$xF?WY^^vji-H5Zgk%o06-S!4j~&Q~oWeug=QkBa>%BLe&S}(5WVo>KE%vv=`GsCs!9%g%hD-QNA`##uDt z0VShm`&45cDA+Mf%yb&+Dpp+(_qYM@^J-G?Y1Os-;Rj!%FukLn8q`v^UFR<_DKbs1 z5llN~P4h7;V&e6cvWcG83a6+gpLGbJcC88+7x9-+^k%Ij`szCd6tJn2^7FRy-o?@( zSfZJ+?z@d?`xjbT@p{oq>-c18&DDF6Pi-f5{g38X!1%lu9co3Bw9nat+Vg(MSxA&6 zx-&9RS4${rMssDnGF>&A1tXs@Ke>v!&t`Yhq@r?Jl!l&Q2GZ(^U;)cVE9bd`v$9%kV zSg-Yb3O%D*Z@2@welk}Z>Ck&}WYEhZcyk=A7HSgOMg1P=N-EVfJnf#!?vr!S5^m6RdLoN$!~bcI9tCBz}B^^*CRRPm*+EpO$`v@ zC)u(a=UWQ7RNy~(kGMbi8y?S*yLjNbOA+LI)CtF{q5-o6E+i7 zyaGCd{22B^T_5Y7TZQb`^Ew#9>|dRUa;5g1@ao zSNoVJZOnOv14JPqJR}T7E~boi%m9y2VK;&Pyx^~WT*k^QIQdZ8T21h{h=Es_QF@q3 ziO3`doKW+})NUmaZo?f;M`+@!`oa}h+;5{Fo`t(W!dEch8#odH40sL^;nN)93yGj+ z4H2({1xm25Vtho$BO;q4I%-I62!Z4Ac0lR?IJqBHF%xZJhnx?Lnr|e3R7_Rn6~l%mE0q909E&j+i%DoEu@X{m zHi~@@iIGnV)abqqR1oqBQU+MU?=A%>TE;e)z*KZe-Wq|Ou^@9C*a!>ik^n!QA)oS! zuQ0kdQ6kSsOSN$pbxcGmAOtENh%>^0GceG69M~8WUnl|UQHAeGVEw$1b#^j4U5E)g z_}Bo|Y6Kdn7B#6$GB*%oivt-2k_{kW!!8u>gvgw_fUhOMDmaoPx55r^fSqzv{by6n&Qq_@r}qnyEkR=MxI_RXGt|A2bw6(2rJ{0+LY0+aDxG?!O*n)442Af+2Iivwx0#<}0f3Ol`X zLo-0(lHg(<@Lpg+glnVD=`tEWFuQebV!}!QbSIUz8Y?ccG7Qp%7sfKM_yl9D=Vb<_0!x zkt`>AE+bBzJSrFLjU|l=B+I4GVK>QXmrQ51kM18Vje_vh8lxUs&}APlKe6TGnS<;ZDj@0m%q#fM_|e8_9;vDDeJK0FEEfOTqT?O>j{PzxU(45Ie?3N z?AAp%Ju&m4F>5gc4JjSnc~qWfMw(F|i0Gn#RVpV=y>coVQXy0W(5nHkl2=%eJ;T@3 zOxIM)^QK$bM@MB;(UvFC<$1IAL}lq@e{Zb`5+1*`G4(_9hh5*B5UksZ}|OXVu+ zQ3|`68>iEj5s=l!qtQqd0~KOKEIuyvryB1w|J66Y)x3dktAFQGYJk8#=4TPO zct7|p=*|+%b~W72xcY_*n06EFJD6oN{MzVCtEt%li7+9*^d36j^-?XzVl%NrBU)t) zkg(smA9F>GpPhy8AQQXpYt*kXmda8T#$8DY&q#M4Y8v()1CQT3)Pqjr^(h`>!UB26xz*q+8*Q=- zjn+G9hrcLewvi?KP%+<+by?)CMDM+8NON#qzpo6>#WpEDZV~E0o?5&tU|_*@k+xaD z(&Y2rL?m@!qiy%WzT~;`4iq2S$FDJ>Fy9eu0-b=Yfhfdh1Bz_9D@n^nEwnMQDLXfP z5IzI?R#zTt0;m%61{z*b%Ce-U`b zqMfdrOY=+bsth(!sDlPp3Exf?{4g#egK}R$JQe5!CX!3}b`E8a6p=;wYIq)uXC<$*AhTpFwIP4;ZZP4kQGE7R zA^#04Db{L?D3*g_-j^KuE^r*r*BlayY8gSaJ(=y$8j&Dx>EMMr_<@NeV>MfmAssh! zIM5P5+7II5(Z(ddA>icPYLe(afJ_auA1c*vpfzrG;xQtW;=N`0^k!RXPh1mcRQ9>r z)kXO!-k#5*-tq3uB%1qReAM{xmk&+9QNKG7c!6aJ&56%DHF96#AWkWcjNj;yAGkz# z#adZ|X!{>NiES+bCM;yh@#G{4kaLcrSZ}V~(q8`5-UTgR24uG7R8Bk^%r)*n_64pQ z`^P++P565L#-5>B-h5J}6ZOz(jZAMPG#(*|ZzpSNolTTZe;yNkZ+|3NlIva-Yt$3X zX4M`s3!otn^!LuxG^U(b^Wy>x9pXQ`0K=h(--{2gvCfqvhk>5Wh4qqyvJ)@9>&H&& zf^O!nXSjTYX*6EDG7Us-{EA!i&{~e@M0k!=>MoQ#V9rV6OTrG2H^#TfEYmkTv89>6 ztx|8DOxkMMYKx(;ziQi$*+1ltFg785;GEZ)s76KM>Vl#9}Ua(u(&d|8W*j564 zed`3w<}3D>z~j4|u3LknyAZh%)tu?C(Ias(DS>e>D0Zm=k0~iYG{I-=`isa)i3l-V zoH?#6fWAC97tG|p_PT2K^7|pJ>+&965Rx_Wnt!p)*rGZqCyBb&^G@x7HL+63{m>oD z^~#b0qPOcMx3h3e>=VlGd{ToKX~~)v2YNmjwvoz zL#At<$|(e?)qdxzGrY&mVtwDrFz}P3QPhD%ho(gxF?6eU?D&f8?DMaO6bV~_cw}_U z%16P^@?^bOZt$&NIsLM-BoDQ|uJhcpJiY&&($9zM4!9(xBt|R{WZsp>_od_|_K?qc zzjfrt+{+8{w-?Oa^Q6PjO88F?{NC(R36PxObVl%eJ-3=@Raj)T={oj;?x6VEil!Qq3;OEU6f0TqboK|N3rX*By zLK{!4+`d8|^U`Kkqx!2Is$y6#S5dOrx`*%c@3T*-VHq^=HjR@-j*ryt z!n3r0ZybM}e67Q371x}Sf>FqcRAy&`LF{QT%!Lemfz2bHjUm z^=<3a8Qi~lgwe7k>bJeYGB`|eR?S*g8{&2>1hf#7}Cgzj}M4TUnOTTxelS>NHfBp3O=0x%9 zkPsDFlQO$D9Oux`$&HVWb++s}JytfP*`6O(S@_MrUb@)9)fU&k%6+@+Si2L+z~ZFa z6Q{|RRZ%A+WDRQjRDp_@J`iK8Y=e{z+1=1Mbcsu!8O{G5$M5p|R@b?@$)FsKsmFmb zZK4uIU+_TCxA2!DQDFK!?rn}lCH}#O^>HppHEA68Btcl8_CZ%MZlL~u(e~DVQTJ>6 zF3r$0bTdPDx75%nsWg&GOGzUQLk}U{B?!{p-5@OpA{~MVNU6Z=@m}}ytb2W5d+oh{ z+5f}L^}62I=RA)?5&k10g&^zG3Zo5}nk)y0m2wcHCiiYgn54~?wqux~yMwtR1C59j zld7SjbBg%lTK+O!dFiQh4NsD#U`$R< zyJz-+8YHm^Cm>=qN>$89m~g8_f1LkoF|}$bz$?8$WW2KX{WbY>vFA}cJm%(Iwiz0N6WyiS z;$O|3v%C)ui4A2SohJ_y{Q&3aubfH?3BD!exU`?6e%nYXu+6x3o-G3$7Yz;XRillb z4-bB1B#Dft@u>RDvd6}=P$XFN(&2TO;J;OhD>HFqaTOlaIQd(3)py|e*Hr@i4N zsiy$-dLNs@ks>`@o|ADkI=}=q0kNU^QLHkroC|YOfvqk*CTnR*iyM_>kN=|Ah0=`` z5dae@td~XeXN`vZEI>S#9}GTGiQ-1J)`()~*t0JM*RT(wYl5I3Q!MfM$hpk6 zN(^au;x$$2Q__``TM0#eCH!%9+VNufD4Z9k?4!Jiw@E`?I|RaTc~+l1+Wo6Fe-o zX<^Qy2&J~jKGF;uo*-%RQ20$aN|UOi$h3z{Z0~0fy8A~J;@BN3Ij1D4^lf4l0;729 zZp74vQ(1n{?u-u@5(|YbGQBg_V!RHCv4=6@^8?XENp^C++}n_HK@;PtV>4Vbi-H|L zZD|cZCvn6eC_7R7#4cdHxNU#QOq-x8{iJd+I?zk1%3)eY$UU6qa74p|BUQ`Jb|Lt3 z@vV>mI*kY0Y<-XFTf`>%C(?^VnZCZ*#J0mnLYz-h%I0m~4mwSowj09bJkO`<(<5OQ}VfMxq?n>h5;=7(?s7fdG&%3=xXkO3RVl?dJ@pYm=kjaqF} z7jHe9lpc!+sb3h8#2bh6?e+_i>D}5C52ye(+6#s~OXinxjXLSFXhWWr|^qmVy9t;h?qK`$8%Veo)`GXqnKpaD>70LU| zoJd~t0?B~gnfmEQr$>2W-F4Lztg05`NX+o8(v9c4(QNQz$HQ5jYm zS9A3+f04UQ^X%scU*dUer`bL)-}uxsUg9@j%=lQMBRQYD<8WSv;O<*)Jw^n1#TwJB z0XYD|u8=NUvm(+~xSHxa)M`hwAaTrK)n#b$Rj}>nV?V0jnti7^rYB((_He~HM88Yv zccoA6!RkwxkAXh;VRNkY*sb?S@!w65_^q$%CgvAVFAe>vwyxApc=!qKg89WUV@!~= z9)RYxHT@nXhPZP7D~|c-S-#>ri}kIaE=^)|@gd3r)D=V7_Lvv>WEZL)2xr zhzKfNX!4t}p{dU!yYkG&{#|W@bdi~(EU~({hGyF&H6gVMHs2>#J3Fo)+IPQoe<_|Q zu%!uX!(hTiB{0O_X4rog*wXr)VV4cT(AtSEo;e0kJ}df#-PI%U{aea|6Ufb|<2#oe zMZHu~2-aQ+AQ%|$u_AknPLrElx@|)*`crlR9oxwbJ6K0_@*IXYM1`+e11jwv!Rh~w z9qJi!WnV(o98~K7opu2 z{ux?jUWbLpB>l5+fJ7>TuT+#)SW?|89CnzuokB@jO1d>yrnm#}X^W6IQj|f7*w{i> zmP<5e$B?t9P-e#zg%S$6MRr)CZ~TaRI9H^o9E|eqmGZ)g9UFL76kQie9~=OBVpcn1 z)gNrB^whS)Bp%@J+ZRXK^UyYkH`M~8tS1>_=9E&BjvrQAl{cpzq9nz6hM($U54EjU z{t!}Y-=rK<*qsEj=4CG3{R4UGpn ztLERSk*}&1YG$m{Oav~f<-3Q*aA0NPlf<$s#uUkvR4e1q*85eA!gQ21&J|?SU}QQJ zTBvI3Al_({;Yx#My^x70Nq`fKTNnckrbADtKwp%m{L5iNmo%qXO5?i_fMpTzepe%k zbV^FGKLIe6 Ts650x)?ubOW-C(QH9C$l+8wVs72nw&FzPTi6;iGF0bgrQF~8Ao zYA#naM{0V!ilanRYbj7`a$9xfQf4)GI@ok9^)OGb4zLT>fEkr`!QlNrOCXpR)6rve z%^1uLO$F=cT03UrpTl809FLP!Q*W~hx=H9~^+|T(Ve6}pVLFAnV?<83kFz8{RHn`Z zuV9{e0czGFPo=bj7G|QwOUb;nf9A?w4FP7u)xXAn$R2`yx}>`^9>@IkPQ?VLf2Dxz z=es+cnP9ibN&M2d!@O(Kn9N^!)+o~0c{=fMkc!}JH0A6zN_QB#^zRdxp07;eNgjR> zU|CTuS~Yqka5Sh+i`Kl>KLqxlD!@NLsPn%wU3YBOh5yLoAg0W$4_`vSFiIiEb#b7M zQsV!a>C#AmSWGP5R%(oq8Tw9fAK5a&^8TI25hK^cy`1P}ME}OpHRfd>`)@gba&V@f zcngy}ttEo&_h`75GT32fB2S|gseB+bm$(xqs{MacK8SKZaomY5L`2eexBrm?yeQY0 z?r8oK_x%?TYOU?>OxKqTLuA|+2@;MRNJ7d1?z}JdXG=7`+(~VzL|r#%{{cej`fU6O zbbP0{Q+LJEzsGCtk^Zf7@*%lEyWqj`2UpxtZDhUKJyQA5mMpxcik={{2KWOKj#Au| zU6y0g3jd>gkoyA{5yyR{Gpk1t#m?wMy)hrj704_;3t(zFEcV)}L+yudgGut>IXjwu#T=q#NA;cl9~1%co!FVv$cBG+}9ERl;#u5ScSgL4z|dRdMv-cZG08p zCiCBN%{uDAX0Xp1ppae79AWzt=y)sY`(rls!1!{Jk9|pWT>9Dl^}GY0)VJYarUxfnN7@7N9hy5Oq!s2I6hyZ1;uB z^-b-B0#D)|v8wiC5!SKTpphwpF;lQ5nK5PC#&}6T)`V#t~!|N z;i0p$3=G(nS(3yTaF}K(BgOcbGExPoNi{5IR!^WHf05#o%*-n0K&j<%ks5l=%&tO} zrl)hnK+Rmn*(;b_RekZsa*pmVAQUq0+cs6E056@iz#UHM!7t~JR-3Y>{*l>@y8j&I z#UoqEA7GYS72!~Y(HF?LZ(DtoVEwq(>bLFZpOJB2T_MFO%{pEg;c;TahS7Ivp5X7* zW>R!1h2Hd7(TGDjTVW2wL{r-US=sDcU05qzU`vQ!;oSneAen@gV3X+kSavcBTb^=* zQEEz_RAuzkxdeHDCy6wHxB(k*n}xm9tey0EOaEj|?j-~J@TBt53t;?+7Zb>l6uNN1 z^d{E!Esz5XZPIRmgiYR6Uw+`Z)BcjXB*<^1F&HtOcW9<+h$PCc-p2% z?qoXF6gH?W@ywMGJ77icjujcs3et?e1uF`s$pzGRm|q%JOCu^Zk~C z3vF`i%D~Pqs^Ald2e#&o)qVarf##l-^p;_%&B#+phR?*FdE3IS{C>)Mwu~C>pF;;_ z=tq2E8BAj@Tua^$=YACu(42}k z`H|FkU&AZGt!e2>p0T3j8VUQI`2?9QGS_7V%jn{Z-Z_e5(1$N8;&(fS47YL1B#<$8 zr63*#$MBQC%Yrp0zobNu?#7G+Cv(%ktZ2*Gm^};h+9DPJVo#6|>hcY&PueJEPN_wG z4xp+gHY*)MOd;F`<-DeH1d~^SZULl5pfH^Nc=YKfp#rU^G?YhZk{n^L=K?_T_Wg?? zeMyTK5GpBrTQDw(# zr(vN0*I%Z*mwWM4AtFJJ7C+O@6VRI*I@XiE&Wus)G~6e#nr*6FPO;!@Z#Xje*6N3{ z?Ku(e+VC>M*V}o>$z?#t57Kbf@~oUL=jFWjN(!n^yI(jUlsTK@Y$l%Nq5oTdUBCuc z{UG-;s#}V4`1(x_miXyZO5lf-t+h_Er_3%A5*hmwxEF_E+nHHnq-O40@9}BD?`iph z19^=QE_u~90QW=jb>1fOn{m3IgkLcI_LU&*zE2Z=d^49CRx%u7AL+`IVUzaLD&8zWa94T+Q|Xc2fY`;KQs z9pI@c!kqho7Dg6A7AmSN)OHsiG_Y{6W4bz2E^o(jESLc|u{qB^hz|!xo;1mp9-p&p zu0NVT>XD774%5EdreL5NtLQr4I!;{ijQ%>ewYp`~>R-E3!uRwgKv~E_Z$HA-u{a?g zvC_SLCoO=qtENBxnV~J+RB(PEmqoo1*BHkOZ}xD0VZQi*Vv6^3a?)2;K=KZOC~D9~ zy@^6U{(!!-nXVyQIju7o+jWRDQOJMkZLlIlrG*VlNg-ZsDHS9N+!_UK&W4_dNxpXr zi%1JgP7UHbBzx3He0&o&-AK-QsQH@tF^*txlNa8CO1K=1Qwl%_kQb+*jHtB*oshXquD$QF=Urp+q5;kjpPLRXnTj}kH}gR)7=Zx%ThirJ2RYe z#}T`>RRBaqSP^(;YKH0(BGR-Igu}46qP*H91LYn8?8(tkWd;Wv5k9K%vmAK{50yYS z99OF-Y)loJn3N&TwiN8|xlx;U4_Xp4F#KUHwpF zAJu8O>BUx)`HXN}H)-6vw8&b@_=*Ad0bki3qxiARcrR$|6DuW@ZHDK*lvDP8Ho8WL zO~;)gZT8~$RDtmI+XQ2QgiZ0p-CMs6qr}6^L`8SEuSk&aZQ>ba5(=YEs2K1t8hCX} zG=7GI9IXDyNr*n#IdPy5Cy0tPaTTpKqZ(Hhby4L(IIeLkZ*w{ z0d*x1vl1v=PEMJX%*p}mUU;N>k&Md$Y%xs6IZuj#L}ez&PS$f?qXGXBQ%6AoBG=;6 ze^wpB=m zwcwVjj67X$TbrH!C+A$f`nO->+t_Qw=ESSN)?(iuyiKF6e9yZ7YQc$D%LYf1{4t>k zs17AE{c&a?{ThEDgH!Fun0gG60v56bhg^%1Ex2iuqE(yPg1&LfRKjxAzboL z)q$?){O9PQB|Z<@>LAjDCaa)KEj&Ani~W91TS(yc?Pk~mS&^Kg+MOpIaxJdN3nzql z1ySQ&&xTevmMLF zpXb>1;2dOwrm0>$h9!8z^9jyl_5$kERN(Hjix(d#*b588isRoE4#*tPfqIhUKDKZvpx*ZW5<2~TlGH-+5cFJA9)}Jod3f~ZIGMDt8R$o zs6A;2~lhbtggmQYPz zTb>q+)A;|7evPnpy$CrSFH+{MK#Ct9*bwn!bKCJhwEXrmX+*UDti?YM3Zr>{9hPRe z{yM67Tl2qbG4Jn_rjPkX5{V2=?Ur^72G^$w0t7x~IRQ?|NxG=PPdVVd7+11onora@ zO85&F_AsRpfxWFdEH+BhcujapYppL5L^O)DDHjwRspzid1`BF`;X%Vwe_g9YJvUj+Emg3ET} zmk}?*HAF%NdCT#6PhgVPs)N_IMN#5YMT9&H7xO&UUY5A3e<3izRVfI#4J(coFx?&A9b+&AXs4(b zF$ZJ8)QY{_?Y&M)k!x|jJ^Ug~qn#PaNo9uC{hO2eueF%hXMED)*F{F(zt`e1&2qtv z@u}zk+k(@YqN>eaDJ5rX{8@zS@mC8@hmd{7<1fF)zgln|Kl0AvUjSQ+#<0)^z&^Lw zG`A?002x)Fi+ye(#&-!Ze^)p?LXKUr#kk3TVnG`92~eT`Xf`^Lt(ZJgTS@w6{KL1$ zepCdNs-`6qT8WpX?Egm#PJJQ!(`C8%bS3|&p-kRD1APVFe@Kn~d5fS`t^4~HajT6n z_+QAb|Ish|&!Ics_(y7_im??1j{Tm ziWcdXQGSa)r!fxuS?XlyS^~~3YH(ccI}$cL!P)UJ+}@x=TBw`*(>3MSNb7L{F@d_h zVifh)=~ZtVzb)gL(ZF?XYV4{L^eGo3mT3sF|`clG#R*5h=iKX7U! z*>#&qEHx9@>jvSo%+j^V;no?#G`u6XWP%8YE}fv$QCRz%u_>%&8@FLX+%w5uc=%GreeAM7pc*Iy+yQpJa#$tQ_mwI z{dG|4DeQk#5#Z(jwKU@*T+Yy|iR(D)yI$-k4&NccZvmk@qS?!LQ=JZt9|Et=Z0{5Q zRW3oosToLC=l{T||0w8FtdY?dLgS(cuL{Tw zGy=v}nH27QL(DWk6s^&2C7}6gF!dz%we>H0b;&lFIPn)WMkkDWg<$1%Qoi^WcQmGl zv8CY_7Y%(eCr{JO=tbXIe?hUEQt2X{JI1_`(4QUA=7EZ&XW`R0AmbZa_!KrJ(HV~c) zI~5qHS@RG%baN?5s)1A1SV|!y22=bg9GMXULN8wjvTk@ow0eVxlb7O6xEqaITsA^; zjMknD_d7JXq%=Bi>g-AUsUAmp<^~DL^GrL4iK8r}WN?Q+Ixa^<3~x2QbOnwH(5n;& zQ7ikTBb-S0_BdN(HPn?Z^uHZlAyyAz_bW>Ag;LB4*yY~ z7J0EGVM+T=#pBO}PaD#l^B_WfiS5LvH5<&b@ny3$J3c-L=0nB zH5nvBQDeX-T^B>duiqQSeZgCs{ll-{abOoVMwEeEQ!S#CZ? zV@-c;e_gQndKX&B+irHk26*&T*A+3925;Yt`=YmPtk-)dEMusvykYE1(JUs>o`!U? z&YVowpOcn^jarF=Hfx(kQ}&M=vp-HfuaY{me2-1_*;L;ij%Bh#tmuWCOD4ZKHw_=h zl%Nxdc{eD7C8YJbcj{lqKZ(M$Dq1YM(MW4!rd*#H1jGpGzIF8 z{+z{@5Yl$tve9g{PxfFsL$&*-^BD!@zcxDi{r(?}5MW60e>Xx_nmr#|Q^E*=1$gVj*J-lj2S z{l3u`?vuz>hyE$~A)@o{eOLRB1JYClhVek85#s4_i&wO??bYe#U=rd=CI3z~7?{k+ zVxRw8F5qlu@MZgk^~LXlA3GVmi`}<~i6xa;ALDi#u+IaPZK2#gOU6t%PzC^2z|Ax}>=l+Z~>pOM|TVw3*2L?>QX{I+n-|`qqfIZoZGfIzCP2aatnixEhaWD;Va_}% zzpTkI#{avtek;_7+FyG5Z-4!7HAp%?y2nMgUxv#de8u@9VgV8UvrJupiYhpz)T2zcILAt zyG*}0GbKaEH9JWra#HuqV|RI2xm-)RT7mS@?@J+4DKzL#r{YH6hTwK(qgPd(^2<67lid9ymWm&_ zse#wrAa>Fos;t-t{UnMe3HL3$A(q+i2erW(v*H0<_Baw@`L@lVpOj}ICkg;Gip;)n zeqboE-zF*(JM^bcUD%I$ScqUIoLNLiiT-ZXU$BB66k8X85lR%iEJXCCf{z*JZu7xw zUbaL!3e0&Mjq3_4zOX!t7Sx0wgR_TykdcEhWwSzHYDFJ27Lvt93VUP%;HCV02!1+M zOeRu{Gzo%~Pkt6t46-Zpq}0co(gLWbD@H^*&J)~dZD@Kcl4V?W5)DjXQrB$~MoRM)$aH-c6;OAACt6$j~r4$-_m?UQ~@gBg8yW&Gjelh)LpnPd2@0!ibV zcB~{>Q_lNBc}J7>^r_hkP5UB^N184ddiER04*+Zfnqh}}T6_FDcn9u<-haJx4noZ; zQ;A04;c;==Nf%j&NB%8J>UbPwvZzB0r&BvIwvg?q#T^+V!bM3yP=z9!dNNG!vKUXg zScN-wE>Zq#Aiy6+8sh`JaeJ#xdsU(S<}ybnnS+XL5Z3ineZiP)Cy*K9pjDfaGxjZw zQ>-sWAz0l23>_q!ns?C6_|-F}L)~n-(~G9EIznzU8UVP0*r)WgxVE zXws^>CdjMRoC5?_Mu{1u%}A2jn1qoJNuPR7x6E9bm2>()YN}|#G6LPfW&>#KOvYOe z?wRst#kFC&t~knypEh&xIhIvl7#_V732o~GSH0r5$GHe6m1+|r@tzbDy(0*7yB(yf z0gMjp6kMM&zro7Vdvar95eBwb$CXKak!cDLsu&?l)*qGqB(Xr&^dDHk_e&Ymq$~|S=*O`sKJqALXD|2CSF zife?+?DvY-z|K_&_cg5PKZo()70bu}`+*GwhUNSjDpz-Ws`wR#-FrnegkAY=w3 zDBC@2%z%bfho4>4DhD8se?<3=8vMLgtHe&BOagQYtsOe2*8_gQ45XN(`2dL-GneI$ z@{X`k{p6*zj%%h?nY`bt4&ZH5T==emF`ufAbpBW_jvhuMGm7c6n|}dEDFRckNI=85 zZ<6npPkg_}T)p0K_+97l@L<(&$8)WN$*|c8*0_=XuVj&qWxtY|URcLXz&HXc27MzXdcuu>i$6dVg&qVi5b! zJc5Fi2`mJ)r1NPv5>R1>5S$02ZQ&k$3}F!r2_EnOp;|f)g^F7T6OoC~tAq*a zNzYydaRTT;M*8HpVZuh?OCQDL*(i-a($d(6lW&I-S`p-y198G#C_+Pcb&aQ^LA)P{ zcMig7iDd=GfPo>@B<>z~(C5nbAR&8V@GTJwcBEdUj)FJHh}}E(hA5=afpRVq0jIY2 z2Ei$$Y;K5BM96yCqPVtsS;t7a-vDdNf>x5l{WBp#WU{fS5h09PWV7+k5g|uk?C@U`lNLX#7V9X*&`#HXS=Sj zj!U(EfW)&dB-*js_mfEBq^P4rI-@~!9?p|^(Yqfs#>hsOr%?%t6h#adl|Asx z1CO2~jm|i&{eoy{AUWn0-pURxqc89b%~&UcrUcF!5k>JJLN(Q(Gpn=jb>b-YUXiWFgsa6aheh)_p zAiqPltQrtpnay*Q?y1IYikX_2m7a#4{Q~ECHC1MNJ>F>w^MN(NkZ~?rky)LP{*VLj zNQ~=~r*R+DfRn4hVTW8?fxIc>yiX3mPsVw8?0JXJbH5;rfy;7Y>$fjK?kZo~^9Kg< zaiE5_dW26J@=vMqh~0S)vRuyP4F^~AR&esxf44mkh)F+^#qqOouuKI}ClYQ=qSH*_!O3*=NqiVUZ zm14glzYVG)$Tq3u|h>t9w(U7G*36niUo8U1aB$=kA_qOX2sPh**}lL=>0*w_YRtVrH*uuAck z!C5;hESV}W=EPzr@=h=EP9o#%^ne2jac{#|+bF%T1RMii@y@JNgE~q_)v7+U5j?h5 z(U#=6ka&+_6+`0A>q;%k+#Ims2*l3jZ!I)qD-pk=gp-6;VUBYzqk!DD>Am!Yyiy65 z!F46sE*HiuZ%g5U^EH@MzOg^d^ly2FHwoh_T$1di3EHbyIg3s|RbBg4*Z7uMdRC`@ z}E1HA<*Z7->)gIP;LHPa+<6 z6GK)+{iT?C6pbOY4A$4rK$L1JKhNKJ;H!#9oXS`~oLYxc%!BIB8?-|A+8yUWo{B*| znF0dCOGd-Lj-)#0dB3L)_KcQ^Atg&84BzD5;KvQxA$+1I(V^!@oBoYO1 zC!Wg5W`zg|p$>_Nuj5}C^zhljmGGqCJb3B+n2qxNFof!h9F)?f>+!Iy8XyL0GM}U=|d^utO^{C0~N%96ui6e1cY` zlzRP!Zjx${_(}vTKUjAB0Gb9&;qoCx_IRM%sDw~Z_#2zHHin9rT zuyQkfkslNde4$j_FDy6+z8e&)wpTs@<=$pVT4NwWu%Pm>Q29Q3Jwch=AyW4wqiT>+ z=P(^ANac7qSP)BNUJO@?S-y*8K9ulLIU!NNbN!S-{?M3M2`1YUR;w}Y?CEB z(`|y;+Gt70!te=-h|nODQ||~p0*MPx7!x#smi#yvTa!I^M5Fw`J~LnvN`K7>Wc-Bj zfR;Xn8)(%xiFZDo6@qE(h9x|2JdFRsrGN5G^?NC+;iWFdV=3Us8Btk0@bfzGclGF3 z6+GRMIB4?B=lU^p_gPT?lnC+YBW_(JYtZ}J16N|2UKh8ynsz`Fh^z&ZXn}g9@P+kQ zPT_M(FRkHp-MSD}LaY^+|F8{9-xaQyoTLm;N!f1q6Kj5oDgJcoNfU4ih>@ zfVzYUTBIW$9nmLqxFa>3B3Dfyk(Oe-q@964meA|b-TEM4I&;bNMFYWcgaFRi<|O9* zA|~qey7TPh=H#R4$)|NQB60*2VPMywxljmQV;x~r7fC`ZF!Bs1e3}wR3=@?H=q~*c zPhb{=y2PCT{A3B?qkFPbN8D&xD+(D5Z35nCl3Rq}&{*MVf1bA0V&u&uOp?~Uj3=gh z?_=sh=eoH_fI6*9w}x&t*A=*ifKQ;HEwH34@mPQ2iKR33!5Yb%$8uT4h2Ww0e7dQ1e^`6#Gx8FJx1&bdta7J{*vmx$o z1f-)Rb_IUoSvtEX(n;7vL){@j-J^ysp}FsV(B0s&YNgsHEOH@PI3uFxB~kkQfdXxb zPzInD>O79?}<6RiGu zk=Hx50*mFRkUV;jhXv@%!IxKsbwciA(;$L086bfEmj?02k2E>k1JODG zZ3iK9=?2JrnosIRDDKrk?;`>bdK(RoF|aP`Q}{2>TC@$`r#u^ zx2?eDkLj-f=qWwq5G?R9^MQJlQktuIPArtDjAuUX8}O!vjOqI{#>3Y+{wr+Ajz-aE z4A;*@AE*>+!xC%Ggmm+}^2xOkM6YXr{4+nabtjkmWmi${e0$d?HHAE`zKj z;?>y?1_SFRkP`wvTvNmpM*X&l)fL7T4wA)?sdc5p2PBq0-{RrG#%AUdVSciG?%Myo zVskq*m`pI?%4>RFcA9b_5fa^eN8*7;iy-%v`TVw*GN2H+^o9IOG4Tro_XtJNjR|B~ z^D*#@2Y|4|86+aF)&^RI!9yoV(-JT=KsQ}%da=j?`ilaJMxo`xvk1W`Cgq) z*>i6*hbj9KUfH{BiT;gKKhm$zWx!=Q-2c)ahF%M5Vp(!hG9w_d`X%NDS&r)NVU5Ic zMdH-Sx`)e5Z=Pv%>P$aOW__$x=0;>T-M;`l)~N_XexK zZ+h6MpyyLh)#bZdkKdzj!|De39u1SCyFoj6A%~G+xJWF@m##)W#-A6~&x>{e9S&e? z69;*k5r0u$`pH{|k?FubB9=@8mvO(qsZ@xtb1~4s z<1GXIb|}l%AqSxL3kLDaqPq0JcPcsQKkFdkW{pHDJd>KS1hBiG*qszlXx>8ZuPkkR`IJu8@TSMBa$%`il>^oo*|*?a6ZUIJ0-h3hz(w^rU zTlHGV(^&C+C0Mdi=AC?2GfKG72oX7(<{G<{%1f{JL%1th=8@8JP(`I)xC&gW-iumy z0AC#@3{DK!7{yDNw1J)0J@{->8!)fyx-TqG8QgTEGz_B>L3@oVCur#wS-O(#2dQV& zt9%w;FeUjC;vVoG&5;g`6*wcd{2&MsM5RetC0_Omr<0YFREwJSWmWa~7FKr0u-Ak+ z`F<N$<`^{!xsf`0XlwprFn(L7s{nbilVPoC#T4M@;sr~2xi=gN0nbE~;m zz4xzRv`$AqG`!n)FPGU=dWuZ>pN(~XD7rhi+|%tHzD}^LeJD7F1dj#441z1tbSW=f zGxpA}C#u(pWuh2z=*rxyxdNyKB_j0j>PJx2=Hcq!zB81m&7)GG z^@Wk5tElO1idO692A7VoQwG8iy4D7A^FYNX@BG1v{IGt?quO8;TPqy86?yUjIJ5ZI zFZeiU{gG+5G)$Ec0*YJtyZ2`yT%?SIo`-UGptER%4ufLu0Wry4IJf%m=-(R$6xq`_ zR5>XFRpiB)+u$XOL2tpk$7tpMX0nd(bp{o(e`Y8 zq!nmeSt^NHufS3xo^3C{>bSR}WP5F>90n^7rSh60WNHT{!%9=Eat(NCRy4E(Z8Hc_ zZ3P~C<0XfbP`b94(3T;x`kO>-vmMqUY9S&G+ogK074cwEC*Q%&w0Q^<)s9&gltncZ z37ab2MVw_oUXc&wU2&6(>j+W6r7`$!8b^3vXNk39Tr%f@VO%!X6N8jB5`hi?f=zjO zn2as`X{EMAm_l28?1=QoJM8sLdGtacvseSnP8q8ImgG$&SPV8{wm+90*R}{*4U<>f z^VZ2jBcZL)nGy^)sa0r;lh$<@1vOH&Z)TD{i($ha9-~|_y!e)(yFex_5piYD6BMf@Fag(2duflyF>(%=rw)zT zw;;wz^xxAccIyE(S^A=87KkZ#Y)EvVgsYA;`>q zHJICqW)rVao@g{GIctmXC^Pf9F51)V2PP0=CE~>!#RVVy;d1AoJ!O@V=+FD33oP43i3#tA#jK_dx=3#9;WsdVse2KO)0X8)e+IZ=xsN_6pELIitA&%Mg`-Zble z1l!gi-KkqlI87JO9u0$mc_`Yq(=`v1W3&s^g0X8oKp>)Yo;VTLG;vc-Bnwjwf))B1 zCcC?dNtOL>N+r*dRy zKUr{&?s~HNx;}Z94*zlkCS|mN)3Vh4@G7kdg|+NYrhXn zJ%+IiI?)8lhU9DP#b`W(jfVfY9voX2%e{_E`_S!BMj|18B-d@BQNEp?c^fn+3Ndl~qA5o) ziZ;9VyB??XYk{Cnc}V;{?T3Kn-v{53S4(}2PK86Ui?p6+v^MTvEct$QHV;$3?W{@& z;OGnE4=3HM@eA+Sn(amnglVJnWz=E8O#xGWT+5!gHb2C>QmNak5^g)2FAh6!O5@nv zvGFQk#9lFz)U9T?vKgE`=nAn$XH8Zyn6ZWZTSX5Uql+rb5S|9O46`G)p+8 zNIXT?CK%boUh=F=wi*;)SJ*+pc025iM^lvXA`7r2A*~(^4p9g@OYEo4vS!DG#SAIh z0t_j$t=%G3G^4b2hOpiZsWSsAOcm2J^>!6vHf)Hj<%Q%+-37IH{qT zky(_qKO>l-1!K_R#SnTO&3r6*B?F8+>ySLHMcNaG7;TKZpY z+Ep{m98Z^v6`~zd4ULYfz%H%M_|L8(1u|5<|Ibi$IDq8;F;wlqPxo(r{Qq6P@BdHs z_vb4stXn3{-{Nca!%KA41zkEohGY|5gPX$EiO}=eV;d`bNIzm8|`lB zA4}CXZ~q04{u4Dob`1+2cmHzPT5fdu(>1*L{wGxZ-`U?J(lx#RP>XDa0$Bfa4a3S! z!-4578WE(iBhMl!)gwO35fPV~y87>7Sw}Ia+gls+(e8bS;l_QMC{MEghT4TEs>Lcn zoV>LdQ{viaDM1ElZig$@c&R0;hK(qsXwr)i!L^kCnA@KsL)Avl|5q&%g@i}jrX%xx z_T_)6MK~7XTt_$#ko=Lh*rbmIpFA;^4R;r~V4Te!u!t!dr_Exd4dcXt95 z?iL(EaEIVdaJRxW!JXj2NpOeY!GlXe2yOvtiY>kOIekud&wSrp^FO@neb#zz`Gw)z z9TY^-R~{6`@qdF_4P==Qi&J&%Y$Mc-g%9|dJu(j^2JUf+KzWnAm}L~t7Yjfu4mSl7 zRW*>S&!TuQZ-8ax^t{xdD>PoUYN@Um_0n!dE!BF`v}|YrA+;7pF6^`-i%i>H_FN4U z)ruw){MuO}3wnG5xRu;RsRp$1=yoi=>Z$~6>+q)rv*e{}zLq&|U;%f+m56{_;L@m_ zJ7AxY&X%`SuhjMexK_cR$b(T&K|j*D6+a^S*w;xMo85!E2~c-9CCO=uT{JNAI=NKo z%G}SSSn{;kq?*YsC&C44waz64pr&eZv7gm5;}}?MsqZBX2fxlJB;vWx3XpUGc>x+K zjZ;%|tR5O#`|Xz?5kDM_1&mF!i=`obIP@i^AGuBQumJ(Q%hu;cULciFM~`JVeF*xs zxW!7lr&NJeD&JT5UB|{{Myhp<<%QwrjxtmHw9PuZTB!S9(bfuYK;eDNSk4Q8UrBbO z{P6DuGH5K&osm9F&QA7fOyo7gmAr5}T-gvl5>Ml~FgpQ)iaI3C#*B5pVUIjtj;Xdo zS>8$3P*Xpcv91kxnU)pt{FvS;z4t089p)7QegFm`#g5d1%dc>34&wcjGA5GqOH{-+ zkM~32fe;up%CJX^e9qTMC1??0k&aXHu^771d4WKeo=Z61O0f;@TDAdvb z>B&?*;!P7uca$u0;DsAd3|5))D?qc(BsiJ|voCZ|rMs5*Ar+o5{rYa23c+XWrxvRH z-Bdl?Pmeelp&D0+YkStye1XD&kxQ+ZNtLf)a~i8jMd@{4fbq=qS)7e>5ph>>C!d}4 z8>hG;(tY_MVO7~UPg3>qvnVF9bO5R7MG+;^EuJX0rbMvle`F28akSE|R3nN8t_-Q2 z#qSQ#GwV>!DP?{xUY}3Sf(E1aA0cdt~@W ztwgF7-f$4H=_w+*?6HRHy4-RpBNwzHxu3_RUXfXo%+^SBV z76c3qG&;Q%U>dvPT+HAIk%?&Xmygle7xxLzXPb_P{He`N-Zs`W3Tq~Qzza#w@Z^#k@~aw~0Qa)S2s6(gT{c#B zWHIQp2=mn}Or$pJ+o>fsW?A`OR@=~i_)?WJbM3GgxP4nSij`Qy$z;^$w_g=;RJO6)1h#0K1sWSd;1RR!R{$}yc7T6Uh z^i(7T8shP+lNI9gC01zqcq?dgq(cboDrfau84{ZID(wT#k2I&sIuKb1bJFo;tLviM zn$bbs2HzBIn4A@l>YXq`W~$ju_*qKl*cW!iznVpSml4-<&dQXf7GbdVt*BaS5c9B4 zZY%zfvzKk(zJk-mvLpY=4uEWPJYkT^!)G!KgSS&FR?k3U9S7sXBIczYU!fP z`0;knmrL&6u3Bp#hd-x0o4yulOO{y}-g%7oi^I9T^?J3N_tGB2&?Jg4TGJpeh9mEb zm7vhGY2}}^?z8Go`~a=5y6*FOpW$B@uv$auQYMj5et<=Wqi2L#-utWNx{{YB)o`ah zSF3~r%D>a4*8Q&`KUxBmK3`Vals}T`^)vQ#P0dI6t+`1#rd>*IUsl9T?GSAOr!nDu zhBYiiHEV2(3jfO0Y!i8#AC_L;sUTCUP9Ldq8EYL$oF30N!ajo}o%BG?Sx<36HUg@^ zbV*pAp z4-W_Xh=C8h?SVM)zidX4&@t{-SYPwqL$eqapE(k88}66I6`n=;qw<88rj-v2DRQSNP)j` z891kKP~1+v2v<|)8335dMve3#wcPdwRgsIYG#;)QU|OSTsC z%-dTd5pfKl8egfkavo7(a9OE0QK{21I$z_C%es0+1tr$3uh8&)5tf)9yp&`~6C z`e27Idt07An8y6UT@jaJenxmLh1s*K%6EfuF^pBqQBWlY>lSyhtawi3>>dec5uaxQ zT2322aU_{txzt#f@>(AV5Fd!p56?~GPkIkpxT3dCSYsdn;PvSGf* zq^K)t^ABw=N1be^4X=)#Q8`J((2snBqeHX!Fo0g;-UX{wuPUH6brRuG8~bi;*|yxt z5y1=R!@_9-dh^~|@(c=h-$nC&t18XHEdFA5DCFyV~r7?h**8Uv*Tu^kLv5e=90@a zZMxm%-9i4(9%8lCB|--T`F>{R{w0tQ#qH3Bn;E1E55>j*1Q={4Cb$gQYoA!Uqp!nu<(|x8BlTuq5-N6j&$Q zx^R2XpuZU{0|n%$%u2g#^%(h{i+&KnK{9^LG*qsDjxAwm?bEb(;km&#FyEFf*!*## zVJD(9g1&gd&fcL$qxV|{f{4R}lR?>jbS8Uewfg5tV!_g`Bh?RduJERCSS>G5HtCiu zhE>t_XUx8>Saht=zu@n*yjb#C`K59B(Tn~3{m(Zv@sy>C>K$XsV8z#wE1F8+yayGk z;B&_XcPN;~#2@(L^cVF$zQQ0;L+m!+j{b*gff@$0(}*~KN)lcHfC$Gsuvu#%;i9?E zP`q4n>KFJWu#-Fqk?xrLIlP~tzxE_ZGv?*gjW#f3T}SmJ_#fYrh|ss>^}o1CP)U0I zZ(JnCeE8kIt^S@BgD$(M{<=ueAty@b{HLrKR*k6-(~-2q<9OxEv1Z31K_~4q!GkdB zs8%SZGM=T_I>`+72N`^mZXprnQ$W`UXFyv*yc-!|JTzXqKkg}@d%MAq`}ViDl1#>U0=JkQPauvoR8O`Z%JK1yJP!)TOtL+P3MjH z))<-UXXsnfYhv*e^ey?CrxSF(Gh6lQ^Zy}>tRS>a-%}e4DOgb5jRg9Zl=h;er3SGB zv@k$Zv~vT|ypOu+q&0-@14>r3bLOA{Z+Z2X!V5MQ!Ca;Ai;RR=OX`U&fQ#)U%93sL zG@Z2GBtGPqL|7Tb3VXARmZs53X;!5bRti7D8le{n*Ey?Pr4kwNTMBeJ=tUv{9?Rn# zT(0iiqo8FY69#LYBp$aVq*0Wh2!B`@uQ^(-mEyTOQJU+-tWlO9@vTIvFkRSAsx;=# zwlcqFl)0?tBgD5-X>-mFniXp&n&yW+HJIWri_r%P_T&G?B3s#SK-<%q^bD3|`O!3| zWB)#X+)Bwr_@f;ZQ~6I>v7_ei$IyUo6%N|jzY6FQuoj3g9m{0~ov%Kl*F+`BJ!Xfe z6D{ul%C>3oM#)qaZD) zLB!Uvk*pPe*7|Hz;3qDRQZirHg%t<31$uo`n)&6t4#O{ZFS74f3iGIS|OQls8m+J@V z`(rL&1xF*w_3o--kRo2^p#0g?&t0jvPJ}ggZ393?}77`$n@fFnT$_a z0=-o~KWa99`j6mi@ptZ3K>+0qMe+r)-!PT;qkYu! zpw!~pkYZcPJoYHiG;V@qAPE48j=L8l8%$}|D37c*+#6S27tUEcjA}057cpK34d|NM zk0!;r4X8!BOq<9XIdqTfoi56T?eDhD~@ez_&w?iXC<;!*A_BEKdis60JQ8b@>LElM%1zG0x zdm^)Car3^tk7Cw`|8%9`=bi?n7)7k=!VH>E7xUJ-p6^Kpu!gSZD zt)z;PRg$G&*cFUL@zQY1-RjXOjNT)e@WaNtdZy&S3Ne5NZGj#Jw7G;8d;Dyg6Fx=>t2BfoV9=>T7)myrY|}wSpNUyJzfS04f1v)_+1VF% zwWbi1R47~Fz!f=zr|3sXtS14!j5>zaaqG<2-+!Wta8mw5KIzccyOQ|)&xUe`XhdJr8a|&* zpb;#jEfr+29$r&eyE-UIb|mR1DO67ITv5pTI3I>P601oT-52Hkb#U=8Yer(KD@n0w z3~N+-r1rJBA~5`em^M7MAih{mPH;7@9fIKNyKCxr7C)6BqJ4h(ign`Skbnz_Lbiyk zrIWMYDaG;QO{?+9Kzc_RA=fK4E2WlGf z`O~(7m-xofL3b{~K2}*$r7RB&4}OAvqK_Uoc3nr##PG{s-Xy!G_TmXqcYLzLcPf)y zr1G+k$tRNLKEr}90YC5PM&ybTDSrsW^%@E5qQ1$Z_2p6cV)qQKj-p=5mn8aCekfz0 zdG+}ms{uyLD%H)+yRb**T^rFA{l43P%~f%n)qNc8pWWOOzoL!HC^82-?}~Tc2^cA2 zO(2`uu_t`Irn!B7mhsuYn)4JT97dWv=kwdmy|Qm}UMh+3>?IFYuc-9LHyU&kFjKk$ zA@_(ZZR9=)k21}Whm#@9Uo3ieH@Y8=uf&nRoF~#9>4L8~wb1fTG%)43(#x>=7=TxZ zK_#icfk;z$K+|0mtkz(f zQ)J92;B(wiDh*dW8!TQyAngIL!#Nb23#DTt1SyH8_(CIKJCuql2+k#pVHnr}d~Hd^ z|5yrqyb5D9MSsx>{KtLJzmEJ5*KJDh{1?|avX1DB*1^L5ej_T2R+pfh7(#rd6JDweVdUM zJ~k>sl<5E+VfxbYc{XNfjmB8##@a4QIvm7~{zcEqE*V^%-!X=Rv)8vE zH%>nkj$GqD3^~?%S)iJz@c@3U7P|wX^^uG1vFKgB*X|@wgV%X0&G!-3mZP&V&c8+e zjY=tDM+(nf+wNJLnn9S37t`_#?=SR@yW5*yQ;T=43~HV)xX*WK#D8!)#BJnzDdzt^ zhY`X^<>_Scv=4G<`Ej+v;fQj*8fK^RAN!z`A1%(j_iTc-HW+`eAN;~1{#*SZdGT7h z^<>ePY-rC);DTTd38i60TnzV;(2Nhy)zP7yR_%w#XL ztg_dm==(pqeybmZ@+6^=e~yQXIo;2!dkbIiK3MjWBm`V}77z;E?75I9pCXTBjb~)Z zdLr|t@#FqOACeYKAZ*2ddTvDlN&k z)<5?laTINBp40|zfcV$C|4knfLk_VQv=7Oj%z=5B0s$U|>J}aW2?heg3Ijt8{Y+Q> z@iRqeR{Ae%yNXA`@>Kt=ydV*}C)fih5wi1^n3mi3ZPpI*y8A~0@1b z>v7VSCB71-r7F2`|Dsgan%wfUxV?_nE08%pp|z$P{*G#YYkU?!k{N9CwU%*$zc2Q2S%WUh2hE4@pqEGwx< zP7p4E^i^8@c#9bC^})`)vZjq(rc$B{1wNt5`1h^A?+r3XjZ=SY1^)OnK$oB0DXKk#7x4V7E$^DZgb0JrI9l64H2~m@B-{w|+Lgj^3(>k1Q=Ks33 zi}dL;rKH%C8K%u}VDF?~F_ajs?;2@nW&fm1{E^!Y>Wm~_k2{|f{abCVn zzK-=%n{BHhtayUqPO|RgswkJC7|9-VoYYOeo8`ysE$Bn^J;a#cDUT^GN&qhuzC9L} z>VUE{q(7}YfsgX%YkvLgM&js{?+jhIJ1I!p7C9R)(((UuoIEwiz}@;^r8<6oxZark z5pa_buO?=`2gMUFrwZgf-ucnI`ISGCdVySCTxd^lG>-TWr}i0im-q0Gr}n?`g-ntE zlP?5$mUbdX@ULCo-^%vxT-Q}{{w&)oP_+~e_l1gre?Y~bL_s;vOd@L9FHO!*9n=Jy zQSH!O-rATKBQiEc;}zLc?^7DJwETl->r?L&7Hk4x^M(*X#&i*A1w}ywoC_l`W36nA!ykiy%b}i zym~fjMcZzUGyaaHGyx#2=dGt$m}Rd2)oqF(d^lyEKP2dIzaZ*kYJyApj2}9st%Dly=J*b3ioM6Jk zF3qY|TaE;4Vlfq1f$O_qV`mNEf-9;TDAuUgJm7tU2!*W8+yd6%?k;<{7`i$KpMGs(3sK#<+J#B(}$!BwTMw4y{|`ZqUH zF#=+OTM6F*8avTys<&GhFB5!>^mjWCqhbt=g`g#xv{W)>IpkE|s;Zn?G%IlUSll-S z4K-o3RT z=dVY)0StHN$}@T{y1as8knce;m=ekPKK#Hw47zs!+Cso4(!v=9!K`(mj60K4{(Jdm zM`I)_i-V8Z>Al2T)&!4@1sFN*p=5VJwm!I6K8WJCll&EQn5IGbG5aU<8km~kmvTdmQ=)gGi3EK5U0 z>LSHuLb=E|N6HB-Ds`Pbq8KhLM}>M7taLX#Hq~7lNb8mcw@z9%9ekpkOR**jh_qI4 zIM(WZ*yH8|?oe+4toaOHvfKkV0^I^ZQGtL3^{h5OwKsXTezfs%6Sh+RjN%Z=NXv~F zuAC}j+1M~wJF@9AyGmj(K-=Rf)2GN|O3Ge@k2guZ#bOv7$wAZCz=+(VfZr^M+hD~I zpxFL&p=bUoM0&INMjb^_R+$eRN9=M?0U2qv;YJW}pWv6c6|ORsG z$moGS-Za2xl*j}TPS^;O%#2B=!I2(Sk$(m>)V?9Ncr|&1>g1_|qF^OU2Jcdgswe@> zel&+gS}L&{Eu4<04%7X5ie@vHgn>?VLcw#M2twr^h)Z>Oi?_rC^8c1?)*6xz$=uac zU!O}lo8gQt4tO5a8yZ#1oFyfON@yp45IvCK{$3O;r~+bXV6-<`0`oCz zQ6jSeJ}5e~EA7fi!__m)ny>(oAh(g^7nyQSE`GYJ(5v0hoTIZJ$?9OPItLQ7I`XN` zO1m%y#Z)}IWtqs!34H;%RL50?LA#_n@~}#bB?NT=?%=l-vSE@QShLx}^C9_cwM(h; zFMW8w#sqz_qE4!mdUKdm;Mms@zByjH>N36>0T%O8sS6<(-{|n%tp}G(xsaHtMr88S zqsSr~A%(n%U2UO6ZO6AoZcvJi%cQ&uN9#9A1Fk)8XL7L12XC|B1*WFCe^EJ(y5j9i zao40SX9d_(hA13O4H28s0vvzTrzM-X^%A!Yh)Zzpgv4!)>PR(AsbGPwhl#i&DAs! zVi+GNuS_Er;S$2wCnF+f#wZ#AQ@vVel8in`y7@vglv(cD?Ynv2kMJLlm=Hd22LMkz zdd3amOLAz=hkWvv?LV!Czx~+^l-Jc*_-gCEsD*A93DY4mb@H`Pd4l^9e}(cBwXS5% zZMOSVL{Pu%x5gm?$1qM3rZ_-@a(Mu!_3K|0yF7W4ALDT&vSz}Vf(LW!gdJc)4OG^2 zzUsQBET==!-n;$$(l#VX0D%lCbXCC)RE*6ClH5&r@r0ZWYMTsinrS5S-I~t<6t4j! zPO652Lv80SwG*z}G!Sdd(UP#!SO>n{t2RNT*H}`SsKe203~0bN)OLG>(olr?BzyB_ zo?*-27r}bjXh{83^pxlU%vpp$bK6_KJ%7fJ^a67&>v$4wYhrhe`L)51mtP3Gj%?IA zmNSYSKH}bTIk-)rT)F(zcKTVPzjUxwujbbI9Y3@zZo+^=dO;gU(u}Xb`v-&6SEK+z)(1@a zg-4^UD}~R`Uy=G!l$-*d*?jUa^IIQMJaw)Yd4=)%eh<#_Wb$hQ=jV6LZ|Q{Y21XS3 z>pwqa-WytDG%!L0O}bWhvAen`)6Vm+zloy>X~apZ?I}R^mA!HUA5et?Z%%99FtF?> z-@lN&LgGIO=b>YepW1w(mG%a)o<|T)Z-Q3yo&oE;E&#qqISbY-%T#?%3K+R-fbMOd zo1}w8rEd-&1fUAU(XhiMl=Re6uGtkp*u)UC420C7t4@nshiW1fKr68Dl<-^{t^?Pt zfyQcwnH@J1(i87`&;ap^L7#iyCMxOgy}}GCjeyrcu^!ffqvA3-KuM+z*JZQ}u>rarpm<+%r)L0F zTTN94!gV1Q>c-xvfs8}eu06&M)oPpRRu3yXop1jfH~#j^(g@(zZSf(f&{LI#ydypxbNxtsfvF^N); zAz5JbHZa#kfM7IOf(RSfm}02S!CvIeJpxn^O2H*cz1jjA@+A?CyhOB2c`k^}k%8^& zZnEnY3XvsYheJ1GN`Jh;?1e|iiB4C-v03FyB98XYO0sBnOSg}4Oqmot*5@_si;L=EL>xXhPF9U=ptjjY|A|Kp*rfZl&7;Sc zb>@==TkjHhTP#~qn3k0+hLew&RItDUL~hksFe#Gq<$uSN_3K4;L>mvnw~{YWTGOEt zD1{-5Lh>nFQ=1-$eyI-s3^wzNb}8r$(k4;Kl#;HNyUv}Tmb1L?W=2Y@lR!(>X)v@hV5 z%8O;%t})Z4D>3Tb2?~hCo4p<-nX6m8d|4p%`)3#~nyOk@kivH`X0j$*e?tAn6PKARSEgok4LmPUo0AMACn}mRig;Eenw{){Yt$+-g^jQcu5R`~^AtSCjus}yw?V%` zGbCv{6llr6l;cq&hKgXVgPUOrTjQ1TUfR-lD^ZEiw51pVtvcF#?P)}uX@quA4lCI& z3Q8d`FMc499pvK~Spfz`MRHi~hH0^|# zV8BkdAJj3X(c!khRKlEf@Kbj=OQAtvP%um}n(ez7lQNYc$;f&pH0DKA9aUs~vAAPX zz@rn)zWgo#3T_a8ILsDO$_eIBH|<+g$_!N8&-TDOXJ8k&^WhrDfTV*dTQ-Xl*lgJL z9P|lr)TN2jO)w4MEWyBDW%wS8WqCr8Rj=cEDZ_X6siEZ)M}dda3R_ycv%C5WnEVdhk%R}|FMM?v$REJlcyHBta+t^PUeHEukq?H0qSwR%p)ER2U&|!qz4&!-_C}3uU#YxYO<P$}O!;BK!{v(UH(y_hy^ z>RCG?CUFE|qK;r=^*Nu88*h7<3)A+K;0!}nu@Jg|V@KHh1v9R}fMVB~6wTRyo*Ani z_|;j-`3#)z5)-&Rvx-RwvMN|@oJ_{ObNoBAmbv4}G1EMHt)}NwJY3Vw_VXAT{U&}c zkM*c0tK3uLL7Zytcboo#OK$s4Qc19oO` zEYPQM7Hha>1vJOCun_UJ+@+`|E(`44>c8Miw}Z|W!lcF!HD=(Z2NAgDAwkQ;G;U)f z^OpLAm1G3SZ>K^}+Y=s$1?LuIEzrKG1Jmosl&4@NalgQXv@c07!&9N6(Jp=86mM6N z8J!?ccEFQf!|5{sPL)a7qY!+Gr%;DT<0bU3KJTgFOC`FjZsyiq1U(zagI{KRwt$c{ zgxBz;^d8UZJ*y<2hK$Z6e*@Z?3b5%psGp4T_y;iz@1jt_VxI(X_@z|!bK1Q_95NOl zCn~3|OM-augfkHDdKFaf)d(EMML5QaIRZw4mu1Z0l?!xg$XXS!5Hq`%v|Xp9G&rVQ zzOJ3FUUF^lYkiR}kjd;K-*Th?B;YKrpoHYp`Zxm%^CzUKQtW?_wFHoABwz~!!NVmD zDPU}ZHk|F@2}VM$QV-ybV*MK>=BOWgqFTT zF*PIuX$y`WHl<0e&pD^i1m%+lDI%H*=5)h7TQDwc44rjd(S5ZyW<&`q#v>|d<0R!^ z3@qdXk|f|@&;t`_6*8PnV>6>HZW7*Q0^cPW&pu*G(pd&BYVHIecc`N*0Bq*Tj~ASN zEDf&L17$j(X%lv_lrorIdi`f?Q2_@RIL1 zz3k@&OWW8x;LDq6tVYN8lt3kRpw&klGeaQwM~%N{kJQ&ANrHLjFND$pv(hELo)P(6KqB5M zCcfPgdS{N1>w;!ioIijE!F?^2!<$v1{w)hnpu{+^)xq{GMxcZgaw>&+VeWDE+V~=XW0nDi1z5nnb{w1){{+sFR zO~l{{4MpHO%sC`fih!n8&ZiZ%GI34QrI24I@w;@g8gR>pJuxx3Jf3w9)tEbs5sm?-Cq?Hdegr_#l_&gfXjWFuu5$!de91rSk=yl z@6=PvHD>Gx?Tvfj=}*~wUIjZ#v-{mj@IL`ZH;t4ZLet{dEtlt@6%=S*kBz#VRy|;d z)2);s$2Z+_T1Y*S-sEOkAO(8Oy{;XJUaR`ZBMWccwsu{^*75Gjm!C6*JP2ESg4+28 zje&Uf_s}xhwH`o=&r74F@zjx$NFF3gymO=Y`$r5;gI|G=7iWxAA3uEQB4dUI&lU zEWg(7eJ&7M@c2}O9xir%?tU(N^|jIC*XZ{C{@1S%NPIUHM)%qkjXLidH}6`=|plKfySq&X#}C;<(WA1*xZ%h6YeWJBI9VwQ|)9mTt}DNamJoAH}bw_Ao_5T-2M{# z-c^~qs}s4Go8uezL|(21P6;0m@lLt`vg>~GTcg{39No<7MryC*`$k$25?@mTq{1lp zNIe$O;zHx29o;e75w))&X3dlgrEqw>W@M5Z6a5Y- zCXXlB^Zdq^+~r~C7Y{Wh8_A%Xt!MyZ{CWs%x_8SfuQ5-tKGlJnoiH;q-KL4ho=u8S z3fIK#DSTOd%Gd`s0_r!fRo^ei7+mQK>nf8AFBi}#88nyVc(&X6=ZP&ohoDTi>&xWL zr}^5u6kwfj=1t5jzM@9p&ik`Reg*u0Jta&f(( zf_>o7o*{3ij~|Qrt>&Vq1P4?o1&L@633fclu+wOpM&$FDIb^U z5@REJzl4vIg?lb#KT;+d)?SXbSU1rp*Lbz$Dr6vjy$5+MtWqvw_3El&c*WR4LKMR% zg>vuVIQW=kD?ICCt2=8cv$v=25znJ5EM(LWSrK5<=({zI5CV~?l((3YH&Hr+is;=N zVzfzHdjjPZ6g@jo;E`QX3DZw#yqWSP%ml$_$UqrksCiB@IW6&kgAK7QoaI|2kO{q5 zt3|yaM+{qf(b1VbgMeOG9yzVSn? zl-^vm`IwlmDL-hj+3h=qsHDm3npQ7ViUcn>sHfn1(ke)}Q?;)|R%CxG^_J zk;k$=;uOX5HXu#L^X)ONzd8i$OvVW#R=nkt#J9DueWb0hNS5P2+(}V?iUa#Vwcs>8 z6r==!_4~A^EZ9>V7*=~S%jTW_UbaK?VX5Q|GTmT~`?np%zh%L2rr{3q?R}W07$EMT z1EnC=J8Y7KL3rDur8Mf?LdeOf8dg$`g^j0+C-b**L}LXgW2v4p_xg`!7aqs&7@ywPLIw zfY>A!!NGf~GKrUGYAC{yOoeJcur1uC)Q+jAhUEo*T>Oy*({=PvO!rN{oS(BDZL|=) z4`uu^7d*IxJ-{l<-$Y&m_5L^y{h&{Bg^IQM$^^w$pq4$zg@gXNLrRNk;3eA&=g@6m zYk9@5A>fi4hlj-NgmWz@7)d^1mJybXeTG31Q@iagoR4wROu@Op=z(m~uWA0e4yd-$ zAXV(}gQRB?KgoT_YU1Z7h}5r?E($rX6T<0uKe-kqU4M-!Q4ZaUg?__SzChF+6?Fuf zaD!`1thP?68Ir)HO`5c?cz2W)YFNL#hB!V&*nZAqc9U8WpF<(htQ4<&{6$gO&Cm@? zd;+y_=l{uA0>)00B{0bqZMh7}r8!O1 zBHnINYqL^HD7%kFA-Lq>fALPaBcexx)1a#4#353zgEE#<4cXay!$hHsB|+0I?nka+ zxJOhlBlpW7*5D`fC|#>}F)f1vG!A^0oH@RuVvw(ZZCa3$kG2ci5d=$OOB29?N*LjA z(do&h(`=)6$aUM&EnAb*TRSbNS1p^3nB^s((4({Sqo#L}sgTwPE;j6DOIP(Qc++Cm zm||J$83wWefEghA6ceO(o&$e4jn|{_4~(T5o0wzwM8Y5QW8TZ>(&C+EV%H8x;43W0aFJN#sk3=;}cja!Y3F@lSDJ^h$N1t?7b6L`_+h%D*h! zb}~B5^ObjphB_)Lij7ZMu*)iw-*F$`^OI=k9TG0Bn7)towtG2d#rrc@sWTl^m%S4GPEOp6{g$KXRw8%ajO2LSpkc(>0HdwF=UM zW^XQ)aT7b_jmsA)^C^5^!F$7sZvaI{=bXg# zWsUcfP?wGJIlM5`W%EvpP5qb0_aB950W9pTsPVtvOYix{I<^0LKgJ$N(qo?-co%T> z*&lE!=hVy)!87G2V9;OFeMvrdtPH2?UI#)AoOAy8bJS419~+0ZMivGxPJQrqmrW$A zYXG&Evg-&@Ufh`Rl z54TxyarDbcCEfUoIZ>Ka4;jN3b9|q~K33!hLqoj($z|g}Bi$_HrJE|SD2f7^5U`Yn zsLX#1`FG4{{NAA7n9)Mf{ag392MWg2!iVKZ`j&q(quKqmt3E%@i+W{N;!-w`9W3hh zOZvY4$;#gIP5g}-0;`U-<_CsTF8tm;y0@m<71}l2iFoCZ`wibrgZXzw@0F4;AG8j= z7Y=TZtO?5%pS5s&@-?faeUo}^ze5pr9x5le*I6~p5cL8q;t%VeGW`;Q8-j~KugV+l z3MivJ3*AQEL1NM~aZX72l*_3??~6?3M`{B^rDSj>R+dL1kbIt=SxYy9qXAE17LQ!2 zF37VJ9j2H3iNLJ<4I0{b10~%Aqag&NYlOnvh|X^~Lm?v!o2tuUn%qM{d7)E52X0!MYc^%< zi_7b*sW@neS7E{q%8Xvj)uGClNC8=%=P;hW%kEZ~tdrm4v@I3c z7fR!qIe+)!mQL*Y5CVF0*T{4>p3HLBKM^>}n5pio#fJW^(1Y488SQ0~ab?(4)NeoT za#?}V=>*@4B5K!KT1FK!Uju{(y@R?-@ zDV8>!*-1tQS*m3FzHdpF=QviGb_ihx_R1E2LeXc;iz7r0ae(VYQIPe%_8}brF3JS1 zox~0^@WN%dcS(~KM(M87;G_UZk;T5VcWx#iMiBI|J~AcTC zmzKlVK=lW26DimYsZ83$6^$3F_R4FkKOV3%+fkzn&{1G>A*J!LSk0sb%NiZ$W=<3& zaCPWITn0BZvN0p$7A-^MMzVRLXEeTksNSZq+{+~~kk&X*F{6}u(Q3{QbqQ43j9`4a}I<5hGCm!j>J&oZr!EDNOv(YLp`2GjTceDSnzsnS|$ z<~bDE4Gq@RP^WzXvLeja88WrYS2O~x|ihT#*Ql8@P zZQtOR`mZLN`)2lTZskc@G!>e3d3Q(3&F59`_;TF^j$zO%KZoiMb$Ik1CMF0op-8}J z6S7S}34cqCmDfr&%;}zLDI=R=RRlp@ah7+o8|J5IA8FWyTuoP9364E8Dg#)Y8EU=; zL3|<&>ws^UdR6Q3D?%ma3Y=q8=XW0aJ@mvLsoey^_I{x959v3ebF?6CNqMDDrc7Ss z6BZm!y9%1D=fe-Y_j}_D1Wa-@Tn2ty^0e!go#31C2z;~V$-Bol_gr$vY-@XkMQ3E` zIqfB$koONPHW9JQglkJxg`I#;`!gESCv(q3s3U;@cIbQMQ>53jMcW`BMqz9(DD>y| z3cp|2kUZ8S6IPy1;Z4Z1Tbwjw78ibb_Q#R_!2aaJ5W?YaLSe@QUF6?#39v|EKl>$( zzh$C$N4gPQ=o%(nB(wB5R5!qV1##$Pm>}NKLc;bZn-FvD``INwe2Q6o9kp9L_6Skk zFzh7+cf8a-4IPdn^TQ2xTE@dGLOK9H0`Zl7ZK45{WJ!=wkZ5J)?8?V!Gi{{2fJ z4VO=JH_fzjAS?(mFH$I*K`2EI6}6SdF9;Y~`UZB|A0Q7DQ}=+~41zcZH$)2K2Uw4;7n6Q9TWzcMTQI#A=fR;s}I2rXo{E;YdV< zsg~n$UWWt!%l~&QyCEyd>c=?Ln){B%a~ z%LB_EK#58n9^(lY?-qdQ0vBl=8HLD5LLW9xrE9xL%eb#9&x`WT#p?(wyb;oih6F-^ z6zhC$ArCo-G^-CGospDLkAn2DIB*)1>;)fg!WXI2El9*1pz@ zV(%@2$OJ`s%-pM5Nka>rcr~M0B_p~R37PcbK2gODmn+F@phdREJ)~GeHsZ1oEQhcV z;GUt%J4z1eC{ZJY#L4B)+&I0YzXH3P^rJ`d`$&^+VNL*Y-=tq81I(-6;)9cSx6@ zbhosqbjPB*8v*GQl9o0@>A@9 zcxy`+^R5Io=oBQ-H1X+RJik=@vu%QxBoFv(g-~1|0g}<&noks1h`{}{aymp4b4iG< zN!qr!V&!1mBI1p_q*-1ii-5#y`iFW3DUpTo(-w)+mpHE5v|6?)HCmV%XpT_-luLcR zJ?*47E6Fb9gtx5-edklz+(ZIe@kDe8eQ6*A=Qx}ZX@Lf~p80ZkRRiF4YZnATew zvfz+QyzCuZ+-XCt;}oT?@Hk;{Pf}G!;fIL%X{9Wp<(C=ZJ*jmDnc5|ZO*_Ibf-^A* zGndcOTMdM6^=Ee3ib@Qn^wwn{<%^1JCG_TJp~W*It?Rkkgrd}DSX{~=9%rGtqGuPG z&S*wVSKS6dC32AAvoTim91L=hC37}gbCB{`+mCXPj&p|5vC7@h`=~X~TlEmS*bark zlCtd0H5gw%=V10{e??|TO0q1j!I)PB=jKKG^<}4;V5P!gP2a}A1X&W2LwLj7a6 zTOMK(<)KTGpZS2dS#X0s%WPie^JcIj=I3Ap6znNVxH1<`zb)jwRRH42MHo&y+5vxx zC=|&6vn>>0<1!|6vtbxyRU+ab;r_|a@~iFan#t^E((HGi000O!+jgd*4kx*7wLi_l2XOPOAm7LGl$j2F4f3=y|^YJrzZ z%~B4@RHz-o%|e?>Psnz5E4R${jr9R1m4?i#X9N=|l}Uv%W-t~+{z{^QVTgR0^=Ww5 z$4c{_W@wZZrR>tmm6YtN3RgDm?mpirgWBzu)g>qaSb**d`{ym873xB-K|>lf-A*#> z=^{;q>Y}3pi~C{+5^RnQ(`tMBjdQxs4;p5y_BfgsVF-bhK#YTy`HcIVZ9CN%obBVB zi|h)=ZrWUR!#YKqu@~oizr}n1jv>Mn+&}68%d;Q<7Vo`e9s%_)JRX31w#qX|kDs0| z@g@2D=m};!8}SNztlj7E=$Xj-%6d7vPy45jac?lwRHcZ2_X!X$)hmA$;P7y~o!cwV z!lzcnZ*^Pzy?P`AVQu#emAFmGRC{3I`GQ^I`!`G(Q(NateX><&V%}TtQ$+y`5r^P= zmSkFR7zu8NRq+-+9;|n948P}0D_!Sbe(0fQEL*U~B(x1` z588ZY{UVrXmp&O=RQd^WO3P!v2`9*V*_qf$u``vtuWw#Qo=ZtnFU3ModITS4ir0z4 zY-jeVYBSzl@0J7KVWjfD(eVP3X7Sn)WAcEc*_*5VX0FChhXY*V%B>U5&~F!8FQ7Nq zUp|R{ExzblR)13tMMixrn=z<^IVe%IUK#LFlDZ#+Ci(vLi@%PZVDa9Z78d-s^U+8L zgw`rI94U6IK$x~m`b%ukm9@X7A zi46C97&B{;+dd~rWuqV>OkU*AX}rmuEQsv$&6okyYgEpI2#+5`P&fy>rW`&RAnCCb>6oOqtJ?d%-DdX^A z!a6N#D1@iN2kfR|eUGqWFoUv+(o3H?p+hO2BphTK9y8q~B_ex(P2m0^Yh9A7AE~Ge zksNLebVa130r58FUMt%c7$~2L9WPFSx`k1}>xsuX^~15@>tJCi@d|TV6;NqgPu0!z zL;UHeRN`$hpAEe!e^06KROd!kwDf|{sDxqAk&fey9P6yYxQnGl;60rL%NPOpLXMz5!qMe9%QaYmD&)aVO# zEO%8)y}62$ceiXm8gq6}X@^MJ90k(*y?ENS*g=LVM8hXk6DW^3i+qZTf*pG_Jk!oA z|612%5&R;U%QM&Q$g2m34lFbSGK)Sg>t$GEi_{x63(n7n(F3BbRn~4TUi8zI+kbft zZ$eI|^4hrfMI3U;np{c`p|2#@enOXMfQ#!OMnJ#&x1O*g)n4`-Oe*V0%v{O2K{-Un zInPseAn#jb4 zqznF;Vn0GtKnnpksA^=$s0&qg1Zgm3yrD#J%$C=+_3hCmU)el9tw!9{beURX#Zbw5 za9-^@D0Qu5Qr315JtnAT&PnF#Dr=+1RRiA_k;tlW7?2WJIME>Lr0z0Pm8LA&UCi}#q#a5WL2*XQVG-Trjc~kkvj_Kz^4*`6u zDy?-J9f!Q_?Y|>uJM@-bVHE*8rjBHJ;+Y!rktYt#HFK|}KzmYbYJef|CvUL))pz9Vt38*OK1bk*+@i5 z9rvmon!b03@1Y+8_T0m#D7($-fgsl2JiqU}L6jTo6}1fuV8=v`jiMDsZO5z?PV>Nq z)(k5!X&g4HG&APzV5W=W9TQrM_;pkXL(cw#wMf!Q?mr-B@gxU+;|-2)7-f4OZjbBv zock1rzP)6DA!n6R?mRIh)r@pyvD=mocS1DJXct2oEbQYEu@VlFL0Xb!AV7&V{Frn# zCdcWrX`>bZbDCq?LU#?yl7#(jbts+q;5`eCt$_jrch1m~I4sYpE7)a+JhiK2*Phxtnf0UW?6=N_j2yPR86HjG9nILkkomAQ zvZpA-E%RJ*YL^Yh8{E%*jj!V}k5Q$7S3u`?Lm&ra)N`xq%Ll#{=-8K! z*q2xr%fa%S*pKj#b*tOakk9L-GoL&G*GgjvR4Yn523<-V>N|lqpurf}v!s@>%xmb1 zoZuaBvi;<&Zp8zUdy&KAA~MahoVa({V|*V=W^%3jDCWPoQE%zO=kt@wVB=D+E+clH z+~~EY7nRQ4R#9hx1{*`pu{-wf$R^u;8opfKs(gC2M8yQY=!eJh$K2s zn)~AJ=j0k{az0b2SWD-p^qN4{osZAO5S~AKT>*SoP_TLGVjg@39{T-!38){l3@ySj zl_WVW-ji^dTZi0?)r*7p7@9q-covbji_nOW3#7?DsD}xXD-gT3^o66xQ(LbqkdP+G zaX4wvI2$RDZ+L^*Xf0xu=L^J1QZen9(3$jm=}t3jWEglgB0}lcB_hid81Gh7zpd@3 z!+6^V_#7f@GK!>p2?gzY$7sXY`vm}JK%hHb{4E1IV&$PEN0s>GOg~FGz8$>kRyMZ= zHU(i5^wxl+pp=HJoRg;fmWZMnnpx#+{w33xc!oXz^&Wnk3{N#wLfV6jZI+XcS2akC zlVXNjQSkOjdvx?EwSZ!&tckm*2d4$uhKz#PlmVEAb~GE46hId;KnLfJ%3X*T3eOG; z1q1`wB6XE#$+yraQxswtxFql^Ip3)_sW(u2d524;uY+!DYjf(8DS&7bUNLlR7BW+y zb!ZN2V(8QtJ4Zcm4McyNaZ;7*aVn=MK0NYj%H2hjV@cUjNznN^zAQ+T5!f+l*RwPN zn4sMxxnk^cLmd^Bmfd@E`R_y+@L8t3r@3#n#pF?1k{b7EiTJinKPY%!-1Zy0iRSTDy1k(~E{ZHk0-t~)fOCX`Ue1YU3UB^G}zB&IUOw{dQsJXxezKh;8T zuXK3fpft;p)CMYmQ-7E~a{HDm7|^AIC5;#Bo;OsdtpA!kBt!cKz>L}_Lt7rGTO3`0zapC1O(3#%*UC?!(xW;5gT z4%6ehK*+B8l}b)xCz%;ign6wxYtni?5-PAnvB%Rke}OVGse#-q@vI3KFUP%0hB#LH zm;17WC-C#T?txJyNgjkI`p(1@k4N4A5JJ%NA7Ct*L$~AI&wb zFO67b-l;AjkL*4yOFo%cbx3udsLJfWdTGb~zF@p@oZrl>YVU#c=0qoUQ;nqXzS+Ia zm;Gu$x^0K8Ya7@gZ2 ztvn*{k92363l|Q&xzK0NQ4;!3-WU6xe*FmNh1rIvb|iM$zI>-5zXG>&R_*q+BI2`) zCI;+c0JO16!u6@;_P8sr@yj*hRsKPFwM5|;Z6q(GKjh>;w1HR%`O&e+FUxqkM%`a` zCV0OwI3mB26%OKj5s*0S`w2 z_Y`6GzvwIWZC(@&CDQ$wW4t-)bE=kdZH-epmixy5LHx>Gi0k*h!fvbtqt@^{#~9GA zbWUeLD(C8LVA>S{76TWA6>Pr6CSZU7^c6Y^x@3*7kaiBgTb6s|#!d{i)wyBX6`-$( zXXkfZE5UWwb7k28EXz^Y%IDb# z`4N z_p0+-cgOAOV;2~=rpeay-fbRtI2g)aXzmVDNFIz^qwv;+ESna_tzpIsAsKp$^z76Y z1{$b1qXeyn=)+c^Xc`^}j?1e652FPfjbUs8tU%4e&84N!nAag_-QGygpm~t>1XdFl zqPg4PwmS00YXhbly&bH9>)2%*LyGM0^xq+)(FrW z*NqD7IaG|?h4C%r6p zw}DpX7UGm~JpT?s;zRv*oAKmbS>zaE>pM6(Wr8254KYVh9>Rx&`!62?pn*8;aKsMO zS1*vEVh-0QqZsbKr)MB67`Fxn8nAxx?R?4W695_r0|p4|sm?c-+dl>fpKiV$4RApR zFGqhFAV2|>eV#(m5ADh*8|oH#@HL9aQ2Z?v zb?`u;2QS4ulz%iITNbH>LbJlt<|H3H643aX=GJkz0)4_>LRrEao+*vm$E$fVOtRAa zAvQXr1w_pN$JqBc$^^5JmUI(fEBpnyMH78>c-x^qb&4 zlf?jgh6UyC15)p?>f)`#Y|%j1QxGM9WH4<+o=RsUxu^k<40OsfEvb|bUlVMp0ouHJ z>y9*Nm17pSQHvY2w;0q1AQ{xl-(bmnYlRI+4aOJ87PCKnbf!=u;~X=6wFHxRaK`g> zEfdOmldJ@^!v0NMvMqP6!g1$eQ5{fWYoOA}xRWpkz63xjLxl=;@P5|-NM*7A=+g%l zf-4wECB!GM&lTD`Nf`iexv1|om`+dJ;SsZ0k;uJ%G{8aZxvddQXzPkY$?5<`kX-Q& z&lHq4&R>l}Yjp#Ai(KE9LOVw%7#D7Qy{%89dAza)L60!`==Cw5c~V#grp-f(csnUp z#)A(0o9;%?dw}IH0GBHZ>wP&>4(5Rm zlg!fvbelXkY^Co}9c2aP0#_3P$i;6)fv65prtF4Ag>4DHBT(N=x{-Fv@;hZ%%Sd8& zw<}E0`DUL(UqVGP9#J=%!?;`vYN~DLzHBc#&xdzw;v7rUa}Yw_QPcwU4RFCqK2K~I zxoiJB@8|FJZ3`p}K?b;7T=$Ge?LT!~Yyz~<>pu-h-M(=nj7c^s&`nTJQn0(WMq_G5 z2?d2~9ajgrXAM*1hZ!B-dH&=WnaW$CXHfj)bw4T@?EtwGM=%&~n7BO4D9f(VPa|to z!ix+$>x4eqq!PM+>kI&?{I1Qj<{Nvbe^2WNNW~iW8HZ=0L-3vRc}lDB^MwmS8b~_E zRL}X+F%37E0KH4~%ZJq==(l?vP&iY8kHPJz-Yenw=rS;n%EcN){yUe;>2f2D7)5O} z3*d5fWIp3;Gnom#d$m(mT7C7q#N+FJ!>rTS1KRf0b8+vr!>=kR{+{om@_lvfmKxJai$KW_$%(_VOMcCcTd?KsBeXH>@mpv@Zv@T!9Y)Fm(C{2jnV z2;b`)li06O9e>m}f}w}JtqrPfCy8=NG1fN7!{gVrNqC|RtQb~pI+4*K)#N06HW&Ri zGT14#NNJ*Zu02L5n5muD#2i72+$MOZX@e>yys1N@RwAc=l6Z`k2s96kIr*Mu&b1>6 z4g8_a<1&cwXgkdw-DMElN|*6@^H!tcyEe}m(B@60-C&kUpejuU`u0++PJiHjm8x*C z@#rxe|1a7+K;jXp^2!wmA;BtFfJJp=oE0&vmMiH2Q5|h(#oRIFD%QJ%$ZyU{LNJVP z@MsIOQMU7C=Rb;j@h88tlwq7^~;h99u5=eF;D*1V=|lN|F4;Abe?GcX?FD= z^wF>-6>N4j()`ig{K(-nS;5DbemaFw|v1Y1&-F4U*C)^AP7vgnNq-D&``GAw5d z^`F0&a49z*d1_Sc+3Mc%2&%kQP1_J&Z4Jz>vX(yuNZ?+`` ztwT5vm(;N!*blu>>-1YXB123U9P#<)dq&K4-7e@-_Vw#Ud-ue|EAgdoWHHNBR=y^Q0xZiF=dEa&1K|vNh|=ERk?-SiVTZo5~uE^hLgTkM6s-) z9n0Gk)Br_|R0BiX&3lR2-{X@0Ei1!Q;NHK)B}FdSrC8ZfQe}MaF|UGc%ZeBo@0FJs zHqDFQDgl;Mzx0@C0flT1W#^%LFFev$*lK#8@^4G0&UrZ0W*DC0pn;MZXT?T%qbdSt zelXYe4&;^n*qjQhV(qK7dvajRHSAO;0CD~I9up4L$dUztSOu>Oh3Oi%8zfkX+k=g2 zG>n5_9Dv)4`=wo(WvJxHr62x&5TZN@FuRgs$gQlCnydZ`8?o|q{PW2$AhYY)do+oR zqYAJQ?^!lT^yof^hz^jR9ZL7;a)5;zIkIuB)x5OF6^t>?KFb=C5qL^j_lS7N0!qYl zRr}NF6RGOxH&YzYM3^Aay}sw zYT+IhL$zKW51!^sZVf-Pe!Tb^f5A`YZK1o-G`{J*w{s}2aZ|2vnrEJ$(cx`B%x=iU zrGK&R7}L0rXXF47*R#L%m}_kl-+N3rRE2-iNB`_G;qIeKUWavUc}sg!VCKW(lJGK) zf|*tFfAp9~A>2QgR0~I;TyH6{z2=Cyj$Am&uUGP+fy^>_{x#ved{jUUmdOFv0yUUc zDT(=e4enj&U#c1GBeJWB3h<>R86de$QNZBD5fFMc$c)cZ2h?D{ShBS=ypt&nwMW{^ z{G$jlVpjdJUJgUI?w#_pqSUGRi&u41e~Rsx}eES0|~HJoMaQisGht@`;^%bUZ)}M&+qXpA@tD zO=d@yKCr2BuHsLjVL7Z#%3BN6V1Y=~O_kE2w!HCG>b&f3Q|o}}1WZ65t>MQFhd_|0 z!(MBobJ_!8gLt7=yLxW0DQ_j7I-mA?Pnvdi>Nj?!DZV`2$7lCYR#fRE< zm#6}C%Yu7}QS`@|7A|Q$ae$!ty(Q{;X>7M)zj>QAK+rs>&I&!+ri>aDK>{;1AU5Ofl5n| zAO&Jk7lEoP;vpd@MnEFeJa;V3-&`Y(iJy?i8==vvGtQ?vGzq^?Lyma;UP`77pwK>! zVVMxWXDJVMMrllB$O$I8N@l|dfwPf(lVw?^J4vEXm>O5d(2Yh+K=4jVSNGO5jti^y zutcF0Go$1OwCNBDKLYvzsSia^)+tV0$NDH^WZ?kZYUJZ9oba4TZEN{4w1yoCu9^S_ zgs!#*3^NDjq;U?UCj#IH(Cw=U!iAf+2GY^YfMA*vOmHCaT%ch@0BLAtTfB~l?@&TJ z)@?{UgC^LqUgb`5`JFG!?uSX&b-hlVppdJHm)N+rj?Lk3il>^wHZKUAKAec)NGG13AF4;U(JpZf<$=I?+L~Q#eQG1xk-^jnbgF zWu{y=Ja@HZU2J9Hh$nHJ(_#fo1iQ{rL}xy`B|&nw`z{_aBAXSsgb0>ZP+;ZE*EKh} zAIL_ZD-I#H!0~(i7G>-dC0lpF&i@QKXsI9TpudRygGVc`f%zJ@0cnciVeT4fuxUT;3G|rD2y!Kzz5Mc=Au~%&qJbP zjz>2<3dC_oLlGp2W%fA=qFkp&VPlrUNT>n6?9ANrSx%BDE1jwgm*)%=pZdy^#dzs{}p;^mH^u zQ3@&%c_;~Oi>*CM_A4uTN?&@;YBeOfeAe=&^>JP1j$=J#&?t|E@aI;*4+M+o^fm)t zy#l+?gsFs!;|-NuT&Twe_71yHqvI*b0Zx?ZaH`uwic*&l`po?z=0%GZ4d6GQB?bKE zNn0LPttXO9-D1%eKEN zey(q9VtS-gYIoRs%O!@byI*D&q^L^GOEK8&siB#I7o1nTy(b=emG-jaGp!T9CCevJ z0CTett~-?J`W5PZTm%P)KgWpx|P{l;d(5c|G}uJOLT|{<4HhVxYU#^Qke-+a_sHr} z+YoM>;#kZ>R6J>xO{yg}S9(OA@V>C`ijK$|A$*3{TZR~5bkeU(els`w{3O{~>-70Y zi2E!xv#f$LgJ!PP<`Gr(H&ifqO}%4sn19wNK*Gio95(YJ?2saS`eILcy^2BTEH6xQ zT^ffZM4y^=b3{M+em*7{Loc`5v08&D1TiS61I;xAk3GER(T8z0?BzeYQ52o<3r#M%bzzF$CxxqKsLk+=Wz@DT-7{T5)?N+OWHQr#R@RyAy}_w}RLj=TH` z)=?l+AE9VSp!}xe5vVorqbF-9+ql_g99q)~k;vJMZX?#bfGAz_q)}hYWH-#ozQ%R~%ISqnlLFsj}tRE8GlSS#&)D&wTKg~^+ z55`Fmj+kUSIZi3c+TqD4feuMv#y>pDm=cdR!b#{9 z?4xdy-ml0cdW3@hTu)vccUUjjI74u71wX+%kl^m;G>*aDTIH`HGNqpp{Ue?9T!#v%1|b&gn(Wy$I^qzGz`4gZ8(hm~8NZBHKj zjmdvn{wb;k|64&;p+bKIngEP6ytiKd`0$T2X!Kk{xG43_Lx} zDiST8wLZ;j2@QW_jsKW|)_55yXp>=kGDh+(eTf%UDs=>Xz9-AE@!-0;ju)Lty*rbe zyTFtc!xUVURvfxUY;8S;sWsh)6k4rRJjqb-lTF+JIq-Ej7(O(P>9&cG8#l*^*6A#m zs1+<@4aP8V6u`&GYsNhckM-HY)_(+Ho=;$c$L`Rh+w-QWHjQGQf5-_d6z+A2&VoFb(LQ6tFPE=5gRGtT8Z+I%BW5>)T)yXG6Ok*Weq~$k3(%oVF7DmfI znT(4nV8D_hP>@6y7+}Q`vMaJ+Pf>Pgxfsp+i**?Nwh#2F<% zN|r7eiF5dQ=|)+B=@k(PHKdtKE!Yh^_|mq7Ha?jz=QADSQ;F&_Y0VOiOfwsTFgt}Q z>hn~aOfZMaX?x4*dXBQNXtN6A-9zxQrz2w3TeG*reK-&zMWFG>kQI>fCdu@q*y46J z1U_9$61z~^6S)w3*MzBjD94Hv1rZnP`qH_r0M2A3-MAKeS3OMt56i(C?m}4k!}2W@ zkSeYZ?-7eq{g6^-DDLOhJoc$b|se; zW#4JdG3`QPVpUi+$+j^qEOk$ED^C$r(wS4wm%Q}|%dkiy0_S#M)}Tz$7fE`(IoiJE zLWK-!Uc=&P_#zs-V)i-=?KZ+gVYMBJLZX5+;*;Y0T}ArCcv`|b@iir)K6Ey_L~Yb$ zXbVn;pwcx{tVatp!oJ1y&BR`IU=Lp}oCv~YNGT0MnJ=^gw_t`4!M7wPGGX5>M`Wky z61SWX0sF&bi6o7A6-t3;BjNp2g6KAa5RvjR^RSZnQu2aw7Tnurr4_p}B@(72VEE_5 z?iK7j*vLHD52SLm1IqQ8DprH@>_ou$b_CT|6`=~x1*US5RuC2P%O)2T3-SoYB5AT) zEz1|^U;9=ucRgPts7|M;=8ebLu25R9P#bi?++(dq)531rP56B4#YF~PoeM!*27a%v zF8h_zY)LhGd=&ybs!05E%476q3BTTEu%93pSDCPZ_bKQYeMv9na-~4~9SFs=T4m}5 zt33E}wcOdeE`OVF(&YsRJ`|6pjubZ=KT}sh9sdSe{&Ph= zDH@a3|idK1q!1)rY^A&B#~JHFuJ2Kphuwoub_dZcI&YAN98X) zrZmG>!&Gt(KNqLf~AEpvM$MoM%mBT6V2&3C3)_U-9D zue_|VEx?j|rLn`t&XDFNJQOvd7CW65?*az_j?!a9*|yyGrTjSA*#Cn5(XFLgM=mrA3L z!C`VQcKPa!-5LwlnNQN&mD8oMF0*t$8^E1v8T(^j}CeJ{Wbh zj(#M@PV1R9poJ{JfH`Q0+mCuO5Ijl{+FMCYk~Nrm8R51+_^w~SdTKC1<(|9WP-PKm z`shHdVjqTS2d1kl_J(>}BsWTUK&}K^pa1Q*+G>4AoC9AHgC*(F5CcHR<_sRLy<4DW ze`cLibQ>R6Btg|nCU8~ssLF^xcI}AE2zZj4^$Gj4K9gEx(=n748x-$*ccpO);T|rk zfJ-yMEia!Ck_?|}Kbb))SB9k|jvb(MJ%$Z7US6L5;&T|ug~=3v zHQs-W?aNAV_`p$9^h}ZEZ zi$v%knZ_fGk(vzM!9++P#cVZOliTrrw61Bj9|(y>NPZL_PN+x}9|2uDp1{C1Wl;yb zv*u$m=B%wn3W_oTQ(z~%e0;EK2Lx3JzmFGYrJ-l@p)wulwfba{uL)1M;(Hsy<-i^LB>Ioc)%J* zo~qB+1FM5;t;5#MlKa&%+fC1%+JyQrc3+-EZXb5wuS7X_oSoZ$YQHF%9)FF5BbJzq z)EZPH@g&gxT`u+jABPnFNJEX}n%s5GpJfVe(qNv)FvQ095i&UeCNB`T3`j^lm4D}*G9wQ1Wr}w4r)IT z8ysJzk}Ckfr^iIhS(@)7XtCwTTMtzx`vNiUf=w_s-W$(FtFj%gg+kZ-l@fGr;x^JA zR{LzGOK)TM-dz*Y2=%x3Y}SmQ^%AkpZ=&4I@$2vO-Kpo|JC_oX5{`d~eEcmA1zMyY z{1#Ee`1&Qzw;pdvFAqaehch&SCo*R$xpVs&v85Mw6I?RBpAbT_+vMJx_m-qKM+|=p zT3ob#@^m-V2@hMhkBdiYY-B?7l42s6_!}<`rO$rpD@X$e1K}Jx)PWbrS0&FMECWg< zN_Z6r$Hl-Fo`>R5_ZrLhh?e3y4^5r~|16%H&z5cBqZ)wNFdU?qgv-W&`5Yb~gLpOK zvAme$>%J9&#W2m(p(#wgv=1L(aI zHQUmvJN?^I_EiL$;YunF2cp#_3Dos7X$&bPRMjQd zL^HfCOCt^$dcz@lKZean1EhCR=AFwu$T8lOYy=8UYu*p%u{Vpc5=gXQJ~8H%PLPUJ zNMvFj-V~P!lL;`o&G;VREB5fJC6)1djA1eriEpjd7?wG$o?~WxWnH z=#~A-AgbdQ36!71Em(Ic%<)|qyBLQRIV*tW0XGU9W_DYOS;<7_>4WVaKNh4RWPvQKDm zA7$}JIQ69y-tdHcDdM+v;tvyX&<$-yImR+zR?vI*NbkZssQ9Vs$6Iktv1t_MG}FxY zdZt-#k2LPrMsU0*{z!b0pl3iHKEx`Cs*EE z_>8?46wQR{T$=Ms*U^Xq3&x5}97(lsj!iG=&blR1BtLcnh-;G-&Iflr@+@C}d6lpD zqQ{Cg-^<^=@&OO4j|ylx4o#owB4*+dS!FuHAZ9CpCm0`#Zw^x$TuHf!6S!7@6lcoc z%bYX8u8?VVZ6d_}_Q=79%Ax+K+nR)9yf)Tr)I4RddEn6q2L!cSE%%NYpjvT${9rW2 zD37F8Va8B6kH_pY_a$q;M0jG54Scl&;;_c%1ae2gI(O#f=nIzmX55h<*Yz?p3Fd(`6Am@O?aVxN!{alZIL?*AA}|*sZSCzjpz=b_J#eX^QFv1 z>xA6T`ovu%9~ot3-DP>zI_{-LOHJe_wye)S5uyq5z@bo=61h@~#$GSqxbJvu?yb+U z53b-szD4yV?ybs2oN^OnM;u;MOC*+3f?yRwNF!R=mxG`09NJpR;Saxidivn`lWeq% z_BVbo-Pex3h=G7JU&p{$fqbC^|VY z>}1RJtHv7s$r#z>sc>ui4u&+}yHbn5=c)Md;TB*7<&d}59=2A1{_chUU8dr=jw9F5 z%dg>_dp-B=dhFr*Z6r=ydP>Fb<=^|ZdmnnGIo2w*8;d(C8u3+_LBW?x&?w%7w)Ywp z9x74SX*jy~l)9H5Di!(UQ}F(1+T=yA-69pP*$1g_j2fVJ|ED<`xc&h`+w%U{lK!ar z;1ed?&w_YlfU{GeZY45AJY!W|F23roVCF*Z~Xzzf!R(Ued|lY{^>?RHLm`8 ze6S2tRsF|y?Ag=Z8)T9UQ3N99TS5Tw?0ec_p4;is%CKZRfz~($lCJJx(An`+AID6Z z=@_-c=xH3eLroW&40+SGo-m*rlEGDTuN?{9IXaHOS8mx}dr(|7IDnISy)456_198h zkzG1jOZ4_xixYouDm0mdCItlDXgTcw#4}KuTAGFLnX&d{5jvLSOwvw?tfTHmjG*&O zxkji;Hp^t1@50VjuKy~(RbFr)|F(i2L4}PT^;zq7Aw&)cx=GTtv%R-yRI#HZ#I4<3 zlBX164O5NUsqXB%?N;xr&{v-yQ=C(ike-L<;$~w46SxJ z>iV9dMpZR*6o-Fzzpw(YecPgs#9GCc6i88Xz29@uhSf)vs zv5bqEmWuzOsuF)t0zJz6_As5Z{qoB{s77HMeQ^T?-zTpOKEI~?lMqK35JT4gO^7pi zl-m&src=sNs4eJ=g1N;9YYPYB{^}MVN#!;l&Qh!|`PD@BPvM6L%+?j}p2OVYzHYM; zcdeEP_q!Wm`BZO}IC@K$^PX)?|8R?^gKA{$AAWzEBcIRAb4 z;lH`X|MRtVz5%iquaR4p7!Y8oa{oj4;Xm@OF3gO0|KEon{z-_VX&V8(l4$90+aM#|Ho9hrNi)un)jh>hd}tD{bB2N z$-jpm{^k~cW)~wNMDTL43ohm4zQi9&1qnDdIBtYD-;d$}I=MnkUdJ;uAS&%*BCjU^ z6F7?DNBMnG$Sii#;XG^oz){rI_Erac2Y^z6&%mwt=TXEjd6juXvbr>MYBiRK@T_e~ zv94G>>CO{M<6#1;fl_r2rD(f`GRb(HT9IjAehb|f_v2E?l$#vpOUOEWq%pKOV1CAK zWci<(utqjl=MSCFq<0C0^<7LLA-GgTBA*_v4aCw3rnDa&zXXn=dC6!Eg>~ePR225N z)&|?wL(O*Oa$k?$89dWhx)hn``_qlCA3ev^J)IX{dE`?5$l;7Tm2J&3Jq?*4l)EQH zVfy*mZ2c^Kyo z^bU;9lz;Xe{4FmRctQRaa7e*Ms)W(1@;fv0^E-X(s{bb7Q1HhK0{9t;?|ubl?|13d|LQJvWS;+bcggqXT^dRL z{VwVJ(72y2B$p9$J$yxT8c9G-!mZF9hK1qyC9Za@A3{1}yxo!w=qtJISqVAc?ie6MiR&#=I5wu-JZg(9=jcCOiWtEt0x#o^5l2#C;e8%w;r_BiBHY73Ol zloSiO65pHKx$&@n*$xW}{m*6nFWJ-oH{PWu54t8P(mWSFFy#CnczFJNm$K|B{v|B* z-|tewru56iyHfwCasTBmx$?rpARy6uyhPTO$7w@Ou&~ho_AWg)BmYLntMrG)T@MHg z{V(s*?VsJ)(hPNQ&oVmExGhWgUo`GN?~-ss`}NxJ*6Tlq?sCI9A=Lc6J*t7}lyR>z zN&dfSkFu5i61w}8ovG=2d-R>-e81%8!%vvbKj@Tywnwk6D1UhD|GV}GTNw`U*l&vk zibn!oI3O>hojM0l&;8-C|Nr|QNx2(Ead*2b|5VQj$mzz02K4+NhVH_CH876z`lW(Z zv{l9?9!rznYTd)}DX!!ae!X z9@+cNF8xD>`+12C%nSD)-=wdV44aHi%74m+{$kcq|31pu;p*_K;j5b_fqNqGJt!_+ zBx9%OmZG$k8a&vqti~*Ut?Y1c|BFD`2Z{;g2kA0Hb-#5} z?o2X*DN|EaTx3}|JY)n0YZZAw9)<}~VwQiP~glHg~keaCT_C`3uia#m>TIT8iv z4a8&!jwl<(Z-68fB8ox6X=ygx53K zKyN=9^#TNA35_&kDLR^9r1z4L6#X0XP>frhCixQn?<1r^6trj5l0s=v1wh^vFxVrW zBT~eej@sq3C;mzxi7B_2=>9Ydx`rVH^^%6KS0V##j=7MPo@UaA2w4=E27FUjV6pii z1QFG%VYR~*df=`|jOsdQCMCY4p~8}zWvE_eBbTmjh@4IOPW=4lk+mcXNVKvRjS2G& zS_RfLyeDG3HMkd~cpcY=i$9&|Lb}n$QA+INpl5j9%sz8#c&7i*!-+97E zrT5rTr)4Qx@NTvrB+ee?oHd&C=#Uk^A9?h+hu9PoiZ5x{eOJ@UPhxdz-zamh!+f}gH`iGgnIiUr)6QR9Aq zJ2UQy5K?g+_!6M+!thQ%-I>2A=da}HXYb#9{b)o@`x^dkk-q~vH3Ff&1PW*}02-8M zL{CBt>Mo{5kqH+hLBLXdw@R~(pNl4MliR}}qeU^D#CJmz%V>f@^9_v#9awwpCXG^4 zT%+=_H%NhRt%ves0`4VO#2CU8NC?z>MbeBJ;_2cPt{%Or=dS?KB4jr~(J(`kr-9*} z*bmgh5s}=m!z3LL116S;ZUW6%}Vgv5pvvj{4wMIz%j z$ESl63NJw{D8r7a2?XD1m0!beQ!i%EVCd=XszVyZ$0RB z`G+odMxIvitM!h)Xv%kfP_^}K*A(bYCcqeOApgvwc@^>{!scGGZb7~ zwTFIwn;~j^(0D5L0Mx@d9Tt#=YCuwfWa^+b9YeWvz_x-eLVj_p4Jj^>#u=5j43jE} zX+VHK9uE@wfG+d2Mhqwv0I{O9&_hrz z;=pRdcu3ewZ{<=dF-Ki+&qYD4Pdl7+-9-cUc)Ey27~@rqH`vF&K@%EI!>kzb4&tpM zZQK$frE8{OEmsZ>Bbbx8NS;u^qgTpt3sEGBq{FAAPmnm6#zGCC$-y?u&ovulfcL7v zGseEP{5rmp+HkVY)2Ui}i9u$t^zKMEQfaniZHhLq53H!zM*h3O- zy>AW8p_lZouiA|qzi3B)0QIUvYqX_?Tfn&_Umg>+X1mEoK{hyNTd-ZL-IJ9eI}3*` z{a=ketDcJnxez;>)Q&xP!;90G-b*$2*;w(-$cLF);`YNQFZ{NYKVB_-`9Xac`FOM7 zMQ3C=Xp4#$>*AmuiW)#Aa}>sUBb@dG&!;9>d;x-f|Dv13D)(OI&fE99jP*DHhQ2uk zAD)p{2BOO30eGiXqV|jLcqfhP9@52@0E;I$CUDXZaEMFVX;7RW#kFx!#W7Z6=hws! zC1Agr3u?udd>@XU+EOip+)AAYa_46iVovqUJfOf#N>$n9eYi1zW+lFs8wUA-T%utE%oT97@S#_t&3p zo2JUo8v1rH4~oWK+pBpl_@=sty|8TFHxqqKJHQ!MovV7_2)d>7Zd|}VEF)ReKZUv- z4wWX67QKCP`_5J;=vK>sV~vVAO>&=-aK>1osKh&St3g5TsscKRmaVw(cb1w6_rYI` z_}OAEvcL3hlR{>2y`Sg#{SEUv=leN6 z=Xt!}makM+*;_pG_1}#{xL}Blgfenh5-l^??9$e#KAHQhQB<4y83jWIkAKELCk|Z( zcZe$P$3kh$Q0t|un{?(-ID)~aob*~99W?;fXEqoQ)_ZY!(#?lT4Kd0%jouiZ7kJp& zGMlYrcy>KRAFa3y`;1X@slS`W?FIb8<^>wcs97_rZMW-7o&K;Q*7Pi-$mTn_&d(LH zCyqS=uJUuU{djR_FwVWh4(*v?Fe2f{hG}UqYL{lRjn` z3cPE;Hgf0%?}!Ag$p!h4kpmhaq5unzKFF{aBzh>)u&>kL_2+RBP~ zJqz}KCgR)$e~IW4ab6d>8;2*6hrtp-ND6I+J?Wzkku407#0;2$vJXyaWcDREv)_!O z#%-p@w2=t1i(!qqiu2=K11H!$djq_MPA=?03bsQ*%V!2RfJrS;d)!DLlQA>c(`XrKGfqrP4{MaT`_DlrJg64~5(PX2sQ4;}dV3|#pD>7D@Gk_|FKchF8VNTU| zG6o+J?CoGrLpZ>QPY%cK2*u??r8$eEvLGZ`0n=01VI2o-+e2n9z|0Zh7!#^KK*Fam zXq_!hqM)ysm^EABeJ7m<=|!jv+c@@_X^u^4&hu$$EZ{ub_z`Anx>Y^dez3&_$WTlj zttK5<1JI%HVrEW3m(3trr9CO*Ek@Cqb@Rd-U^Ki;akKy*XlJGwX9~M#W;JDsc|xRi zMB=d_%rAn7YElI)c=$8H!I{ixHCc#+baDmokT*EsDh-&Ad=ab~slwH?mZB=l6(AI4 zH2}Wp%zSl4*qfO{eVkc_nnHXWH`)aDXmY@saF$WY>YdEMbJrnnkOlX!zAp4HM1U1H zu0Vb4M4Oqv(6lrY1j7_W{stt9J|rNXarT&0C@AoYs3*Yn&a!XO+v0-jVIVdY{rmN zcB1id^@%2H+9?A(C?~Nl4yQdPRefGrVnGipSn4_0U6ha+hltk1EUu2k0g`+FL$LI7 zLIDRZKlhviFo6Rq`DQtlf^WRLfIV2%)_AK(>5X&GmWoA6LO49LA+gx2qe#fzdDc+X z@S4Q&nvkXm63_uOnRMmSF$9@7KT(w$7{~-e%Cg1EV7{3Y`I)c|=wpOZ9#5j@(Awl_)4uHEOCQ`Id!=C115pjw6iMV5o~muLT~F$ z-jdmiT4lU##(~s}L-NqAh>?=a5=-(#SvL2#d^K-0iOPp56;QiM0@66k=PQKj3#NT5 zSFN&Se-sc+yqR-!ymiRMJmw}!fXtOc)*QiR?e6G_L0_XPN!Q4hn!y?k3{pxI>8~lbpU(i zRnqDZP&VY#4dKER#H=CYwx{`-C!4lR|Ai0uS#h7)~XQg9Cr)g zsq;Dt43n8s&7Xo`3^am#OUzF6YBIj}%A8WgLNN@TESz)Td+ZSWBnI^%k3b{v&9y%Q zoblnf0R(A853c2I#{}D{G1O*rk7L+^Sz1CES@s~a;tkV!Cg@6F5m5+npB{pcWzj7( z>lrJCO)?^g`4N==Fh#liEetTBn)?XMh8u*I&m%ofKf8iHo2Rj}=B6hGW2p8n6VD0P7??1X)Nt+R#oAij4*#~EuEgtc>? zIHttePVJ+0_{UjA@4vk#HkB$R>`=6YVzhP)oDu;gz1=uF`}KUuFF|=l)}#OeEGu`_ zEIZOf*u#2Z+8Dwujs}qm+C<}5wycC&(?O`=p8Qm=>TJ6O)x#WEf^pLx%5uSmI6nX( zhI$oXF?Fz^BqR|l>}XYAa#KI`?8Af_xG>KE^JjpsKF){F%9=h7z*QVy>uR4Lpki&f z+YN-ntB<=TBrF3QXbLXBpntvu4)lKrV(1G(Fo8|$)%{H&z8g4)iTzKn0)(@aoc(d_ zs2}E?aikCH#a0X;@_6E$`$}SZF^+o+^ZHGn_pNyJwfXec`h$_vZ&^j5WNJg9RW%f+@(&+q_0UvVymAf~UDgDBOp~)kj-M#^|=el#>n;`YP}==nI9hIqMNhNGCVc zf2h@h2oHi}VPK)+Qv$|6JL9Krj9yn552%kWwSq5~w1Lr#i`9Pk#q41G$+^Dq$1339 z2)P2U$rYo2fZ;OG}F_bVqvM#lYM<3`gHqOlVX zTqvP0hfA6spswS~Y>N|IDAJd_T44iAHQ`IX36jqjgAYyOLkvT{5zNXcj95d+?&nz;dzV znbG8z69V|7HH;)P@$&_q*7ZDh@lH}nBC2WK(e93YnRGVHgS&~}Fdm?OBWah&J%A$0 zIxNoC|1zj@p6#Y4eM52XREdm6!pvN&3)jT;!mH z0xFkIPfFkluc5wOz!2L^wlQjYDFlN5ko&{+1R=^L+e~(K_dj;pI zbq~zUj96_6;_lXl5Cw|@w$a=-+yy|WB}3g^&(^ob!$%>TZpxrWqhMhv-(IOnD9!rI z9c}8KX@T7u#+{;Nd!GQww6yK>PkaAei9%zWZ|msRP>kb)(*U^bj;er2)NEZ?I3O4g7do$3v)@&Rhtymrk2_-VNMX4+Vmt-M@q+VEa5BO87*IS6_d`AC-sn0S z78D1~0uO{hfXJSJzb_v!tm3c75s98054l0UCG;@m?-uHzeZ3EUtPN(727`0KZY#Sb zht7LP$3&<|rac~-1;MN)A^)zVQNpnX4_HkLY!QFTR(L`H{Q;Hz5%=r|+VQD$JXr0? z`RM8AMZGm{=#TxQA}0e(W0`aE)$`h9ZYf2lpPJz7ykdfmC`7^~00QEoJ}aC7U>u*J z@tuR*@ets1I~lN|`FW`OVPHJrt~Rk5kA!MFq@NcL{rG!LXi+n>LjWHE^q9x9+L~$% zobeTG_3jE|^*TBh2Ab_4?Tb!`aT~HuERo~G%SVT+|Zj6F&*KPzB#t5a{h*#Q6g2soY zQNiEu-d`Gi`zi~=CgHZ8J2DrHfG}$o{UMa4A^j=x{fT%iR6$k0ef+gt9JX1kH3h$o zS_Yp2l4nbhqn@+K*4#BeRXQdh4N+vg)@Dc!wTS`REYz2ZJk=~_;G#J0kwepaxjb!> z=wt{Jgxo&5_|9n*X*W@2ldYXHUVy(Y{9|Ia%HXwE_G8(8WghkSff%gA65hW4TMM7D zBMiZmB0eklPDfzx$oV?da>`vL##5OM4`%Kk!%lIUu`Lr8Vo6v*-=JTHGPR)gZs=3E z^S~=Q18`(-pmp}hP)SLdQ@$0wi7hvh;&ZGjiU{eYiud?2i3js$3+h9hZcyz*5|6r+ zLA5FS&nClI=JBtvzw6PY?PzGL8zU45 zq2z|jQ#*5hHiHZv=26~7G8tsb$dP`;OSY(mCEDXuY1|RN^_-9KyxgbzvOIW}JKT2L z_qn8Bp?@lJsUNVI*6JYCtY`JIfm1+8TqXZ4CaUW~VAYJ%V^2;7?K+!ov!SIS*c4tq zmpHCyp&3{=O?Qiy0Kcg9rUxD^j9@3JoVYU{u#Ea3RVKsr6IWOOj$_@gl4SL5m0sS8 zB{nu~$zOnhe?IIa-+XGlA>WP6W6n05L6gn;hufiEY0sdeiZMb6(ZpP|;yUSF>6Gef zpDxj=n8om2F4^7Q9oLfrYSMSVjJj+!JH?^lL6Qke5Ob2tUNeHw&FCQNj@2G^UNpMW zmVC(O-b<3@%ID8S;=C*YCiI(p@qX$i&&D4Y^l@nXo)PqvQfb?2>gQwmn$}|9_pYcS zx!d3;c%R((k*0cg=Vra{EstA7UG=s;nLIhaXYE2Kc@dgcdvzA?|8@Sr+gTTHwp zos`9!4v~+huEv>EI1116**9GVh+t~K3mKs{)DT725I$YR*BCEGKG7Zg#w6NiLfxG8 zZUTH^Inr~2$re1#O(m&l3w$9DPP4=#`pC;ZD8pQ-6pftcV%_}J9df!}&}MrA$s^2M z>r}ZH7Q8ZD%YoP33 zsE1>ulq-)%t-Yj6LQp_8yi^Xo?qZNL3QmBrqsy7B^%UuL5@8guCW%C4K#-_&+{gS- z$scR-K!7CAt7CCPG5A!gehkR*gkc;v0~>VGg}U|>_NKn@fwEYE%48Dr2W*m+`Uf3Y zYY;tLoz%Fz&LU#!ZsP#ie)LOanuQ|Wbcf@l^E-qEJG;dI;n#^GP7nhQ=6Q#j#(q2| z_yjww-7O0=MVWKdpb2wJf)+1M3cQ#O3(QN-BvW03b034`5(UJoDHEwz%bEtfclkYK zx}ic%tejFCk4CL@dp~p|MKH;^(;cV}(4)Zi(2cYs_5PYhp<>M!Aw`g;UN`!pY|COe zXMyQX(IrFDOJVs}^+ntAf)A$x>n(}4PSzwe(BobKNEmOahab99SsMV7pb4M|9*3sWO3b z7<$uKNKK}rIHbUQnAYMGbAkIp?-8O!c3+ZSmt~N=AI)-6c!QHga#E$cLmG^ic^p2z_Re6F zt&?>AIQnHp!h7xwPLyaqy^tl#-3&xVHy30q2=@mmQ%)~Ub@Rdu<%t#-Vm+5&wOIcC zgw69P^`Xj~R2REjbQ zdxqc+E2`pn*PxGbw%+H3rMZi-!BF$;`#7PuE|q(xYD_b2iv90gKJJ-ah|g7*946NE zJqnk>Ul`a<(yX|RcY5SURp0SAs^<0}%=+iz=PG5do|j;cc&a7f&Y^q&e%!nIN1TFi zc#_fq5j0I?5K(<-j&ASd!pWqJpf!NwM#K_@Fy=6P3V8HrV8HBoX^S0rOt`1U#l0*t zdu{29cONvsJubp@W1Zltr$g!_%F1+6`^Se?(Bx>#4HtB=wZXtz_8I1d6GP9qiU`5l zX`twy#|ieOoO~0mlRTj`a*sPjrt8=Pm4Zr9@xMquTntoxl{LFvo624wxM!WyOd3pi z)t?R~__@Ix$nimOQVMdvd5ODH6gcWMN-Q^dL1*C{{kaC0;A!h9W&HCRmLc~`vS;W_ ziL-rIO@u_q#!}#lNvef3f=14OKc2FS2nb~Fgv92ar#(M9D@xSyK?M6$+uwx|b_o{| zB@wWVh929rb_xS+@Y$TZNGiVf3nv|GIVF68HU=_M9WJGgFcc9EfnrN|@kl>i>=SJ- z-519Kt?_4JJr@NEn4^h405)~dE^ZTk3S|{;XS=q_88pW*Sb-{CV(}Z{%BmDNbwtv3*+#fa(Opxc4(MB53PmSyl04n*omtVo7p3 zqB78+`UFSz7|m*M8n|>T#m8<-bjhJ+M%6|Jbs&3L=N`&E(>Zi#vrLIhaj49@`7EqZ zkIuS+@@#WyE*1uF<^Kgm^RYBOUM~lii!0&g(c*7}yO- zYz<|T&+Sj3CF0r;kXJ4u@Yv}JG>4|K330^83-UkEkT1l8bZH8{V{1nhSMD6c>7mNR z$UFjS$8;eExg_*~n86qkK^Tu(dlJ1d%=|GT=M)~t5Im8DKbh|N&Ra!Zs{k{iA(QFh z*r!d?O4-W*WrbxnHKOabaLk_!Sy|0Qb0Q~p^ zNb&pz7l=nvMuZ)+FbJ#WSL7{T&<5tLGM9O%;$JEClu^RKL=khPNkIf>dU5LdG4Ut$ z5$vfHQ?l>(K`-wXb5h8Txp%+f|vezysV&Y;yIP zx$r)`*r8UqZU%}ep-R6#LpL#AZ9VIi49YWAxWKm+DL0({E+@tNmFZvAe$|D_aS*yeFtfp9x@sTNb=LRfU+F@95G9!B> zGP@SPCUi9Qy7AP0pg0p=e}nZ_pQBi|^n6KWa(rm{1fC!cCv*#W!;Zf1Sj}8G_6;x9 zS{w+bPg%Q(AoLF2*{{@M;zKoK|6n!L-JE8*0tBL~MMzf7Q;%mHYl3>xGMm+k`LzZI z!BcoTQ^Cyx`W#Q&N5|!~NK$Ee1rvSTNac0_ifrh%zTF!r6CFV^iu4mXh2s?OGi9A8 zrpC)cS8$1XVoQqRva4gg#o&RpV|yrAFHGCb8p}i3FnAP5H|yZ6UkG#9r#>BxA;RfN zXgVm0)qxHBASd(DAN#dt+IsZ2xZ*G1oJRr(F7!q1DO5|CmZ;_ocsg-AWZ8mcU#wk= z?fy5T$Z^w{HNP5{ZSC1(zW43=lC@>`I&>rF!W8A;I#NJKqmX8pdYP}@u>S~)27DD) zJgiK=`bdk*W(sFfZ+|!3s7xcwyi-?!M4}L`E2F=0qPAaB77RL5mj|ei$;-; z8YveOdc7s#aUa8U`;8Xo<^mHGMX4|pP>tUj4_lIB*f*jxI?mVP%{#&B)V#6d#*H{1 z7<0ZfwkKCA&Yw>>n=^we#oe11ucJ&1>kDNwfw6^IU!upkkv4k3b05Ov-e+-lO~#Kj z>)Hz@y`N0`X!5SOF3in1<2^MO&7!rXs7oq)u|z>OMgtzI4)+zUdU?s?eHn4^5dJ;PC<*&( zDDa;casLl*is*j7y#9yu8heWApGyEP_Rq>D3YC*N%>GV9{?}J-Q8ZM^u(<;3U&Qwh zE?V`jzm;*^3XPt>Xq@R>%dZc)DV~3Bs9xOzRnSSgxFN zVb%JdKOv>paoW)`Q+!Tz&k1YCKXGEI-6E!3#~0eos?hD8YbJ=CT^u!d@-{+?t52SmP!?QUL6_|`0W-ayCrH8qR``r(kf83L+B|v}cMLd97B|A=T zgXz6i{^+~nPEcs#8?4a!9Euit^yLDy7okgeelsrU~QHzA5XHub>**efL9!U^2ARB8f5P z29=X(Ms^zYawc9c>yX_Y{0}3RPChT@E&0QqS7AJKIg@qmI}n!h_$*cU#ru~@2C0nu z^W}BmDa=J~nW@Tq?H%_&6Orv)eb%#(MC7eZG)kgGy8orS<9B#k zKlSr_8y4v1u$%1Z%^_xodh?@_u8|)n<9_A-F%yQMhhM%+J-t28bR4_A;P%bcSy)N` zYHR1Lm(q5%kCrJYcgS?A#n1la`O}}5AZ(}8tBdbQ8s`sXF-+xlRL9K#gs3SBnGtIg z-%+q}=CYa|{tSu&>)uDE%)QC%h;`p$BF|Nrv50U->5Q2xf@dE$vKL zRXF|qiSfjWZV77cnFW&=hEM(#!djVRp#F$1Y&>6wyyIOUyn;PwT*J0Rj{K&P(Nc3F zR!04t_$Y?mOnXPpUVD?6S3E!bu}2*C6!cT6ay9!J)gXSZZjTtwX6)_hD$d1rKF0^9 zIDb5*hx?*(vZ%TV@bX+V$Uy(sqG2(he56KuexImMgjp3 z6Iho%ja`<7s(^N4dW@zTJqp9sXn@6?59+L)uc(=s#?sqoOz_4=)m&vbYPcVjgK3y; zr-08wt|xx{MoI9jd+4qFR3=vBra4ClRniS}(rW%A*%G#qz_JsmxtKBjt6HX}@-VeA zHC9EzqXaY2yqs)fYsKeih|#t@_f!vKJNd~(0vChgRF!L18LbehlA?H8a4eFDj5GGe z2s1`WcUvx^mLf?Vh51HpSzA(+(C33fAr(Fj5Xc{u78kF2yMjCxcR8bqG zOsAvf;pEHCx7h4X4{r$z<=srh_CG z2eWB|Llv*)dvCs-R7HOQ@f~WvoB?{Z?B(zFV!`ocpQ@_n(@Z9LX?}Yd@9V=rUfPzf z*)o3k{CPRiEKc{wixhBR2Xmc&30OX6yc2rnZ&6(%hf?E3#D0Dwtf#`DOsK zw?|^EFI=N!Rf?w-LpMkz6(hY(gF9UQexZPi%Iu|k(!IVB)=*z35@Q?2R_)bPv zU-8(_^X^b%H!mNttQ=vs*%0o;nF_P6-wkpLrqzZ^za`(-s&UZ((R`tdKrytjsyviD(qn{g@8`t)?M&&aw3^dkM8Hl~f`x*u70jyJBE zDSIiW(@g$|?Y*IYJT_D`Q*Q;|c-bMYZ&V%n`UgA%i&~rKh7lK(xXgcdoFI+EG*IO7 zU9s);*v!SI$k#*{^S38p6N#-peZ2gqv2e6>Fe&I59&nSUyFXrO}7ZoBi7n0e?T?ZbA@< zDd@KDB`Z1PyCJxLg%u(I6G1fqx;tqp6K1%vfkjC%j%dk6ErzPYWEg0dS3+b9a2cSO zw&Wyx)uHz+_z$*NhIqrqPc5fAg1l|QND{--Jn&|$tPujSl63rQHB0~GV zktjGhk9|voB(;(B(3}8m6-LzYKnw!&OoRh;!c`}NeK1L3%9ON4k)o>+#Hdlu9WZov zJnu$g)p4p~CGqdbJ<4hni4M+-e$s+VPzX8xaUZdjH~GvrVmC~=YEjZ@dOtK&OhahY z^hLOg1>uWoB9l2%9X9ZJDamIe_7S+3wnearjz6vr*5YL(ZD+U~6b?8JC{gB^kpo}A znZ^lYq!L44@I@lDqYa?(2bL(9?J|zrq(d@Mh88iX4&--m|MNUY2zv~X1-2w=Jnm`& zKsLem*w3^h-exjRRG-iLZWl(@9#*#oycDKN8 z&P%d@#yYQ3?~*g6mytLgfm-3Q{KsZ(+tSqqu#oAnBKH(%eTs&I6iTBGMLNrP9xrz$ z2`4$t<2=&bo)pO@VmJDLX($e6VuAkGkd}f5 zfKBQG)!5ba4aml^NMi3J>(Xs=@RYV7tOSh=1ApooF%v6*^|NwTl9 zAIK2^+@sz2W1j2ZH~O3fcwon)Sd7@lJLd&Ml(pNv2A|jL!RRk*u>kJ)r;)Fub&Lssd0aJc zylaoJtGslwz*h-uU#}7f-}sd_(LdJBd@IJG!mfOFMZb9T3}FsmT+j3Skt^F-K!HQN zc16TEh~%LsSww=Xyb7@`3W;?xxvvWfYkXf^xr!aX4lgAdFfRfn*^)2;fly4hE27Wm zAS|WgQ_mFDUE-n3*AH=u+H~w)>hWjuEELMA%Q_S`wefCi4E>nFsxL|a`E(Ad+*q<~ z_RR_#wac(Nwl6N(j)%vFFRN68L+RxQ7|f}fiwTV_JmY5681 zpO&r}JUlMlVNT4F!uZ}C%-}%2H}%%f0#h1DlzIdbYLc4Z09%O>Lp|Sk>LMTHRIW_n zV7Q~TghBc!$sYBTZ}ntBGMJ2u3-6tj5rzXt-{zYUwc7^}9hLcx^uxP~fl8wAcff4$JCvGtyfs8WtMLu+NwaJGiqa|7z&M;_g}mYx zX-tP%Oyh3V&KIBp4y&jr@>ju>`-2R)jVyGybZ3Yin&R;al*xNBytQ&`)BwK_p=>hZ z!itaXZ|NWox0Gz^veo*lKI9ho$xPtn4eA`W0*hdjOmv!%VC}H>A}qdo@N-eMpG20~ zWcm4Zz8zd-0$>*j&I+A}IQq3ZvJH~?Z|@~OT3D!|bhnISC$Z>nnB_0z4Q`lS$1#ky z3*5WMDgUlLqCO;AS!!GT~lC9-!YrX>;l$W*u5UBhqB!aDdeLGrfqwwd6mDTDxYxpE zL7Re_1DdW)z)I5YXwod8YyL(Iqq6g(B349*C~NAxMO(!w{7L*}Pr%&IZ*b3Is$6&^ zo5e0#VDc?7@l@L<rjlV7{T~LFq20mmUb}WOeRj+{bA#mqX}xW9n$+=J6F)cGI8~aPIrwbbXIBO;pTKc_f?~n1UJogNtw2Fl668&yS+`@zA#v` zS7?)Obdz-S+Es*$w04<0cdBl5g{f4?20HhlL3rI9!E|_iDO>|g`}MinCC<+MN&Ec> z535M^0U?+|tTWiz6fe`ipR1>jlnX3k>Xm)d1BMI{p@Jy5NY1J`{@PeVMd3w~K|cL+ zhYm5X_FG8*9}xp8FkG!S|JW`d5d#|W>`!<9;af79_$pcO_w54D7ui2MbRX(j+_Gnp zKDobmYX8<)`d>SAfB58(p4$Jr4&C2{^Z_IEzdCfMNY}?-J9JWKQHLvChKOnU|2t3{ zZ<8)eMw^!{-!}K=^a~6rGJJp}&?C1C^#2wy@Gre?BU3TfG_<04o2E5Y_No+brbKmN z9}+{&wLX#*9Rr77o?0JDI%nkS4EAw_v z_qUr&l0rI-)nDt*U)kmV?$9M@&dL=z9VXcFr`SeWK5kS9m!)c5Ba)}neFQ1&jFW=I+0SaK= zbMKzp^MGH7fnFSqCbp7L*Ql+ky~Dq_J~YCk|Jk=h4WWv;Nv`g|Lwt7a0*mZd=yfaN znfH)h^x5^+DAdL1d3dNV_A#V8^K1F!ROB*q`~hm?63|Z{{sn6LMF#mFz3$3?@-6-9 zqUu`3aM06O_!i8Tdfdj(5izSKcrb-@an1J94E}=*BI3oogxoIh2_Qt`ZfOk%G;Xvm&t80E74ErNd^PQb7g>rFIWt&;=oCzKJEb*m5(q%=Vs-mj z)lyIh{^ais5o_d(>%fxHK6i9lEqO3Ocom8DCb;f=_l(2} zL~v*Cg$*jc2{L;hrk!o|UXq@4t5Gpn>9A?@tOrhHb~L_@+w`%wpI(C-bu_OfNL!VU z_3;`y!3wc%=RN8L&wVF+7L#E%+u_}Jm<^^LN-fg1II% zpobSfjv|IDn5EfhOMYAy=>F>Lv%!cSysTx(0-qoZZ+GnD=2Bu@iP5cIzhndN+8A@_ z{@M@bC}YGk1+psGO}xZ;qwuA>JHko}su$1+m*{DHh9zp$w9a8a9&0;7)59h<$Si$y zYvS30XE{b_8qlf49{pYSA#`>{l$Lk#}=`}p+*5p z-^@jLlC?~qcBPz7Kd>==;1rEHyAWD1^YU!P>PvFdYWmYQKF4plrOoo&*%D;t+_Oic)i}$W; ze#>wqG89sl4(Pb&Mwmnf1~l6uv83B)-S6^)35N@*!%vHl!bR00{X778C6tp(Mit+g zkviC^nA@GO@v-7odK+Ge94u1}22Ot2R?*KVP^(^X91QXZQk0M}N5ySzr&I7(7UM8Z zbaAcAUdG$v?#oPei%N!m`(Z0L+LZ7-fC##;uZ%a~0I9eM%l2>XMtwhJhXuPJq}%G(xCqxOCh}r4t@9JM8dxjlBrH*E&M1E9VyrJ z8k~B4{i9R{|BZHp>U06+S(!4DWSBlUT_S#lBpJTZFIAm+V{-P!XylDS!{AJn@7Y^( z{I^DZsf`cXB4=w>wfRAl^V*D&3R{t(`BC5Vx;*?!dnL7n$*lAGGL1?{!=Z)Q=JWS% z8Vt^MEs45_n2-;RBbBbNe8KMy&KuhCtK1?UB}!z-CU+x6&gnx--^4GP#$&6Vm#Tf) zHMwY>8>#Ya82WPPd(pCj&m-40G_R9%(Ymei&iC`s@_F;c$D`Q)QR(|OL;c-5A`nY` z1whrvW+(b<{kDSXW!*}ET)(lZui~_%(vpVnNlYgqMb4L9^aM5VN+%Swn9FW9*JY62 zKPr88>g$w?m%XCDNrvmRKQH@a2x=1|)HfKYuKJZVYm?H4H&`UD2DIZ&wBA8&@)fPf zACA_hHQ;F*xLpmI6Vzq&ahW6}UJcu8)@6Mj{wCaV#WUg-SC_M;z9s%kF@`3=>9EhcfKvbZcxlxi3LQHzgP!FE0Uppn!1_Rk?RMm6mT3xTVxKI^j|s=j^-Lc^`f7Rs42sp`CaCec`971DzQHMH_OG-=V_)m(k9 z;@KQazSX#adVsrqN@U-rHC5&$2WLwNJl1%`Tp!s$?%QSYbXod7DENtvclk>=Co z)rAW4(Zc&jhzIn%AMCd0)RMRxKjt+=lvk1{rle7V827%#`M4PG4=GVuPjXaGfQyhHG#xOartbPrdSB2@s=Ri9s{`M1*@Q5@3A4Ixu;#&iH>e(%vh0@0D>4*iD2m^AbWnjje&e8Z;JoV~ zzXRLV7SBy<24|I&I3_)Q^TAQKu{l3Dg;-z!`aqn^!&RTW(Kct?2fd~D<&!xNm zJE^Bc|4NL&=m8QxC$V93!+mqq-Qc46H>u}aU;9(EmS>WGN{e${QKub{(qcwz+aR4;_d9Fnhx<5hC{|{#4e>&Iy2Yk9e!=vM$ zGV=c%9ZV%DJ|v$mO&rOm>%S0=rq%E* z+nQSK4&xqae(#i7I`qD<;`%)jccIjKhhAyavd|QYOUGSvDrxB3Rmn2iD&{_L(9Bd{ zx`dSVe#qx6YNJL1STbS*5dgk%TDPO^DXjBzkPxFoLx7hdlJdReaiBHkn(pg^^%DiN z(ZKmt7~_~ffThgb2wm5}3=>z8ZPg&*A!%ml2{j*5U#2;;VvR7JU4=6}p3(ZHFVla* z@nPHYAy*qRJc{)wBlTr%)>d(n=gLV^u&dvq$X{2}Eae->t7)XHH(r)~Ge!T8@L0Z? zX6pGTfaPDpqg(HP0IMu{{~?B6D#?@^6B^y4}S?8Ax;&)MB$YUCrBB2`)|tk zzdP5z!y{4@>2=sxS6N&fb8>*>(=AR3Bl4V(qR0<>+*V#0I>Rmh9V_`?&b4Hk*!Nb( zU*~#wo8otPd=Y|nIxP5$^35?3T9^F2tPUxP#JcxmTwcT`2vvdg)+6En56Fm#1Z~pc zFR(o1fj|iYod4}y+$dRgw}<}z-W}0n2V+wGH*O>B((PX|V-C_(0e|%m3`}ws^@MJ@ zUh3)nmrG~$f8#a|lC0uJ>b|0GWvrQ&rZ)TiWTUdZ#MK56^#=w

    xFVa|J~k@_UEG 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

    &r7mW)N!*yWWO@NBkr%O#*QIKQzFOgS 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

    >usmlwilXfU8qx?jJo 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

    1W~L8JZr@bkjl zN|WAbI_2i-kBzo}5=&B)-u87r*qnde^3|}@AA{h!iq~#)Pb4X)sj=`AiMm)O{sZdjrooz(lJ+J$=~u<&$m7R#q2A9>Vr0MSD=`kBmZ%s%adv3aYyUkGtx^I zvzktC*QSwTF@)N`&`B>kfMWI)K=r3s^6j6{Nyp=-#|K-V#$Ub!gi_dS$Uw}$MJN4Q z%r+*6y6EB2gu^Lmunl|Pi(_8EmZJSjyOv^3UnG*_^8UF1t14*jH0^^z2su6FTX0wqx_g=N#u(>}!96PFgPrCAYV=WxHNwE7|e`og~Lm zDV?P%N(oQ5soE&Z_1N4fF9_$@tSGrcCsmYH{m5TkxrCN~#GviBp0$3i+Nznh7~abK zCHr8juJ4F-TVfFUbh}|hcx$_HLXPvx@93lq*#}!+T9DzKJFP3}oJka)%bxDEZ!Z`~ zbnL!*oi4UNHQ6zr0=-bPkFx@Kqqx>-xUOK@KVr~-gHC$t zs5qATrRQK=0*Cu>LWbhO;iNT7w40~W%@-zb)$ZfCzxyz{O!;2RYIH)JXH>6p3QHDC z*yF}9`5zEI($@&DasP_&kv*qTs4M6W!6oG|{`Ye~_$P=wn|9T|0|`>6E&hcJ`#*;8 z`91y*ock+qa97B0LRHO-yQXSZ2P+dPr#U+}e?x}-Jon0|3&~$PPYw`_MP_Dqm=ms$ zVHs@nmtCjgeOvSfi)JX)CDyc=Ch560G6?%dBvKkzxB?k+Cd;-M3f2360VMceI``oQ zNkDONvfXxMro!^m7VhV z1K`}7kYW7IxgYw|x$ivM+`2-BZP)dYJ8ahv{zQiT+qrL^^Z5PTJM6Tr|2X$QbRe%d zzjhw}*K_|XGA!4af#m187r#MB?YZsJI^}k{r#BV*x99%R$Vry*{%Em#_aklG^Xk-b zS=bI;_`kS~|Kqp~{YCv*3GJ0+I-W%eDYbKLN;6GzX)yhz zn`EL*I;3w#s-hAADH1#u(GW$!lPILJFW#0(beuo>AmNHDIE*NTVU8g}2t$zw8$v<# zRP+n;QVDB*s^qL_J>4D&{W*3PNp)%{8vy878YOAz%hD)7mki04d~~V3hVaD?rLMrP zKk6w&9izzZ=arEhMwQ^tyJSf!AL7~ZspV2{A=nAs44-(8w2kJ;3T|#YB6y_|IkD1; z4J|MYr8U^6oxER@%-h?9C#!!B`@#}3xb~EG=E#t;5Irb_n;4VK)ga)Iyy>Y#?YxC5 z`{R9HaE;&Pfl&_PSU2+)y4{(&r_p6EuHdfL>2`mtyF=Y8bni#~B&G24V2M$3i&eIO zHzFF1v>2OG7A;Vn(DH|JZt{I<3StVszy;4h_ofQG4NK7rvx1W=O&0|UzB5|C%9D{c zQ5dCs7;5ugUGsHqvKKY;Gl>Jg2fKM0C-&DHFkMFTdvWK3%EVgkRBIGrka=!8pkP9g zXZdCP2V_K<>kpjC<2(`UH8rM-0`;BJ+T^3 z$}bP|-nbKYr!ZvYB0|TdE{D};e)U5BZ2j}9+QMd?{CCl>pNGodh(9mC51waDia*`A zc(L{y5KlY*)*9x=6xXl)C>-%GnC=O-VTBZ|X1+^n3vYdS>mORdcLzW(W%fTj41gbg zstve2ogEKA?Vr(KTcb!qTlh+ktx|waG1zs==LiE4zDH1|b2w+>(Ne0UN%B{82Rrkc zR=h#xiB5wF`M-wh*hS#$A18P;?S^s1F5=&(l%|}S2!nSP0x4H%GU^pKkkzO-s$C#q zG!9de{VfPTgN;#udkeP3z@Sh92Xp9YM(bG@k&88m#kke8o8{u}SCPtnbgzy5@(5J) zDl>7tp4sQVTOy=IL7p3!i~W$@*tVta?InO*B|rO13`&t`v5PBQ$!-*a!uk?_lij?k zNLF2rWFl%%45rs!y5?46{S{Trhh;JuPqesAT|?-X@Tes)O9aVug8WvQZP53Y1zV`D>-Db?q1Z5P zU8QQ18(o=t%0xa?CK)#Tu1H9>HgjO0j+#cIRDGrHxf)2a8rotNF*a3mkaZ|Y<@aEd zp;^KR!lKzw-4&o0*x<=w{P#8c8o^Kp4&|pxeF{fptf`xvM9=Y`b10FeXKTA~ys^3*%iaFk?^5F_Q+i%xaFrSgOb2LUf}ZGHa?Dm!==cvG~A_Jwkb*z5ZH6g84q zQ+RDvx!kaRf5w|ot3Q-ri4~JnsApY*zfn$Mwm(V84q{lHe2Rd~Ca!&{i8%pq8&_|N zaE}{HZ^`ddxZ+?z`;?*^k=o}ZTKp(}kH^eI0|>XLDY(LzWw=Ak2RyS>{XESIq%6vq zp4}ht{vY<<`Yp;vUD&1>YM2?iyFmYmL?ftpL-lQp#l`*?(Hlvi3hVQ zucybB4pgx6PZCM-Pb=BgShLeSC(>`$<+bq1B?>ZCykW}7>1tEOD-A{{;Ob-L@dhj; z5nfF%n9dfTy!=pV(=!_$rfP<&xi3f2Cww5nYkwSE#7$GcAITjr$XJ*rf^Nj%l%hK7 zvSv~&b2&fZ+Ae=tRuJ!CT2lW zTOQd#x)*VBWAklB8Ul^tR+T?tE&HA-G~_L}O1k5n zu@{DIr{!5eVqQU1AH`3&?k*Qsc$Hp%k~m~dR1v{%e`b2f86o_D8~LJXxgeEDX}&~? zie9TyQi-$6#Vf#4touIeoqO7l@>36}+Se8G7-y9&1h+$=LcwX?`?9skCs!|lDJ*ll zSp0xba0m%1rTn$>BErgl4ng$^+MS_hlp5f5O!c+c8uD2l?LPkNK~OXPQyDkfYvTMT zmyo^($AQPeyQoYP=(3on-5%fs73&iF%UGfs1kpjI{MqW76ygFeaYMt#_Iih8g3nV7 zi)5GS;$zdz$-H((!gGECbhz}-$tj$&W^0Bmf4WB(bp65vue)IfWe=4%gP#PNW0S(r zdmajQf@N&2@|+(^Y=)k`3WqJjqjJFHAo{PdM1&KJ$Rd!54R|jv4(T>}UMsZA2A35U zOwJd<;DeE93!E@fiS*ic|NR4>TaHaucJoYz)_KzGZ`9?GK4O8hZA z<}NFhy?^vJ8d&**=vJ&CWe?qAC!_TQu|_!MSER5n2>(E+Ev5(*P9j3=uR`yWM}Qs6 zi-@I63G(gwK<4J6ywVX{6ON;uc*9n@DQ^Ps@emW{vF zB%G4why$7gfZJNg@n?fs^ybPl;~4VN`5ol)X5?VeVWYw`L^p{vh*%aTAk`Jxx0gPx zIMp`rHr^+MEE}dYS^Qzg zsx;QVr8p63Kgc{*gtEU~*|w7yvkVt}D=)+Sy|Qoc!eU)qmHFz+ zdsUh~(MR=$bv~HiUu9^&q0%)Y2;;5&wiyVPz3a{kiX6mz@&-~GRLxUfEoNIY{Wv!) zj{d-u)CPe~ASdQ2%3im#S4PE`kwLEAnnS!BT#p4u5(A!;Hb^CE%7urJfCRB>EaE6P zB7#0el*#&&4(4^iu-ZNcz?XpS>{(?;Pl??qh>iy#r*4ARK=c}#E+9;DO;5#$K9S~4 zVKY(@)^ClBo^?oaS9%t8DL)`HF3 z3>xxg^kMaD$Ekjv{3;j{Oy5S3-U9N$QDcYF_Cs>&8P5y6F`q*s_t-Lq3jtSxkQ)n$ zm>4$D7;7M@lJsF=xywSaiLGY#7$MUxtczRiOA;vChML~t%Lg%&Zwr*`fI(Se+aKX4 zDu4T{U;Q>X?(tu(s1ix}-PTkfl>am&Bum$YDuDvIuAFOBdf~YVCadRPGQ?TjZ`yo( zzkbjGjEZgf7pWhf1@j}``7n9UJ@ak0y7{`a`$9r0+7nAddCrnI;_FLJc+6b2pqEw? zwgb*%_1E<40Yq1fX{nhHwbp+6bu;c4*JvX?_uU&SeP$^wHI3>v`<{!sTT0Z&?lY!d zPTAjwgw;Q*m%rf`5hyhg$1ZQmJD;Z<%1P=o9m+X4?6Iv`-0Mh-LTp;O*&lu@| z%n~Y{<2b8j=oU6bbZ9#cep1%yh){utz5;Iu(Kf!7^vlXZf;pgw5lb7xx)sNmGocGK zQBP3u5j=Vz4TR{BJQPh-_ocOQ*4I@dL>f^RhcV!A6_*A%mT*WbFujuQ1{B7yH-y7m zwADfOF_QfGw$`bLiA^~yG|SUe4z_D~dr^rX#yO!oxTmP0s_&pVf=<_8BLxM9s?5Vs z(F3p;ZZ>o#jB*Km%cT*VoA|oxO*YAriW2eFf(F@;!gyd~3fu)&BOYmv$vmuCjJdQ7vozNDcP>&alU=5xhN38wpORHLx5O zoi()}&Ppj@tqf_))5zY|l+UHzeTDB^6kTVh0k_Av+Z}oypZA?;8=Jd2WUdHmS|Qjx&%)bM>QD)@3HP28aOJ&R@Y#c#iQonz6Z zblhF=FIU3%L!98My|<|EM!y5WaZmAg8tkl@@V*Iy(}BPHwUhCCy;I4UDOA zHzwXF52BFI(GjVXp>U-Q1{@Ef07X^sC%POn%Hvx|Q{i%O2<)4NcWx7fPC9}4-1S#+ z?|lwQ4HK@~e`iIdQ31=HP*_B({v8}w8DsxHw^J$8^%s7-5_GFD|99G{lrNgI>UO9N z-{k#jr!M2@$%0X*%yPI~yZ#&;|4+rehMlZ3zu4oZNhRYRJCkvs%lm1M5?+*0xX%dE z#>ksFZhIW-?)rgpC78f{Avyb$GhXuRucMy-vYkp8%5^-X2zQN2{^dKaS5a15xFup( zHS5)kUo7O@R+es8RAw)_{tcvwt(XkU&d3x2TnTF8oIKxvqn-u>^xoNev;XW$Fe2Qm zxIOBTTWA0CimEH2wk2HCZVCmcERh?<>WKf_;$AkL6#sAEF~?_g7v)vRV*0JU@h_0( zozsN3l25m2dM3EIou&Za@joCcOJMDkmK=bXh%*YF^xwE$JKdB$0c0m*OZ~w%p=YQ+58k&tbNqP@tetGM4+!I^ z!!U_hbwVM%+Z&N!a1SH`b~}Eb_lez%p}eUozfbl1iSpm{0snRUuH2`Ot7o%MCR9(RhiI87DT z+BejK23{_b=geK}u$0_AJ$avn_PITZ41w1%4U6Y%kMcly*QOvkcFmI@UNmDLLx3sP zc36b4K%AlqY!K@1Ndr>+I7!7k+tjdC!Uk4>8^U2sg{Q>rXgOv=jjObP=YN{X*u-y{ zC&zs%3NVqNenCaGV<-@m2Nio1%Un zwtifCZW_x`2^TSGG!9G(=^j4d{520y>$@N89>X|*yIqXxOFzuUZbID`EX|aApI+6R zK<{EHT9k9+Lwfa8*brIaBntf&CzG`7SKFgp2*QlLe9O>N)EY#q4^DZ=iT4;)Ga;f4 z%}V_exSgoxq&vdDvDi!{>+gxYl&Q238e&C-`$dyk_Jqsdog@6N{i=Gy`#J5mpK0xH zl}HX#`Vetu2?SPe_hw;{8?-0V(mmMV`KtH23eXK8Es(D=REW|hsQ*a9;a1)M#_5^P z#CGYmJGaQjB`O{$S0H4JDL>Lr6{~w6&=5$sKJj96Cgx9|l-Pg>f~qZ0z7|IM`#cvi zZp!Zwbb=Vja?AF?^U4XO%S=eP;iy7Yt|YXiiXV1tVm|#GL3cMBE@&E(3_Qb?Ok$NI zDN18qYa7CS{{z|j=s0rQvK-WHgT_#Vgko%Ai*CG(?s3^lN{!?dEOR|2fpFMQ1q$() zkv7C4FLYDH!|fpv?c*DJZHatXhs624XbMejq{i;!=DB3Z6e4pu;pCJ%lw6!if=dV|<;QCLwI2v}Kzqvf(2X+>c{u^T#lLdN@&pg6!!6 z+Q`0^pU@YF8ANU{)v9o*XfJ*@jA?rO=tiMV0lR>( zZ}=YLW|B(i(VwvYkA&Gt*{yNWNx~HE$uvzHVA(CW0}IE>D0WVJUiK$^p(#^p!b5cO zxp4FWPyhioBarwwQR$Yw6 zWTVG%=aKI{LhaZ%wFe|{`RW|?cZX&de;D*H`Zt*3s7BPSNZ$={vk%J#Kl#WVLD|LD zGp`m}kFxS@rVb(j*)UZ4O4JCgLNmzsGPGbwLkr85GhA`Ix8i2q;7~IJF-lkSB-_M( zTwAN(*&2hTH@(lAH$nkEkx^@;u1dRXX@&A8Nqx#}Cs$w^(Tx?m;kB=z-xK;*$O6-0;~))M$@^Gtrdpfe*K5q7*FA z;V7DAK3~FeM+d=)SUo>Az2iAC26w*<5TS(VVNN`#x2?+^}07MTtXP{ zcfBoia%rsy_T9D^02oW*$>GB2J@}>YF7xn|p>|1?m=D?(PmBLd;LO*Oe)MH2{Jy!^ zb;h3|&bta;x-0Z{g^JFM=zKn!gZz4*a*o9hM#}*!%5MnMBtT5*kbN*5Fk99r7=IDBXyguhdivxzPJ3XBWHu-da>K^x zb^WA#DqYXSiZ(ZQj;o35>gigVF4kXXLBGP4_1>|s zI%uSss9Sg&gSaG{RxY9Z{zLlh&BrC{%G~$+^+z(8KOE6MLyDn1!8A?NKBKH;np7+x z;fGc8zIW&YxS?D)ba#c?JeSUbSRzPdYU!J814{j%XcLg-90FM)(aCJ%%Uu^+?EjLSF_ zVXzqUH~6wW@=iq**JdP9IXL=~phU>O3}jWgOi%)fpZXTZsT&=K32xr^tDQ*bVx=pH zqvOFyyc6-jqb`@OH$G&KeSCA6=8hK zO3bfhD_Maj3&$m?fXz(M-@hb1q)k+zPr79uLnNjjY+&MzCp7;IE*SA;D2)K?_Xd$f>41W#raOSo7s0Vx*4#y&(em_V~fB5&Cc zL&tKpH9Z9x5DRDN^x7&xvJEO@Z`sQY;}0s?WUI^6gif79GYSqy%zx$o1#j`g``H};cB)P(WBQjHCR3QWP zgzNG2WASD$0U{HIx6cO26)mnEO6ou=`x}ti=6!5RSJ-PVm6nadTZSzLnRYE}>nL5h zS6Y{?e{^76Ia%IMm{~uTgg~l@i!NXmgFUjbWc^-23NV?x=>l$E`8MUE1s+U&kSHt@ z#g$6VA#9YPGL)eTZMp}d!j&tIRj5!rHEg2SrsOO}AtbyN2XtvB-4C{fA>0X+(=kvB zK-Z4qB|KMY)(3UPHlYZC2t%qc#3&uv{k0mXV>@e9P%`4tQKj1+R)#B)@>RVU!4-cV z{1b~huz}GFq2@_VPLI+7=Yt7IurX+Pv!nRvkEm7|oz&5{*L_YWvtGMc53 zTd8H?W43}8EG^b_ZCvmcSGtD)jVYkkDmdJ7=bX^Y0Ct!k!o$7hJ+mnT3;&8vWpue|h zwb%Q8MC8Zb3h_Q$S-x$-d}ecxejASpa%iGC#0(_V%+rrU*NJt4h1=WXM;0NPU8i%| zp=b^XQ0ON(>Bt&^jBxb!{J@7^_Ba3F>PPCuY>UNz<1RJ9H)IYe)oHX@bbm7^iK5{% z0Hb?~km1v3eF8#`wvm5|34co17K}0Ubd$rX6fB4kTlbZgZe zp$@sHHwYUNs`l?@Ysarr|Gyk&@7E7=l|XRk?{}Tt?*Nn`_~$f&qb9%q>MP1m__Hl= zxpbhi`X+1kx>>s1O|!*`unSg?xnk%k{zEPQO~A}RG*8WNknfWi!pAf*A)%GGnIheM zt@BbyF=%lN9|gQK%Z+ixuLg}|1YpdIs}YR*C@YIV=>~YI6F4WT$T7SDFak35xm;^D zsqdKvyiS+fsRSFB1nC6lYnxFVH&yy*6k6-U6EH22`1RcG-6eE^-Q#(55o5v=8&Q$a z2zfDX#$Eh$#bX~#8X!3Lf~eUfazuLL)g)8cSdWb+30Mv_1*7y*9}RqD;fpSy&eI+N ztJ-{Q$FyU^{aE+Nyhi4V?Ug zTeZUUCSXjoBD4&z*CVPb-(1>bFfecrDRqb92BC|rP6*)R8}snR5vbFGJV`2n!8iAL zxOtScdX+t1>w%>nE!yg<4`?*%$Acq)%GdXK_-k;4cU}IC&2$(L<^knUf|nK3)LVt= zb6O7X5{LlcMS^s&nY|LPyT7RcU1g7NY~?!e-uL=Zr+E~aV(ux*Bxryhh38iQ7mBsPVU#+o%<*1C_+s#cl_Jqb%)UG*F;E%n0*|u^lifU!n~dibKM4Aqm`AV&ZN~ ztmvsUTSa_bhb@O%OLJ_?B9tI0?Z?~V4&MRo;FH}&PvyPqN zs-!$L$e=|xrI%}%NIQ~sKoFX$$S^OYCq_9iT#>4%0h8s-(W*FZELE+5g-p9o8ls+^ zS>Ac4S#=tY>=bfpUZhYR47y!8rAdg{LM!FQMD@M{7OT~pQ<|Pvj;yh+CH`5lu^?R> zt$hFhO>%oM2k31PZBmYUnFTKyYlLd$Uz~I8#0qd?aT5%yThI9Z;xH|@lvJDlxB$A< z+rIqZBH)Th)9%?or(39%>2__8D0x{1+C^q~Q4WuZuqlHWI-@fKUOgiu6ie0^bQvTR zoI%bCS}NueB{jX%^cfUlR#_F}K20SQHR;VN6>L+rUx_;75yC$bb%#P!EP&p&lZS;M zNn<&Jt(aPnhxfs6y)E@lB|sJU#WnncDtPTW(^l|JXnp74#f(_7djeBUZ`7_#g&U-~ z{TprN%ez+hCXM^{6#T=Z9c?fPn(YLc z*lgl5nC`x1o(;^p{~eRC$^HG!4Jwtz(=P%e8hd<&f79Du6U}ImBFL?r_(K{wKYTr^ z7`sRPMdQ&T${wr!NHDmg%$g5<_c|ER+n%ZB*ro~7j$)3Bc2v|CzTIv0TBt|vFhor} zI9d=YcHReqJDP`j?}=ssB(OnJFJ=`OEoEEX^U&o2^tK^%6BBq%v5hI_AB^}5)(?0? zi=lyaBhxaD0tNYV1oy%#mqt^BJjRjbUCmcVU%aUi-w*vIF{-G!XA>4m<-LQt<|HWG zH>DST@0P>-^@8nHI%7xmt?N)gs`yKpT>txSRg6JQt>Z3G(IYF?9)s9N$1#OsGvL&o zQ8e;5n@11b|RF#Uuvh)_=GcqCn!X>7qdW&Oeny;M;qVhbn zS_Opj((8yq_xSn)$)@mJtkg@FV}T(B?e9WuJVc%xPlS3+K&-x6;!+-o-yd1u=^faU8@z2ikh8@(aY z^L}jkVu<4AIPMt_Ic)ZFTpS4Qqz>%3l7E^sw`#Lp|Gqn|eOpr!Krc;$m{t*K8wIeSr}EZ&@#sD2Fe)-Imlf?rIz(qzJ|sg9dIO^#|mJ@=BChwfrQOcfp-mZaKT&0+ zZv&vTLTn%61VOX{A8OLk zg7?Fj=EE_d@C-xzWs*P91jqnI6NWPJUr{t90c1hD8{-eF`SduM|H^RMzwi%Mw$qI3 zhgk8sJW^3L#`y5_2U?1aF1#B%rf9OGezEp%0rHo{1jRDf1`8}Hv2r-X?P`CgXny#| zk5*zy=bHv+K6=+5bW73XGL^;#lD1JAKnjM62;0srU-qPjyzbTr( z(F9((Uj@W}Q8ZqLgP;FnfZQ)n9iV6^=f?n=;N-6q4WFNQ6DR#`qx@pdX^{`V z0_30mMA4)QP(JE-oHsadwy2}b_3f2`vB$U9CeDw(y|D=V{B6l5mg{yw{=`evX)57d zrn~x&1^ENpRf%P;b&rS0sOUB_f3VHJ>9YUocR`F}d+n@L)|mVkx@@w&8lcNa>>3dg?N>_PHm%aN}%7!Tf^$*IXNzRJ%7iFVvgS=pL_jBj6Q{(6V6J_HW zzclc#lug8U)Z;-k68Gbw--jc@pZK+@@JlJC?B0IXb6A))9=Dk6%o1fI)&+x1KR-lpU9hGBpak= zu-YJ^;t__mg1H#2qNq z{Nq%rPQTG=y6+F3^7HDiC&6F2vVZrDfB&~9L1N9nuP!bEIiQ4^m}Aw%pma55pz_sq&Z;Ya9Pru zG8Zx>@&jt)g%Kqp+H#E@hE|<)U$uv8n=ku1?^f%@->e?@bpapU7M?``HZ8AyYFW#`=5PzkGMiCn)8I7!_OBQwco>BF90O) zi@&n;kE^d}2ROJi62$-#I9Uq`L%Bs^TLfc)KUUwXY~=8T{>Oi;Qe^@#lTD7$U3a1b zf6A9AXhC+=Y|(9nAi++YqcJko$E{N`ktdyLS-7IEW6PvM`qZ*9xpOUt;IGAhlr1#&o2{=FCa9$zORl!CX zn?#k3Bt@%Nm;?|jS@#Shg>+OZar_2^K(A&U;`qgZv?}+>NLwh6oLRx1llY;m4|*4L+#%+Wt_`rMMkRWi&+cgO z%~x)D5RU)Y6UEjc<)VA91$T&yc}_n|+vl*&H6aX`bx6wJ6gtUPo&3P3(OMg0AC!4K zj><5Lf(>?UB9eh3z_~5O!v&Yk9~Xz1#-tVZ&0^Ws*j!#CBTEdwOM5HoJFJQ^aRPciXy& zX1)v}xOI<5NoZz{$5~QCPsi@eh91vI-w9P%FqW2-eaTQ2s=9>J(SGrszyQ2~neH`r z8IbDr^a{6rR!Vkb^bX?sZ0d{7&&$P!4{xr&%u3(f{3yL4rbs^y@i2bJ=7M5DnsrK z6A$Y*0_<8`;k2)#FRW>T-{T<^w+5&1=IJ+r-^pd{6Ef@V1Sm63gqXXtckjpnQLjYeW= zI6tzl^#MzXEuK1=B=*vQ8aBf%HfiQ+w{=_x>U02_GV;}UAy6yUi>hD^j7+Nhzz`ag z*%0ce+@Aaos0cVh7)ZK^9zs;a?&^Ub40|y;3Ap$in?jV_FyK)5;7=9Bc|EdM6-3pn zq?qLD(d-T+f!|p?ZP@gpdiXiMsk*9Ko=!$>z!t#I$VNPNwi_d`ICa)7V#ub;V=T-D^;xT7|EQmbC6JrID`;OYU~I zCVwVdJP};^rLIcGXNVvp$k;pYW{`c&Z|iHiL7q$R-dC^QWV^I27D)f}+eg3J#%xQ% zT;L`_)#>q+Wqw?z($Z(oxTh41#${>(6uo0Yq#Rb8J@fuA1*7;1!|H=mNGj(pdw<3YqnY*`QG(s-jxKgI@RURsbdM`@*NVy~x&Xa>uQjUz zL0CCws%}B&oR=gwMEv&drvUg~A>+YQ@JdV>revBl0XfGT5RaNKx=UtH{XDK-$JUDz zYZsHgQgWZn8U&qdqI<}q2Op@$dBJs`+~tW}hkrdUtV_vDe!hUZQzM4)?#wv$D^=5b za!-rs+hV38!Dlh8lZ~0#Q;K5nivp$6!95-cXEuh%{%Z>`B7wt`C8*W%#xF0M{fi1al~HtAZ8dX zX2t}?j{slZL0hpfZi)r8K%TaA3Eu2ieJ|E8&vQ`2$`S)zwn$;}cu9~G!22v`f{+r8 z6iE3(#aOs28_X^{SANR17@B-h0E>Mz7 zifQ(RzR(!eKQy_}T@cj6z?PMer!K{MWt;)7%|vU_!nhKj=FVt!{+aa=DWT-C#<$|Z zqHMQ^{p%j;tDYRpN6Lp{vT1D>FJGeASPhOUx!dBxUiZtg1*)<_39g2fYfp=_dv|H1 zP2G_2=!=wfhJ84KB*~hOFbbBR=w_hH=A2qvKg34CwQ<RH3iJ2^MO1;o+HFxsKM z!YZr{Rcp<2_vmi*6`kA@t?#%Uj|!jNA^U)P^2NGP>?;ECRyPcZlyT$uzGpf*N}i&- zaCl~CIBuRXn3Zo=3O-i7|qO!{arwf9Sk*b_w(tiq6 zOXpE5(u-j54b-W|kvG7lb)mxuiXa7`p)%rfXWETc@0+SnOxs9_AZc+It@c*hk_);N zCSvBiD57~xgP>^F`TJ_T(YO)861s{Y+lS;C@JB4f#3r;BmoO_|J4lQtM?{oMR-_3j z^oMXPOL`0kG7|=`1A1GOworsO5|LGpqKuQgvk7Hx6l7_Ro)Ob20MP>l3ae};4BO8M zcUy5{K{P>iinsI-grVgkE_^=z(Q!Q6ugEIHKH*U}-BD|N>@Rf4&LsgAIhKdY2UnTuX{gkI0#aQ7;vRFHJw6Ncn)?PREA1H+wIOs^8{;UA#uMT#i$` z!}#SdM5sK24vym=e~SnOU18fFct;y!zvkije2-c+-V!}HC&m?;2Ftf#xmbxE8W1B3C_!AWNk*WEnp*FpNk+z!Ar&xh}g@;3eQsP=n7P7qPJH{_)iPBc8TWEy(rp?j@Z=Z z%CbQb03msuB=mmp7l4y?UKZf}(DsT4Cx?!I?ln@uK{WTMYkOT{XQ*5pw$DNgLjR zN~!OaLYr(1A&`bih`$odHj<~GCnRAq9kZMjpp7{CAl;CB7_=JHhEvVM$l3sD_QnYh z);29*M;s>s1dy#7r1H4@vK=RI&)fY4tP)o3ss^=AfZY4eXLJY~o8x6#WP@$iI@{lI ze*>w^s3mA)vSlcj!E6xuCj#g{yc@~h2Q#P@-Ad|y5kP;{rO>`B|5shg+T`P3x)f?$ zUBA%@;Gao$%NMr;PH{zr7C$0N#xxK48Gf%yR64R2@%l*U2A(a_r+EF3MZQ zx39JP-l}m3mMip~^Dn@F6VtszUa8}?dX2gEGvKur<@3Q3B30UD4(&wvz03`viw&F$ z*#)YAd;Y;D^1~(zpi2?Unu-337c>`rCAdEmOB-fVE=QhnJK&Teu}$FYs#{FJ@BO=^ zPGs+YHsBQUSBF-rM41~688Ry8d!O$Q%a(#(t;%?07}Tmf$*yrK3P0M%lkEZ$E(P@O zbYad2TH^gm)|{_~yp=rIdUR<{Vw+_&KbavSA<&&7naUp2cE+llzT z2=92)U?bnP7s4Um_m*9^rSk^+6MPt)xS`t5?Egu6iv2EY%j9sub{LPDLla!Ua+Oo# ztx~`4FHKO_0?c%#cfW)~x9?MzIk@h4oa0X2(Hjm|h4Vh~FHev6R*(8_YNqZyL$3We zJp^KKpBtiO0tPwQxgQy02>;Lo{bRsMwTRX}%3iUiUx(KAX@2m!L8C}=-AV0p#E;FF z){4T`9sG)tQ=EV%2tUSPUZv5(?{<)rrgk9)3^?^qY`(oV(tos$4-7av-_U9z{UU(Q zS6tNl)!({z|1sbM2%zx3h>P{m_lO;n_a1j_52SUqvssK{<*QjYB;Oi>@N@m0vfZpt z6z5!H><#y&rFotbPE}TXZvUViY$S3}S6HXlS>G*XxvAV=@_k{pSPSyGed)7nU_U{W z!50P8(^;Y3i+ziIj#ch7wVC?L8R=R4ssWinae<$KudTT)pMXSdBsVwI8wM^nBfq@C zC$LeY>BUn#S`@>Lmt;I4FtuM=^IISh`)Smhnfe8DzECdBxoWxBks72?hMd@)E-C7u zNtz!kO)N41ciEufFp2n%(cTr&G;Pi{Nc5Ol0Uw^lLx0A)iNz*Abkpjo&_e)5+-xQg z3+;qqOj2ucZlpc)KaxT_OTj~!R%Oh`Gsal+R>);tK4PcijT_M|L-9caL(6x(?%LAJ z1(Cua?mJpkSj8mB!U`3#d(2c(TPm&l={RZ%LE!mB;%F14rFve*5?fVtH@-e`qfu?V z>*thg?UWc&han^@EVVDV zdY*+Gzu%4ePP1EqccV@N5bNBI!Q+=qs$o5{mfsiHGlnQNx`x<#=#FzWOdT||;$J%P zRmaREIq6@sPP=VI0hRkI!Q?1S?(D7D`hhr(zFy4(|8}*0R>d7TlNo9v0yeOKvp~I8Y7n6mPNyTQ9Vg#k*UXEKmMq! zFn;Gt3@Y43iI^lak{m6G+>l6(i(Wh0|0pTig;jB*CymA}stS!QR)Rn|JF4cDdSvE& zedzZ^W6O>@c?Nd9T(fQ!K7auFIip)HH&gGD$#sOJELGW;V;)*XXhWo8GO~4>GOg$8 zMK`lO#cDaNi`?ytHuu?LqtY8C51RfcbJ$?q#LCBfrthX6=e$JD=4ZIUnl69Z*FT~a z6_oakK^9645mH0(eQ-I8);yb8tiuEN|@nksYD zlioyh*{-&=NXE$C*ZwJX8&eFA=|xny6AMBgy1j!l9}kg+Ekt>trF^HqN3!ny{0p_k z1}=Wb=(RnlMG%svvSy2na9 zvgy3Fy($?cSfwoTHr#lTaf))^Y#@>u=CfuEBf;x9xmC%^^^Dz8*rztH;0GMNAsU1B z=kZ#ddqYi;t75Vh-fj?96@EozP+39=NAECvM@(m{1WS9U!o%?Jaz*>wuuM){k~Rj* zy8VepFS1sr10AJ1n$dZQ;I4aoegB;=-Z;cP1ZI8H-{|i_;>1oLLFbGH@@o*pQzH)r zJYtg8{92uQ?sg6Byty{t`#AqaUoYir3I7;KO4;z)k5ddLT)frh#gW?P{7unCO}u5= ztEqycNl2sEK23Va+HrYirn)|pmh$}Ey(+(^rNxgE3jHhU`QZ4LCj}C-^8@qepae=e z+MYC%y0 zlPmPy*kF1|=!3H01>Xa7kebgD1Dr+R7*mKU04_C;U4a@Q;}eKM=gnxMnI9g`wj3dK z>cgE!0K`F%SRn);qLEH*bY?40{z%{=Rak^*n1ymM(zw!*!OvKy;+rw*N%*|26-j!O z4sR6m5FM|`UEy#6JQIkpPN1=iGYVZaq65C_2O-X5`kbo{7Qimi4Y)v#OdZC>l*N9t zW6&B%<8T>m3xx;5X^!;KqF?(GEy%iWSdi_7z_!q;+l}MuVqFnKvF8Ge4MdK~AO(a@ z{5djtUXSKMEzFgbLGVM|f)D$W5@X;5g8^$o&Sq4@GI{NCLRJ*vC(Ohqf2Xr=@izuG zGf;F?WE`dw?7>THL^#f9Uf_LxJEvd8kRSd65p_`=Dmt?!Gcf@G)Jvo0fcum6AIK`d*ms4J07WP^CXDcA+gfCmpwJUak-%a;`#v zevBWpEZmF)3LL~t_tOa+jKn-iwU10vrPI4dp8|`poQ< zi$peMykuq9h9=;@NtL%p`bbJzZ^GDhkzq4rE!D2)fPg0o9ApRtLsb-{Lv+EsSRCOB z#5AT_?_OuBnF@e=)i|m$5J=Hd6`AU6!ecTie0(&TYzh|HI-L2`@}}7i$aX&&avV;z ztg#4a6p2D?Q@YOaiCu}zgW&hE=Y}w5ir+hKL zD^7}eb(6P(5 z26q{w>q;i)XHg448iFCYHm;ZfcLGU8T_af0+6r)VjdZW@2UXd}T?;#Vah{7p22Bg* zc(D7uvBtc0W>*RjbF$AwAa z2``-=2ew(b*emq?3jgIq(q6RnYCdFYz6gV^2v%-H9mRT;U$`I&8SRB2n-xW)MdM)S zdgqs})I(mfm89F3VIUVl#mcefi>S$=RbSyCx1J$_}93I z-M$wcZQQMgKm?+rnDWo-Wc6e^s~4{>>(wWSjDucs9yl<>70di%oh(eOfqnMk89nM} zrhy56mxPAjveL$-7Nv)I!6ID@a+Ed*q94yAHI-;xw_g;icQ;$+@Fw^9Ub?Qodj`CP z+%DHf!1Oufe_tox(ct{snMDp;#mL_8<(gTYL47ty!%cVJy@Oj>DRqJ8?S|&|%;NvW za_!&CO8>rGTYjCYjq`J3M%MAfc>_M883k-;_MG%1AxbVJB22-LbD~I{@>W?6Eb4xp zS>dbidf%-eJW=KtAoW2;Z?e-%=b8h z3_xPGr-J!|_`R%!|^^lt#^ z7Q4Tw|4I;wN~ptf{o zF{Q35#m%k043L%9H{~Q8HnjF3-m(KrWwFMliZ^}i&E1E6cU#)OV;`oC5*HeYg6JAE z3L$KFKRtd)7VKcTkYK>r0j!ff9-w*x(nghtU$W9)(#FDh*26AyPJWcn8%G1P!^ijp zhynR8PvxE9b6nRLxD`;t9ggE6xufXg;lE28-*fNkGM$W(zuU|S5*zEz9VRtbvYo)- zrC*0(c_g(7G7_BIOrd6IqvLA!p>bA#}rG&lBo~EAJ+Ru zgu;Xyql69^qi4kAlX1=$?*}~#OIu_1*i{|b~f0>5VqN`4Go6y#1J^-IFc5( z99UWLjV8}UrbVv#zG?3z=sW)9A-LmQOZN6SMSTtXZ_hcIe1cA?}@GjedLY@#y z;HFK12F4se>t?HxvpgEBIxnfGT1?PAp|~N_cB{%(NqH_h7JtGh?LBcgv;TO0a)%um zpj%dYq=kEj*^u04x%X6*_e7@RP-K;981lMhZ&ld?CU^vs6G|CP33t{?K;_6P%5S~y zBz9C?2c)ZNKE<~wcF@Lovz@RP3>>wNViV)?p?}Nc%N}M!h0|`+cpWc=SWQyl zOrJhDx~#x6j)%BK#pW{JH=CL`QLnJ@QE_%*wtv+Ic5O&4c=K|CKYkB<&w+rUTwqn` zS^kLksdCODa;&I)&&)L>Q|$A#jl`rMUNUZEQDuJZ15Z-~)dSdcq1g$GRJS381NfEi zyR04AchN1Cb*Pj`)7V$UYPxAbZS3d8 z`|1=67QQ8yMM(+eL9gLHOH+$B(r-das+x*q)Um!;yp4-&Ls|9Yqd;(lEH*XE!`M$e ziEd6z4SLBMOxFB(-k$^-8SlFpwqobsrxqkTVq-T+R(jJZc2eQ_|M2$K4^i*k`u9*n z!_W;gbeBkXcc*kWQqt1hozg8zx1bmMr@i}unmyBFerTMUW>f&#i&O@{}8IJ z+ca8LZbt^}ou)>_hl+<9J2)1&&rY`?;6xZF#?20A@|s0D*NUsNdpl4rg>XO%{*T{B0ql2*#Q7zH9NE$Z}9I%HCVsHftgzS zB@trv#uf>Gm|C&-b-sp#h2EN&4s2umOpaGG5YHOLB3dB|JwF9cnz0Af+k)_T8~qF6 z_5&U`sm!v;4$HwdG;L$nM{u>HX6)D^U~XyGbpA1rCTL`GY?b%P+0${?#HI&pPDG1r zt7GmGy6@MOQycKBTq8B?jKx>cM0aQCghZQ5h{o4t`Q%3uy|Bw@KD`T4&V8Ld(Okjz zscDy5+O6QYUP7{SO#|BZ1{tTN8o6XMsQG*fHRUS<*%$ZgJ% z$MSLYsP$dKR=nrMhgi$D#%B~eb?O&%zrwcR033iI_|E^zISKnq|3g?jt0Nyy`z?o= z1Lrn851Ry`@52JbxsYZL*GQzYM~y zu%)kKV(N(AsLF&lewn;uYCMIQ6qAM<=cKKu!&ue!H>F9^k8{_~Np`nAi6w+K?wdQ6 zkjZk>?|vB*^bjZ&)L7`+IT>`j?rHZL%MMvnl3Ebw<$sVrZhrs78RndHA4i5o3+b*X z#xPb?Ey9FF*k3y508ChL1 z%Ko|Uy$FMGIJ)`1GVw1vymNR_h1yL2x&}!_Dk-%E4wL_-&A6$3x@M*2ji% zCVkImUTtmkXh3_u)tue<+}fU^=S75J;;qh+ER)@s)h)3okp~CFMyK%6yNH{dq{wMbWR#>LLtL z6qRQ!2J7#e9M@1Us1CNTkq?bg zJr!Z!pQ7*o@oUPzG3Aop5pBIjKK%E-roT1i?!b@_|8B|!AhYiFz!6FQgDLmdlKVaS zp1Y2woA{TxzX!ky`(pe5q5i!pe)bpJM@BW>Kemqn`n46~dHi&Xv-VmHFY`r%<}jF> z>0gz|MNAmoY#)D|SNw#O{Ad#X0#ede`{TUgy8ZbZc_;;$z4W^ye7YydLPZpw$ zoo`{sFnVQNZy&!m2{Zw<_z)2Ot=0FMT(trFFVO^n!>@UTQT{VXiA~7-BN(J)?jFj& zLrOxuEp8wsh18UP32XSrFSh(&Hgdmp_D=dXi@Ki74wZj^HB6s5wx+iXKen3-WZ8-O znD|7WYt>~{rDNkaSVQ*sr@w)e{FwV$_xgX`K8|sSP~IAL8B$MB-XHeIjC0pvu2a_G zTIgie$@{gvy~d0P!!O@_x&Ld-$gDv`;y920e58S5}*7ChpeQoms{*wcU-|#vQ70XXy9N!vvXy^C({wVZL7{Y(Q)+%1u z!1m^&dR&W{|3l2kpT68b9|rA@u(thGd;2fN3WnGD(cb=Y0MVipVf3fNpucHv0|cT^ z=F0EU_j>D~T(V_+mO*hjwYlMz>=+YqbVSLqpDo|>l{#C=7F$>O?RE$iCiWivUi6U@ z<^MB6Pg)rY&h1-)qr-3JGYY;x9XnNA{3UUt_7oKV5TPt3X$&J%ZGSZTwlU}2`C#KS)CZcU0z>mH4dG?p z3heUTUz;eEDrE2W+Ydc>y)ZY??XTiJ-wTDlQ4KKj@$L-!Jolx$`{zv^{Xz5zc5dx| zlT7tbZOZ99Hq-r;cNE;K{0qD=l3*%J5G?GUN|5kuo}K>H=}TN$m}+RG#A%^oCNFAF z7Yp-*jA#51dw;emIjCj%;!P{IFMlOdJy%eF;J^CY?NC6;upYmTFAXRO$+;<^6h6fA zhR}0U^zAEY?)x#xl-yBtsIH97-K?zfi&Ynhb%DN}c+@;`4$*19O53WvwYOPQ+YNu= zSj+uQaWb$03#CTFQS)4_8b+piT%oJLlGegy?hkUiT9(6W#H2?9w+~yid=9*O-0@L( zgHYO~V|O4A2{^afx)N;~IBfu7n zsh1phh@K;o2Xr2iS(Ew7+i`EJS%d)K>jiW`DepN|c9Ul83LkrY z;7<9L{N5FccQT9j%DHXU=ayaT9BBqLbdulT(gOhlS*3b+ffN2rorDj(jK)(4X7`Pk zB$ZaB#Ui+Mwkn$#)*-G)EWN~#?C#2`0kF$wFSTwpDx=ayH)}_0wS6GDD#+(Ytiy3I*C*aE5XXJ2GkeGISG4uvrpyjPoEhFXOs%B@f#=5cL%m*>FXu@%l zyTKso6t8uHUNi^!kiLc@*Hu6IsmPvjzn-qq=*F>e; zM`#_J6x+-uHZYL~fn@umDr0i1q3q9}h6Fqo0|iHe3SUT5^6teb#Z%)~GQMEebc>~6 zjv}OrPPveI5kmpLgbB+bHCu@ex5S?mo?gc}d~8LD!eb$)+w&rPclA5NWN1+Inu~ad zECN)(j?1n{&WoW5c%3HeW?hrWHDgH1kdx~9C%a6_u|++Mj%LY>6PMdM?}LE zVF?CDRq`sZ(Aklxisy(G&|Omn*X3dZ!P^oMjL+31o=?`89HA|I8ErJN$Ek*oEHjb8 zyNEq4FdW#W(p9N3T5~8)etlGn`Igk=1Qro^wR`Izm|4w-R$t?IvaaSDUJ>$qkntsB z(zt@5f}+ezX838J7!Dl4zlE3 z=zHB(LJS#{Jha(uR0qtNQvRm$+ku+Uk#5vEUYp>@AT1hO8< zTEz)zKYK{n<@tim?_@Kfo{+C2KxRAK!7Thpb-nRe=8=D%;aqp3UdN^gg`f{{3;F6v z&*k&b$n&`QGT{4r{fzQFtadO-zqFTlqCS#uYyr!kpW3d)OmrZ043r-N%j0zQm#$gN zIaN=o?5m4OG^c0K?}6987cC|V2ah6d22ZzZ;kvV)h-)W~pvsDb#ls1QUIrfA^K*@3 zS_B{vn?3Ndih~37ShL(>N{V3Y$&}heWbq(@(*?o>pjw#$)y|;I^MDXId@}$UcR!Uk zBLM!&JQ(Oi0*AzjKuNLy;G!vt2w)6`pLDVjH#8fE+w2vWyj-oB?Hvl#1>`pbuzqUq zgM$z0q3h`d@(=;wpDakA((l@`wEi{5=Nt^5tm9&&S`xm~>)gRAm}r;+{+fHFZ&N zIb%q_j=al#H9I^gXMOx!51oA8GOiyMztl*vJtA=j02UDlK#cDu3w=}sz**?aL>E^^ zbH0!s1r3F>o!;fi%L7j{3KWuwtf@6W_{2VW&_@Q3iR?)-f_EoM(1pDO9gmh&C~2M! zaCH!Zj{ui@6$P)`6UNNp2k)!gSwrF;17yuz#nk1@e#(~m_@WD9YI-PUWS;A*4EO@% zJf|?OUqPY|-!+SR60ryY-J9(8VsJ_MBvX*=%=%$DepzbqN$?;MJ|$#&GWa!S&n$X% zvAlrV()5OtZFoTLgs|B%JMK%u1x}0)K#~Y}`~(<0_Ds_66j{_rdP%#sU>~&skgL}- zq&9Zh32pyYjINpJ^M+*jTkHH@gHbF?_5xZm^K<9}vgDURK9u7Da+zXNqW7~7o*vFb zrSMe+rVd+nAOaWx029^IiU+Ia|5A@J=x3#gpUjZQ`4l>g~Ib#6e zWWWQ|?^>;3vjD<$>1`}hRMD`LpfdyPThqRs^bvrI@F2$V zB@8i@M1L+9L|YtkUmk}a3;?;BsOJbx%s2zealTfthD%=Qfqh{HG64XQT zqs@9(^j`Et75l_iWyd}|B?7j@)}9*cd-GRh6YNatlh~kMN=Db&;Wmovy=+3H3)c9M zfr9WV&f_goRx=4m?WR0QM@9(2c)1U;?4>3 z_Pxv2O18u41v9Qh^pFJr04$S=*lPe5GfWg$Bm_RXK?8zmdtB8vS|mWwy;IcqNr2KM zlD|y?G*~!+3q{mj6)z>3pgfztf$XC?K*m*4t_@ubz(N`fmaSf>VC!I$F0pwiED3?me zIKY521S}NLUCy>Vq!bZZfgpng>=)|rHfESJ!|oR;7)(U~ta-AJ09vMcM8gk&P{QJ) zVlprokYsbm9{S-vRNVu?98hn7cQub?*n|l0iu_~`RSzH+Q&rumfrhtxk6qkn*1qb% zyb=f~m=6V*_Bo|aRY1Y<$0(Jn`v^#`NIPJ^=Ns_T^@xCZ#H-{gpNDYX>KHFp(U5{^ zt4OJ?z4r=Q zB6rsX=x&xCnPckGgJMZee1NuS6p*!O2}5!MHttyKlY%^ZeV%OBO-K? zJtHCCF^hDadbIB*mQ~-N^IQ_@id2P^bNSkjprj=24&E*wgxrm1=O^ zwkTld5~bo~_l2x4z-`jMyN-}ygXqccgXnLl*gaE$ID%hSlnOt?T{d2Q63b$>9C`4s z?MoWX*pET|<+1%V{A;%%% zj}Qwg^UJG$LnO=N@`J8JEUbN6|XiEyP4+lwPkWeNt4| z2ig}^xJTNH#P~xaO8f;E<&%?y{@A{FbeArtevOV^PJ7nj3KCI%nwFF*SaH(#-F0L{ zy+S8tmU)Jdu8}`HB&p4Orp95VexRE9S!XvX-)FAcw!nO4W39li#Fsc@xeG4_m4249 zvM3(SRwpk7r7Rp3Iytjm-lM^UK?46teLx5(aX?j3Hn)mhWhEWjpSMKpIb2mf0A(f< ziQs-SDjq4ov{hT7(?RZ#eZ~-Nbc<(`ua@X02X4Qn!))>7?B|!7YjYaj8l@75rgxQ@L5=W zKi*dC-T+fVZeoKvTrXD}g@f?^FlkQw{s@)81{Tk8J*$Tfs=)PclZ);48q5TDw$o%Gi)3gb+st!07T>)g2GcZwe(vH+v9Y|< z%a+9c&Uo=Y+GkToZ@giyqj_W@+0*6kuA{oE^Dk{*0~Mp6JtIZLBHk4d=`wM8eEHFO zTr54~;&V^vu?WY|pbbBy=%mp*L(WJpgg+$}ps|!V{|PblMPI0RG7bmQC2Y-vVc!1W zL>LywtUtJ0fGWdC%YG?E!HN-EZejOSXY#x2NX0Uo;tnIasBj7yG*rHy+7A&^)(V80 z77b!m32$MUQ+#9B|~t7!Z+z2#69BAzq) zWw{}%2jhl*;z>}OJs_~UY0^8UcZonWSw>x#9y7L}-jv(xhRo5h@xXEQHARWADno|> zXfdTJ4=TPQ@FF$dbSKV24=zOf%Slp!#tYK2v{(V`r4TgnS#$7$6$SVT((4LG&4IwC z+&Qn1LEYXDZ=|uNEzFqXa)}VssI??UT0c?Gd02oTF)Ql-CIa3w9Xuy&uOTX!L1*OT z*khip2z}GTzh9({Z@M_DeZNTavd(J6sVTYHVm1cP zv1_w@-wENCf3M029;^)4J8?N7@J}x&EOJYr6eT2<<^SrljkqU*=Vm2n{q*(REZ|7MH1`xbFAn&N{rb|8@vG*W$gOBYJ;CEDBT*f`MWP1Y>wz91fVO~&^jOx`K8=JFtLQqbcNh}n)_0{~2BMW#5Nn8a;LGTED zWfrt2B)Kqz{=)|vc&3H02ZmUOsQdv;4jXn~JFWIh*BQB>4 z(G0bxi?e=xr&f64!96o6cRJ~4l2}e$U`(pow*v(i$mcVDg@S(T{o`p|s+Fbxs>+FP zR$JF@m{dC#Uwe=_E@5dM*Gwvgx`o>#eAiyk8z$A)9oeIIF`?g%XK$)<6vx%jX#9;A z^tvj?C3#*<5y1b;3o7QF`Q_`o&4Z?FD3G}B1ZOuGc98~8lucB~wabGQU}jIv*!xPo zKD0PTuAEao7d`eaW#MiD+ye4#tXg^r@GbM!Ihzc46f50!red@>Zpp(2S7y?A6$C`f z9%nmD5Lz}wmBfWn`rNEKS^!cKnfP9my#BF!<~{^B3ip={GA;la-~xaA_tk>GepW0F z|1Wzu)Yt#s!%_d*!`c7R!=1bS=;1z0QRyY=K8wBS;S%&F6ftUUmI;-Fv7^as-#5tq zZ`ffQWQ0OCI)2*D=df&_ITGsr=oh~P^w9)dCN=$Vj{hI`aIk>Br`OAb924s3(uE?W zD_Kl(?JWPA?{Yy#WKwpbw>7TrzB%T4Fs?3V? zwR2q8ekDWS#@AZmjv|vrmPR0IMYeHjb$N~fFk|AcJr5H(?}RNoXxEj(u{Md6A_*U^ z7qif9G8U_#oh-|xnINu~XWJ;!Y2_?VZKxDBdRvx(j^$xCGnjK6RxKPp3e^rk9rEKp zd^7v*pn1%{H@^kz;SAO5`}e_)HdDgXphsh{ma%D4l*PGO8CJhq-ka1ow}PytM$@uJ38uD3jia2;SUtvgUOMgn5zyBpp$7bx z1iN2#hV^h%k3C;2b8LIQQ5SPQeEF9FePS={X6&M@i1>pmw!P#6^S|+GIwO2Ie(L`6 zvA4AESLn94aS(EW#^+FERiE$GBA#!jz2;`^pTtr-&j5X-F0k~`Y3B^q!?B;Qm;HSY z=YQJ%WNjyz1rOn?|MWCIzwk6XC6%Ti*~vs+*oYiQcldzXp9ppwS9>H$uxinpk#se% zS@%^`MYJnP@8TMW?f?U#L$5Aj0IX|*-Mf7T7{QK{eQWm}F_sVhLPvDSwg^sal?657Qt1n}r86E|`Aa7-e8&g&|2& zZW?|5Wk0`DY#PS>l;n6?w`9n$B9Ie1bF7S>Z+KW0cE4JLQO+n=P0X9R&u5G{$rRJq zr6XJEQQMyw>f#=zpF7~&Iy+8*2tQ;bIGFx)N`)g#0fdfwkX?^Q&7HrLU@dr%GZ|mO zQ>ifSaOWUbHH410t%Kj${UC3i{4<4i#|s;JnFy$vKL6-60ErPVZdE!KY5I&zlqa~r zD25?HvA%8Md|r$7{DB7A(Ai69M|}~BN|hwh$YiA8VR47joOXegPgGds8tI`ycYq6JL)z^*{)~UYrEsCpH5ah0V53G@nQ*Rjf`>ZI&Ug z-7=yZ+MZryL@{#b4QWaTmd-;&jTNnz4(VrOI<&ufD*o$Q&iU!@YdN*-@138(Z=E0a zFP-1LpPirarc2G>Lz9~vpg2ZlK2o-q&uR(M~k-M)K=< ze2bi`By91QuvMH`&^rJBZN%Q6-H^&+ILoh+v1$DGkqAS*9MuD3rZISic%eqzu|ktQ zmGhqStm`+mJXz$XmLH2>*K&uUg-FUkA6PB-I#rLM(Udk_3tLs115R9rO4^0-VmdGu@lp7#7x3*@ zgCnj^Z-GP(`5k^PhJl2zD`CI8kAsVym5?u%c%);n`OIcVpTA!n)6aCqHNkr$f#^;7Iy5z*(Ei}Hdj$Y z)^5>t5GkI5)f_H9SLMjze6>pryk$i!f!T&=yr`mgLO6S;s{2rKoPQ5jdrSe;Xy;XAwvic)CO-fOWP^s-Swd0y0QETSc~?_@kEB+B3CZ zc|>d!e$|{8W2&b-A8ovu$N#$XyWAhU?) zg=kTF131+!upSg~mZZce`0R$|03tZwn@d!)?uI_|1q($sV92XKk4TO##xR#hwJ4gr zgJp%xO)n8cG_f1ST_()~eQ%ECS+o?H7dy`tfYMJtkc|7>XHc*)63aE4#zLOwHG=n$ zH28Qo&a6zDK70?`1H&rTV9G-DJr^p2>LM|Mxg0qZA{VCoiX2wq6Qsf7F1TmYojLQiv3nWho>2jtGr&V1dt%oL^tr(-lU4~ z36bWlNwb0m>T(y~i{=v-FVYO|p?JcGNVNk96*>*WZ2TsN6AU7_ySxARK?I9XKz0~1 zs1f32EMX$GlJrz|6PZaL4!h(D{>M4qhBq0G(qG&}b>Sb8FB!(#zC_P%cR%7hD6cc! zC6N;Iyj^gfkHOT*qC0aY>o7yZDgv$T~asFdeyFDbOMelyW_wpchYR^@fmk+MjtRF z*&90kTI1G(oP$+?wCM>SU8<~ufc%}kvzNiJXnSOpbxE@G$>=MU1ibO}Pi&p9k`oVk zwmlZ*u*(7PZVhFSH2|W~?Y>OHSRk)6xj19+ls#%on0T;5*n>xJt9dm|%)hCpzdoOC zpJ=J|9^W##$bD)NsHm5C=J*a|2zF;`ZOj_~Vk2}hGnvS%%gy5a_O#=}`w1_@s&SBu z$44+V?@$}kK2<1&%`DD>S37d!PDoMSePkwCo0s>K)p{-x4)sMeMpv!zFOp1QWaOE6^(6uOo zwCAG$5C6L+6a>Hc_fwwy-z)Vq|E|<~{HE03`(?^|_*1FReM_a6_;boj)tiw0G35;t z#yJ zpF<5omXgDGC5Dp2c{Hk$BQR{LV9xehZCFYXj4CU`n_x#B&6n?E5rqslwCHz?ak?1K zGyO*l`}31N*p$b#oGeaFM=K>weX|eMO>Ug+ zJK8dTcDA#6N&J_`L}*BoW$8e=B{3Q8Yfd?@u4LtRw`BoP;^&mN=^z!@4P|DLYM5}? zl4zNGSR>xC$3#1@l%lPF zbGFmdjn+M=y@VYTArg%l2m-NXEMWJ+WBIqS>^N(KlF6}hFle>+2*ndJ5P}_Jh$!q8bK8W{jBy|6-s!{ zARS2>YgZD=KEha(D7tB@99F_pU4Z(bbmJa{0-C)_0VSG4RYsunwN&$A-65}a)1ihJ z_K3GC8YY-)JXe+VsqR6VVISmM5Xr zPLdy=Pi;$luElqeSYigwb*xv^{OJ*MbY-vBaNKz=7@mE9TF_CeX8=o?v*99?r$(z@ zx6J-I==Vo_Aj8Xr4Zaa}qXMReFftx-W0SYVzeR-h+(u?{~J8o!&(zZA(L|wm+A7&@ftSpW3u4prPhV`LSljXN^Q;ob{)K6@EkUt zvh3V4|F?@tL5$u2(0=`acaE>~$Z~*WBKoqbD5AAk@*1iH)wfgpn5485DDoM~;X<>OT^Zrjux{%q1!Fzcz zXMSlCsORWG*ynmksPOQKKFTyK;_|2>Lz2yQ!PEv0kU-w{e8iF6$Xhd!8=)manOz`= zSbs4H>Mhj=A>E6XS1v(3(rvf!k&2m&A;Yg6BFcIY9jiYzYVX9)>*~rCXEqzXS_^o} znXAF*7LzljLKZ)9=-!su8DzbH@osF=}6xgsT z9p8lci8T#u+F;J)|2BCzV<6|F=MbM*sOp1eJlbdyTV80xn7vn00es4uL{}wLCG@=( zv&obd9gVd`E^HF^r{TjhNYB=c5JL9K5a2;G|bFXZg|;O3I&a4oimoF zqAiyocFfB%xT8ngctXG`@=6Boy`jQ97043y3d(42lIHpeG@D~sdgP{pc6lI}1SqNm z?%LtIo~4w=4wAYKQxDOkOEg;^Dn7oWlj?r-kVRud$?XK20?VoxSZytvY(wwZ+yZu4 zF_#?MBy7f&j`DPtH`*$h4yxAycWyaK@7~diMj?b$G_k0C@OvMceeZEX1d9x{fAh%s zZAiLtEZse=+hMeQq{`t6=5HKkit)XgquFZ2gFAA%OUmkd4p1hwFZ7$BUH8D#+1uK! z5QR^?;cdV@XBm7Y+y%mBu&M8w+*Kq;n90~%zpqnkPnLs)o3fjrkF8jx zN@94VE>-(3n7tRp8=3m$A-fgXxw3o!jwV49^UH3Gvn;x%k-{;bJ*r{O$*Am_QQyf@ z;>0Z{$V?Q7F?lO(K?KDOsiX~+Zs+_P7O5|5_K%B-~5#M3f`ybjD zLglGx9WP2L*qjP*pvYBN>``l~#l7@_Ay-slcQ{U%xng2*E9yJ1;E*Js~;k>#BlW*(ro{MFs}; z=fqO4EzIXMyo@0H(cBba12LkC)SIQAxneGW7Jqp*M~$a~Le?lSYTCidd6^)6jS0$0 zdsZeddRbV-W!;<5aiTX7IcbSft&9mGx_ZnPnH~eybz(ECDq?mREt!dJWLC|t98r{B zggD$|m^kvD^dBLl1L@ZjYBT5)l=#G@ETM$qWtj-2P1|g6>$cjC!TcKm&mhUsyWw)u z9^BgPeJ_0azJ?MDZt;@$Tl;LqBmtFosy=IB6_mKY9G)|SUerDnsl`RY7Z(qG6`QX8 z#x+w6lf1tIm8T0XG4bOATHp}QM8x?MW#|J%K^1xOl+aZ|s%}O-*_Sl=7EYyNBBmN` zs9kmJa{iu%i`By8O&l>c^&a`Ln)_!nZdJ!B)aqF*ra}YkblzAx7XytoLid7sk5BZC zzPgn0%#Yh0Yqc7SUQu33)gVV(_>DLm0KC^l(yIh**1__=-g%wB%y z?Dbav-Vxk=cJw)gHrlzv+J?yG@zV1Kwy-{ntXRXH+7|0s6P5=IR@=jb2|-rE%syQx zTjoK3g(~5dCE9rdIyw_QC{$WfP-L6&f{Jf=-hR2R=AQw&mb4W0@29}G2-#r>M$vuKS zTM*0@O17qshog;D91`P(DjpHEybzo(&0nR5`}DRU9zQ6N&ydcCfGHa!{w5R(4x{6; z#5T9gvLZ)o$7xA7d@d740T<4dq92S%a4@JSM{LxE7>WWHniYwsV23Bwijz@^s}YS$ zU8V5KTR}NJg1`-7izt%1gte{6(5+J1+e|jr1|N<{>rJIJ5x-2RIG&NVHasLav>(W& zhnKmF-3XFZKBe?ur>fMT2~d}Ps1cFx8L_)SD+)EoG^wI6eIA@5FE5-0=8&f>wsvYO zi^j7e6>6tg2^Rk7djE0r{Wg3-S^d$*nMxQa@qYc%2d*ZkaF`kTl$b+tE>TMMQQ(7~kc++edLK8uSFuBoxcudW ztzyy&6rAGsWI3mDpL*j3;85J`2rFn3vUKF%RSe~yg_NmB&T8T+J>LmYNMpKe)}sUp^!q- zoLOo_bLLVT`q}$*%ENR|->i6vOdlpf)Q+scGdr(pIadXR5CxGdF3jF#LRh*{P!3PX z`)vOMj`$c#S=5|NU(kJ2rQ0naC2G377{dcQE##EUlD(Ylh>#)yP*#ymMNH6>dfM_0 z9HNX&#T5`yJ9?Wddfgc|N243ZejX%3Yp2hj$U3*Hns0JG4*@O@9!If<3DmZs1?0+u z*1MJaq8H|PBBkUY<7f-%`Y1~~&a&%+c5Z2t?@_;B2MOceS+Fipp>wO9kHkNQStqGs z|6ew68ZTTej?X}9R@lFf5Qwl5;`|?UfRv(VvDZ0ae}szMIQA&lij533>0sEYKfEDw zotJB>Hi8+)?$j9P9Ql)_@;|OFW3RkK^9^=PbuvXNuk#XW-bGNzhl98z``?35g)Z?R zC1EHbc9@B$au)ON8yy#};>c3h-(`t3)ux}fT|;qy(H; z_mc2{=0j3Ugp&BwG5R)VxiM(NoH$+xz6PLd4nkTfTnrcuK4XhZn;;2=iyD+pN#0D3%);W)cmNM^3Msi&1%&C5;gosMeAxKbvXRLR)`wm?}

    dR_zaOM+vT$0o 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Î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 )Z<+d2 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

    A7;ffp}&zL!CA2|MYJ^c<1t9~fl4$%K&72A21X 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 pj%@b77=M> literal 0 HcmV?d00001 diff --git a/docs/user/dashboard/images/dashboard_timeSliderControl_advanceBackward_8.5.0.png b/docs/user/dashboard/images/dashboard_timeSliderControl_advanceBackward_8.5.0.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c2e9921f687c1e5e29f950840787d05bc19696 GIT binary patch literal 1522 zcmeAS@N?(olHy`uVBq!ia0vp^d_XL~!3HGNrubPgFfhhvIy(n=Iy)-_6y>L7=A<$( zXiO}fVCdl#DAHzsn#1{oqSBIs3l&c&#RcXjDkYk9ghkx`oGP^Zz;pKrJ`i1k)OhB?HLzeYR1`rP&$zlH7p zT;V^LcCGik?3=fDPu{8VxuSjbG*h9;G08L*c|#``&eQsx^}pQge>JUHsFAeEtILG- zC11zl6B9)ECLd1V*uO#TvZ=}Md;8zt_;8IcVG-NfrESx1v|i4Au`|1(_V)$N?N*lI zyH?i6Uk@yP7VfsiYPb5rxA!*9`@L{4OTOyqD$VF?(UH%>7Y5a^3&^IP-6YIw0SxhJ zsS%!OzP=1vKsE;hE29ttGmymygba*Q46I-_1A`Z%G@Kp8r~y^O#K6#=$-n|t69uF} zzypYZx}h}lq&5b2AWsO0TNW_ERr4)ihO-5Latw~a&Yqq?Iyku~H8rm|BfsQ-_2zv* z0nP%C$YKTtZeb8+WSBKaf`Ng_Ix{4qB*NFnDmgz_FEJ%QDOIl`w*X`ggH44MkeQoW zlBiITo0C^;Rbi_HR$&EXgM{^!6u?SKvTc4x_5`cX3~Hd$?2OH9z_wsWAlwPj7+RcKhTlY&1l+5lI3`Bgg|*{zaKU%Mw8$Xb04TAqG`%qYnyRq~M0c zC0G;~nRZ+@`tXEd#}%W#DiN4W13X>$uqf80N32UD;2ssKf;q6rsV zIP?xYX1UYQcARn2l_zX%w_KPeG4EJl$db5T_x9;?O44F63jHaSWo7#+*NN@jQ&=(~ zj@?aZuH#}|<>xY?p^leLeqP*pJ-Apy!E#!vW^&~`_5YF64x8{ksa*FY;E;$b<7K0V zyY!xYX7@Rkye4e@4sIsN2~5{7HH7p%w(yuNa``38@56$9e0{7x`fk1nYj6!VJ1DfI zPUp0fh^vLQ<Hm&5}sw z+4uhOt7lTv#h-7;mHvK^X-@bL0Y=8Jb2IGY`KIPbv^B`s*xM@cIKSa#i_jB)@qPPK z*|SY9j3*roIC;CYP9Bl+5q$IAYKpB;fWDP)@J;2f=Z?(Evi>>Y599tb4eMTSy=Ao| gt-trW{nT?0)NLJ_YP){UnGDK^p00i_>zopr0F~Mp00000 literal 0 HcmV?d00001 diff --git a/docs/user/dashboard/images/dashboard_timeSliderControl_advanceForward_8.5.0.png b/docs/user/dashboard/images/dashboard_timeSliderControl_advanceForward_8.5.0.png new file mode 100644 index 0000000000000000000000000000000000000000..788621037cb073595c8324e9e8ede3f7b7f580a8 GIT binary patch literal 1531 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDwFfhhvIy(n=Iy)-_6y>L7=A<$( zXiO}fVCdl#DAHzsn#1{oqSBIs3l&c&#RcXjDkYk9ghkx`oGP^Zz;pKrJ`i1k)OhB?HLzeYR1`rP&$zlH7p zT;V^LcCGik?3=fDPu{8VxuSjbG*h9;G08L*c|#``&eQsx^}pQge>JUHsFAeEtILG- zC11zl6B9)ECLd1V*uO#TvZ=}Md;8zt_;8IcVG-NfrESx1v|i4Au`|1(_V)$N?N*lI zyH?i6Uk@yP7VfsiYPb5rxA!*9`@L{4OTOyqD$VF?(UH%>7Y5a^3&^IP-6YIw0SxhJ zsS%!OzP=1vKsE;hE29ttGmymygba*Q46I-_1A`Z%G@Kp8r~y^O#K6#=$-n|t69uF} zzypYZx}h}lq&5b2AWsO0TNW_ERr4)ihO zv%n*=n1O*?7=#%aX3ddcU|_P&42dX-@b$4u&d=3LOvz75)vL%Y0GY#JQ(*;U=BAb; zDpcg=uf-VSp{TPq~=7rWaa{c8t61TV>27DEf^9AcS1CV7N-_D=jQ_T zmDnMo3aS}d5?v$M#t4LsMi$66B1s@=1bM*9zbF%ESt2L|?F?=7F~p$iZS+CGixk|D zxCDy=Bh!w{MjxIq?6|^?9s{Or#z;>W#}Es_vq8K44m$|Q%+J+P|H-mkQPk07$pU3w zA=8x_4PpYaLi-s`y0d8hVG@!xb>(mpdC1)%Fn8&VPq**H)r2Lf3wa&&{8pKto>u;C zjZfVhk!+o9Z0-U71Oo#_mSk$pOD*$SD>Nfa#Pxy+@2+*qo|A;k)s2ol_-*&@ckPaK z-ErHy+Yd5q%ROvzR`kdkF3tVDZXAah=07&fTerTTvaa#^LxG-^+)vglQsUdGdnGG$ z!hWxgJg`l#Kvu(5D^|4yt>rdy`*bC~!OjR#?&QwtSX<2Sr2m0SIDgVCcLg)1LR zUUe*aUiqV<-f{89&g09|=ggVcnN?IF^)hwS9rnX{tB4#IQCv>RpRtbFNFZ!JG({J%?uBuJa3!)Z+a-^rLpUXO@geCwJiWdg} literal 0 HcmV?d00001 diff --git a/docs/user/dashboard/images/dashboard_timeSliderControl_animate_8.5.0.png b/docs/user/dashboard/images/dashboard_timeSliderControl_animate_8.5.0.png new file mode 100644 index 0000000000000000000000000000000000000000..63a93323d6a36773a92907cd713e17bf9eef6621 GIT binary patch literal 1559 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDwFfhhvIy(n=Iy)-_6y>L7=A<$( zXiO}fVCdl#DAHzsn#1{oqSBIs3l&c&#RcXjDkYk9ghkx`oGP^Zz;pKrJ`i1k)OhB?HLzeYR1`rP&$zlH7p zT;V^LcCGik?3=fDPu{8VxuSjbG*h9;G08L*c|#``&eQsx^}pQge>JUHsFAeEtILG- zC11zl6B9)ECLd1V*uO#TvZ=}Md;8zt_;8IcVG-NfrESx1v|i4Au`|1(_V)$N?N*lI zyH?i6Uk@yP7VfsiYPb5rxA!*9`@L{4OTOyqD$VF?(UH%>7Y5a^3&^IP-6YIw0SxhJ zsS%!OzP=1vKsE;hE29ttGmymygba*Q46I-_1A`Z%G@Kp8r~y^O#K6#=$-n|t69uF} zzypYZx}h}lq&5b2AWsO0TNW_ERr4)ihO zv%n*=n1O*?7=#%aX3ddcU|_P&42dX-@b$4u&d=3LOvz75)vL%Y0GY#JQ(*;U=BAb; zDpcg=uf-VSp{TPq~=7rWaa{c8t61TV>27DEf^9AcS1CV7N-_D=jQ_T zmDnMo3aS}d5?v$M#t4LsMi$66B1s@=1bM*9zbF%ESt2L|?F?=7F~p$iZS+CGixk|D zxCDy=Bh!w{MjxIq?6|^?9s{Or#!^og#}Es_wUbVJ9d;0D`=6QQvsbk1i&jU;QiUe% z*qt94kL=Y~Sm#pkK91!e`v*Z5c?SV;yT+!wC6|MA95sCNJzv#dO3{g0Bf{^K_Uz2h zf5p#yMEvHl%{U|!Y@qn5eu1OyYyQ+1Jz8Az9?yA{cC^Fxap;*Fy0TtHCd_>Et*;-> zs$x5Pkb$pO$@rG*+>IxA-tzN@|BjrZzpbz`z-8&h_RG8h4?z_X&(yqHQt@myXvz0K^2+cIi?aF)4zVHx#s$L4@33r(>tthANuGgb#+?5&!gA( zq<9uT6+WqD$}?-bugIl0#r6!7vo4?IJjxOIEWrC?+x*1KE3fx9;km{hMo@ymZ6b3GoJjk7HF5dsbbn550FmlyRql^wfBh3s1x5J>LDY zXVJ4FosB`=sZ!HAtyqs4ENo+YEEH8zzT@S|!+(>cdXj7OQhYYeKX2ICac()AJ!5ra Vj$){)-33r?^>p=fS?83{1ORqBF8%-j literal 0 HcmV?d00001 diff --git a/docs/user/dashboard/make-dashboards-interactive.asciidoc b/docs/user/dashboard/make-dashboards-interactive.asciidoc index 7c80fa8588538..127c0a4a79e05 100644 --- a/docs/user/dashboard/make-dashboards-interactive.asciidoc +++ b/docs/user/dashboard/make-dashboards-interactive.asciidoc @@ -28,7 +28,7 @@ data-type="inline" *Controls* are interactive panels you add to your dashboards to filter and display only the data you want to explore. -There are two types of controls: +There are three types of controls: * *Options list* — Adds a dropdown that allows you to filter the data with one or more options that you select. + @@ -44,11 +44,17 @@ For example, if you are using the *[Logs] Web Traffic* dashboard from the sample [role="screenshot"] image::images/dashboard_controlsRangeSlider_8.3.0.png[Range slider control for the `hour_of_day` field with a range of `9` to `17` selected] +* *Time slider* — Adds a time range slider that allows you to filter the data within a specified range of time, advance the time range backward and forward, and animate your change in data over the specified time range. ++ +For example, you are using the *[Logs] Web Traffic* dashboard from the sample web logs data, and the global time filter is *Last 7 days*. When you add the time slider, you can click the previous and next buttons to advance the time range backward or forward, and click the play button to watch how the data changes over the last 7 days. +[role="screenshot"] +image::images/dashboard_timeSliderControl_8.5.0.gif[Time slider control for the the Last 7 days] + [float] -[[create-and-add-controls]] -==== Create and add controls +[[create-and-add-options-list-and-range-slider-controls]] +==== Create and add Options list and Range slider controls -To add interactive filters, create controls, then add them to your dashboard. +To add interactive Options list and Range slider controls, create the controls, then add them to your dashboard. . Open or create a new dashboard. @@ -79,8 +85,22 @@ The *Control type* is automatically applied for the field you selected. . Click *Save and close*. [float] -[[filter-the-data-with-options-lists]] -==== Filter the data with Options lists +[[add-time-slider-controls]] +==== Add time slider controls + +To add interactive time slider controls, create the control, then add it to your dashboard. + +. Open or create a new dashboard. + +. In the dashboard toolbar, click *Controls*, then select *Add time slider control*. + +. The time slider control uses the time range from the global time filter. To change the time range in the time slider control, <>. + +. Save the dashboard. + +[float] +[[filter-the-data-with-options-list-controls]] +==== Filter the data with Options list controls Filter the data with one or more options that you select. . Open the Options list dropdown. @@ -94,8 +114,8 @@ The dashboard displays only the data for the options you selected. . To display only the options you selected in the dropdown, click image:images/dashboard_showOnlySelectedOptions_8.3.0.png[The icon to display only the options you have selected in the Options list]. [float] -[[filter-the-data-with-range-sliders]] -==== Filter the data with Range sliders +[[filter-the-data-with-range-slider-controls]] +==== Filter the data with Range slider controls Filter the data within a specified range of values. . On the Range slider, click a value. @@ -106,6 +126,21 @@ The dashboard displays only the data for the range of values you specified. . To clear the specified values, click image:images/dashboard_controlsClearSelections_8.3.0.png[The icon to clear all specified values in the Range slider]. +[float] +[[filter-the-data-with-time-slider-controls]] +==== Filter the data with time slider controls +Filter the data within a specified range of time. + +. To view a different time range, click the time slider, then move the sliders to specify the time range you want to display. + +. To advance the time range forward, click image:images/dashboard_timeSliderControl_advanceForward_8.5.0.png[The icon to advance the time range forward]. + +. To advance the time range backward, click image:images/dashboard_timeSliderControl_advanceBackward_8.5.0.png[The icon to advance the time range backward]. + +. To animate the data changes over time, click image:images/dashboard_timeSliderControl_animate_8.5.0.png[The icon to clear all specified values in the Range slider]. + +. To clear the specified values, click image:images/dashboard_controlsClearSelections_8.3.0.png[The icon to clear all specified values in the Range slider]. + [float] [[configure-controls-settings]] ==== Configure the controls settings @@ -126,9 +161,9 @@ The dashboard displays only the data for the range of values you specified. [float] [[edit-controls]] -==== Edit controls +==== Edit Options list and Range slider control settings -Change the settings for a control. +Change the settings for the Options list and Range slider controls. . Hover over the control you want to edit, then click image:images/dashboard_controlsEditControl_8.3.0.png[The Edit control icon that opens the Edit control flyout]. From ccbac37155d6544a9f579e47052deb6a4eb69786 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Wed, 28 Sep 2022 22:20:26 +0200 Subject: [PATCH 144/172] [Osquery] Fix small issues (#141083) --- .../osquery/cypress/e2e/all/alerts.cy.ts | 69 +-- .../all/cases.spec.ts => e2e/all/cases.cy.ts} | 4 +- x-pack/plugins/osquery/kibana.json | 1 + .../osquery/public/cases/add_to_cases.tsx | 27 + .../public/cases/add_to_cases_button.tsx | 4 +- .../public/discover/pack_view_in_discover.tsx | 49 ++ .../discover/view_results_in_discover.tsx | 141 +++++ .../public/form/results_type_field.tsx | 2 +- .../osquery/public/lens/pack_view_in_lens.tsx | 49 ++ .../public/lens/view_results_in_lens.tsx | 234 ++++++++ .../public/live_queries/form/index.tsx | 4 +- .../form/pack_queries_status_table.tsx | 521 ++---------------- .../live_queries/form/pack_results_header.tsx | 1 + .../osquery/public/packs/form/index.tsx | 2 +- .../public/packs/queries/query_flyout.tsx | 2 +- .../routes/live_queries/details/index.tsx | 29 +- .../public/routes/saved_queries/edit/tabs.tsx | 25 +- .../form/use_saved_query_form.tsx | 5 - .../osquery_action/index.tsx | 33 +- .../osquery_results/osquery_result.test.tsx | 32 +- .../osquery_results/osquery_result.tsx | 21 +- .../osquery_results/osquery_results.test.tsx | 32 +- .../osquery_results/test_utils.tsx | 38 ++ .../public/shared_components/prompts.tsx | 29 + .../server/routes/pack/create_pack_route.ts | 5 +- .../server/routes/pack/update_pack_route.ts | 5 +- .../osquery/server/routes/pack/utils.test.ts | 34 +- .../osquery/server/routes/pack/utils.ts | 21 +- .../saved_query/create_saved_query_route.ts | 4 +- .../markdown_editor/plugins/osquery/index.tsx | 26 +- .../plugins/osquery/not_available_prompt.tsx | 38 ++ .../markdown_editor/plugins/osquery/utils.ts | 31 -- .../test/osquery_cypress/artifact_manager.ts | 2 +- 33 files changed, 843 insertions(+), 677 deletions(-) rename x-pack/plugins/osquery/cypress/{integration/all/cases.spec.ts => e2e/all/cases.cy.ts} (96%) create mode 100644 x-pack/plugins/osquery/public/cases/add_to_cases.tsx create mode 100644 x-pack/plugins/osquery/public/discover/pack_view_in_discover.tsx create mode 100644 x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx create mode 100644 x-pack/plugins/osquery/public/lens/pack_view_in_lens.tsx create mode 100644 x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx create mode 100644 x-pack/plugins/osquery/public/shared_components/prompts.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/not_available_prompt.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/utils.ts diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts index e5c6b5013b138..34d04289ad676 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts @@ -8,7 +8,6 @@ import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { login } from '../../tasks/login'; import { - checkResults, findAndClickButton, findFormFieldByRowsLabelAndType, inputQuery, @@ -59,6 +58,28 @@ describe('Alert Event Details', () => { cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); }); + it('enables to add detection action with osquery', () => { + cy.visit('/app/security/rules'); + cy.contains(RULE_NAME).click(); + cy.contains('Edit rule settings').click(); + cy.getBySel('edit-rule-actions-tab').wait(500).click(); + cy.contains('Perform no actions').get('select').select('On each rule execution'); + cy.contains('Response actions are run on each rule execution'); + cy.getBySel('.osquery-ResponseActionTypeSelectOption').click(); + cy.get(LIVE_QUERY_EDITOR); + cy.contains('Save changes').click(); + cy.contains('Query is a required field'); + inputQuery('select * from uptime'); + cy.wait(1000); // wait for the validation to trigger - cypress is way faster than users ;) + + // getSavedQueriesDropdown().type(`users{downArrow}{enter}`); + cy.contains('Save changes').click(); + cy.contains(`${RULE_NAME} was saved`).should('exist'); + cy.contains('Edit rule settings').click(); + cy.getBySel('edit-rule-actions-tab').wait(500).click(); + cy.contains('select * from uptime'); + }); + it('should be able to run live query and add to timeline (-depending on the previous test)', () => { const TIMELINE_NAME = 'Untitled timeline'; cy.visit('/app/security/alerts'); @@ -94,38 +115,20 @@ describe('Alert Event Details', () => { cy.contains('Cancel').click(); cy.contains(TIMELINE_NAME).click(); cy.getBySel('draggableWrapperKeyboardHandler').contains('action_id: "'); - }); - it('enables to add detection action with osquery', () => { - cy.visit('/app/security/rules'); - cy.contains(RULE_NAME).click(); - cy.contains('Edit rule settings').click(); - cy.getBySel('edit-rule-actions-tab').wait(500).click(); - cy.contains('Perform no actions').get('select').select('On each rule execution'); - cy.contains('Response actions are run on each rule execution'); - cy.getBySel('.osquery-ResponseActionTypeSelectOption').click(); - cy.get(LIVE_QUERY_EDITOR); - cy.contains('Save changes').click(); - cy.contains('Query is a required field'); - inputQuery('select * from uptime'); - cy.wait(1000); // wait for the validation to trigger - cypress is way faster than users ;) - - // getSavedQueriesDropdown().type(`users{downArrow}{enter}`); - cy.contains('Save changes').click(); - cy.contains(`${RULE_NAME} was saved`).should('exist'); - cy.contains('Edit rule settings').click(); - cy.getBySel('edit-rule-actions-tab').wait(500).click(); - cy.contains('select * from uptime'); + // timeline unsaved changes modal + cy.visit('/app/osquery'); + closeModalIfVisible(); }); // TODO think on how to get these actions triggered faster (because now they are not triggered during the test). - it.skip('sees osquery results from last action', () => { - cy.visit('/app/security/alerts'); - cy.getBySel('header-page-title').contains('Alerts').should('exist'); - cy.getBySel('expand-event').first().click({ force: true }); - cy.contains('Osquery Results').click(); - cy.getBySel('osquery-results').should('exist'); - cy.contains('select * from uptime'); - cy.getBySel('osqueryResultsTable').within(() => { - checkResults(); - }); - }); + // it.skip('sees osquery results from last action', () => { + // cy.visit('/app/security/alerts'); + // cy.getBySel('header-page-title').contains('Alerts').should('exist'); + // cy.getBySel('expand-event').first().click({ force: true }); + // cy.contains('Osquery Results').click(); + // cy.getBySel('osquery-results').should('exist'); + // cy.contains('select * from uptime'); + // cy.getBySel('osqueryResultsTable').within(() => { + // checkResults(); + // }); + // }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/all/cases.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts similarity index 96% rename from x-pack/plugins/osquery/cypress/integration/all/cases.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts index cb16dd1dfb8fe..ce586e4d49943 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/cases.spec.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts @@ -35,7 +35,7 @@ describe('Add to Cases', () => { cy.contains('Test Obs case').click(); checkResults(); cy.contains('attached Osquery results'); - cy.contains('SELECT * FROM users;'); + cy.contains('select * from uptime;'); cy.contains('View in Discover').should('exist'); cy.contains('View in Lens').should('exist'); cy.contains('Add to Case').should('not.exist'); @@ -66,7 +66,7 @@ describe('Add to Cases', () => { cy.contains('Test Security Case').click(); checkResults(); cy.contains('attached Osquery results'); - cy.contains('SELECT * FROM users;'); + cy.contains('select * from uptime;'); cy.contains('View in Discover').should('exist'); cy.contains('View in Lens').should('exist'); cy.contains('Add to Case').should('not.exist'); diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index 6d956fdce9fe9..63e7718368ce1 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -12,6 +12,7 @@ "requiredPlugins": [ "actions", "data", + "licensing", "dataViews", "discover", "features", diff --git a/x-pack/plugins/osquery/public/cases/add_to_cases.tsx b/x-pack/plugins/osquery/public/cases/add_to_cases.tsx new file mode 100644 index 0000000000000..bc46a76edf361 --- /dev/null +++ b/x-pack/plugins/osquery/public/cases/add_to_cases.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 from 'react'; +import { useKibana } from '../common/lib/kibana'; +import type { AddToCaseButtonProps } from './add_to_cases_button'; +import { AddToCaseButton } from './add_to_cases_button'; + +const CASES_OWNER: string[] = []; + +export const AddToCaseWrapper: React.FC = React.memo((props) => { + const { cases } = useKibana().services; + const casePermissions = cases.helpers.canUseCases(); + const CasesContext = cases.ui.getCasesContext(); + + return ( + + {' '} + + ); +}); + +AddToCaseWrapper.displayName = 'AddToCaseWrapper'; diff --git a/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx index 9f2df87f35615..7124ebd955490 100644 --- a/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx +++ b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx @@ -19,7 +19,7 @@ const ADD_TO_CASE = i18n.translate( } ); -interface IProps { +export interface AddToCaseButtonProps { queryId: string; agentIds?: string[]; actionId: string; @@ -28,7 +28,7 @@ interface IProps { iconProps?: Record; } -export const AddToCaseButton: React.FC = ({ +export const AddToCaseButton: React.FC = ({ actionId, agentIds = [], queryId = '', diff --git a/x-pack/plugins/osquery/public/discover/pack_view_in_discover.tsx b/x-pack/plugins/osquery/public/discover/pack_view_in_discover.tsx new file mode 100644 index 0000000000000..c6506126c83fe --- /dev/null +++ b/x-pack/plugins/osquery/public/discover/pack_view_in_discover.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import moment from 'moment-timezone'; +import { usePackQueryLastResults } from '../packs/use_pack_query_last_results'; +import { ViewResultsActionButtonType } from '../live_queries/form/pack_queries_status_table'; +import { ViewResultsInDiscoverAction } from './view_results_in_discover'; + +interface PackViewInActionProps { + item: { + id: string; + interval: number; + action_id?: string; + agents: string[]; + }; + actionId?: string; +} +const PackViewInDiscoverActionComponent: React.FC = ({ item }) => { + const { action_id: actionId, agents: agentIds, interval } = item; + const { data: lastResultsData } = usePackQueryLastResults({ + actionId, + interval, + }); + + const startDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() + : `now-${interval}s`; + const endDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).toISOString() + : 'now'; + + return ( + + ); +}; + +export const PackViewInDiscoverAction = React.memo(PackViewInDiscoverActionComponent); diff --git a/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx b/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx new file mode 100644 index 0000000000000..2106f11b89220 --- /dev/null +++ b/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx @@ -0,0 +1,141 @@ +/* + * 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, useState } from 'react'; +import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FilterStateStore } from '@kbn/es-query'; +import { useKibana } from '../common/lib/kibana'; +import { useLogsDataView } from '../common/hooks/use_logs_data_view'; +import { ViewResultsActionButtonType } from '../live_queries/form/pack_queries_status_table'; + +interface ViewResultsInDiscoverActionProps { + actionId?: string; + agentIds?: string[]; + buttonType: ViewResultsActionButtonType; + endDate?: string; + startDate?: string; + mode?: string; +} + +const ViewResultsInDiscoverActionComponent: React.FC = ({ + actionId, + agentIds, + buttonType, + endDate, + startDate, +}) => { + const { discover, application } = useKibana().services; + const locator = discover?.locator; + const discoverPermissions = application.capabilities.discover; + const { data: logsDataView } = useLogsDataView({ skip: !actionId }); + + const [discoverUrl, setDiscoverUrl] = useState(''); + + useEffect(() => { + const getDiscoverUrl = async () => { + if (!locator || !logsDataView) return; + + const agentIdsQuery = agentIds?.length + ? { + bool: { + minimum_should_match: 1, + should: agentIds.map((agentId) => ({ match_phrase: { 'agent.id': agentId } })), + }, + } + : null; + + const newUrl = await locator.getUrl({ + indexPatternId: logsDataView.id, + filters: [ + { + meta: { + index: logsDataView.id, + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'action_id', + params: { query: actionId }, + }, + query: { match_phrase: { action_id: actionId } }, + $state: { store: FilterStateStore.APP_STATE }, + }, + ...(agentIdsQuery + ? [ + { + $state: { store: FilterStateStore.APP_STATE }, + meta: { + alias: 'agent IDs', + disabled: false, + index: logsDataView.id, + key: 'query', + negate: false, + type: 'custom', + value: JSON.stringify(agentIdsQuery), + }, + query: agentIdsQuery, + }, + ] + : []), + ], + refreshInterval: { + pause: true, + value: 0, + }, + timeRange: + startDate && endDate + ? { + to: endDate, + from: startDate, + mode: 'absolute', + } + : { + to: 'now', + from: 'now-1d', + mode: 'relative', + }, + }); + setDiscoverUrl(newUrl); + }; + + getDiscoverUrl(); + }, [actionId, agentIds, endDate, startDate, locator, logsDataView]); + + if (!discoverPermissions.show) { + return null; + } + + if (buttonType === ViewResultsActionButtonType.button) { + return ( + + {VIEW_IN_DISCOVER} + + ); + } + + return ( + + + + ); +}; + +const VIEW_IN_DISCOVER = i18n.translate( + 'xpack.osquery.pack.queriesTable.viewDiscoverResultsActionAriaLabel', + { + defaultMessage: 'View in Discover', + } +); + +export const ViewResultsInDiscoverAction = React.memo(ViewResultsInDiscoverActionComponent); diff --git a/x-pack/plugins/osquery/public/form/results_type_field.tsx b/x-pack/plugins/osquery/public/form/results_type_field.tsx index 55bbe69397f97..ccc1961259c38 100644 --- a/x-pack/plugins/osquery/public/form/results_type_field.tsx +++ b/x-pack/plugins/osquery/public/form/results_type_field.tsx @@ -34,7 +34,7 @@ const DIFFERENTIAL_OPTION = { inputDisplay: ( ), }; diff --git a/x-pack/plugins/osquery/public/lens/pack_view_in_lens.tsx b/x-pack/plugins/osquery/public/lens/pack_view_in_lens.tsx new file mode 100644 index 0000000000000..6e1cac3eda86f --- /dev/null +++ b/x-pack/plugins/osquery/public/lens/pack_view_in_lens.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import moment from 'moment-timezone'; +import { usePackQueryLastResults } from '../packs/use_pack_query_last_results'; +import { ViewResultsActionButtonType } from '../live_queries/form/pack_queries_status_table'; +import { ViewResultsInLensAction } from './view_results_in_lens'; + +interface PackViewInActionProps { + item: { + id: string; + interval: number; + action_id?: string; + agents: string[]; + }; + actionId?: string; +} +const PackViewInLensActionComponent: React.FC = ({ item }) => { + const { action_id: actionId, agents: agentIds, interval } = item; + const { data: lastResultsData } = usePackQueryLastResults({ + actionId, + interval, + }); + + const startDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() + : `now-${interval}s`; + const endDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).toISOString() + : 'now'; + + return ( + + ); +}; + +export const PackViewInLensAction = React.memo(PackViewInLensActionComponent); diff --git a/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx new file mode 100644 index 0000000000000..080c078f6a290 --- /dev/null +++ b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx @@ -0,0 +1,234 @@ +/* + * 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, { useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import type { + PersistedIndexPatternLayer, + PieVisualizationState, + TermsIndexPatternColumn, + TypedLensByValueInput, +} from '@kbn/lens-plugin/public'; +import { DOCUMENT_FIELD_NAME as RECORDS_FIELD } from '@kbn/lens-plugin/common/constants'; +import { FilterStateStore } from '@kbn/es-query'; +import { ViewResultsActionButtonType } from '../live_queries/form/pack_queries_status_table'; +import type { LogsDataView } from '../common/hooks/use_logs_data_view'; +import { useKibana } from '../common/lib/kibana'; +import { useLogsDataView } from '../common/hooks/use_logs_data_view'; + +interface ViewResultsInLensActionProps { + actionId?: string; + agentIds?: string[]; + buttonType: ViewResultsActionButtonType; + endDate?: string; + startDate?: string; + mode?: string; +} + +const ViewResultsInLensActionComponent: React.FC = ({ + actionId, + agentIds, + buttonType, + endDate, + startDate, + mode, +}) => { + const lensService = useKibana().services.lens; + const { data: logsDataView } = useLogsDataView({ skip: !actionId }); + + const handleClick = useCallback( + (event) => { + event.preventDefault(); + + if (logsDataView) { + lensService?.navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: startDate ?? 'now-1d', + to: endDate ?? 'now', + mode: mode ?? (startDate || endDate) ? 'absolute' : 'relative', + }, + attributes: getLensAttributes(logsDataView, actionId, agentIds), + }, + { + openInNewTab: true, + skipAppLeave: true, + } + ); + } + }, + [actionId, agentIds, endDate, lensService, logsDataView, mode, startDate] + ); + + const isDisabled = useMemo(() => !actionId || !logsDataView, [actionId, logsDataView]); + + if (buttonType === ViewResultsActionButtonType.button) { + return ( + + {VIEW_IN_LENS} + + ); + } + + return ( + + + + ); +}; + +function getLensAttributes( + logsDataView: LogsDataView, + actionId?: string, + agentIds?: string[] +): TypedLensByValueInput['attributes'] { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['8690befd-fd69-4246-af4a-dd485d2a3b38', 'ed999e9d-204c-465b-897f-fe1a125b39ed'], + columns: { + '8690befd-fd69-4246-af4a-dd485d2a3b38': { + sourceField: 'type', + isBucketed: true, + dataType: 'string', + scale: 'ordinal', + operationType: 'terms', + label: 'Top values of type', + params: { + otherBucket: true, + size: 5, + missingBucket: false, + orderBy: { + columnId: 'ed999e9d-204c-465b-897f-fe1a125b39ed', + type: 'column', + }, + orderDirection: 'desc', + }, + } as TermsIndexPatternColumn, + 'ed999e9d-204c-465b-897f-fe1a125b39ed': { + sourceField: RECORDS_FIELD, + isBucketed: false, + dataType: 'number', + scale: 'ratio', + operationType: 'count', + label: 'Count of records', + }, + }, + incompleteColumns: {}, + }; + + const xyConfig: PieVisualizationState = { + shape: 'pie', + layers: [ + { + layerType: 'data', + legendDisplay: 'default', + nestedLegend: false, + layerId: 'layer1', + metric: 'ed999e9d-204c-465b-897f-fe1a125b39ed', + numberDisplay: 'percent', + primaryGroups: ['8690befd-fd69-4246-af4a-dd485d2a3b38'], + categoryDisplay: 'default', + }, + ], + }; + + const agentIdsQuery = agentIds?.length + ? { + bool: { + minimum_should_match: 1, + should: agentIds?.map((agentId) => ({ match_phrase: { 'agent.id': agentId } })), + }, + } + : undefined; + + return { + visualizationType: 'lnsPie', + title: `Action ${actionId} results`, + references: [ + { + id: logsDataView.id, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: logsDataView.id, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + { + name: 'filter-index-pattern-0', + id: logsDataView.id, + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [ + { + $state: { store: FilterStateStore.APP_STATE }, + meta: { + index: 'filter-index-pattern-0', + negate: false, + alias: null, + disabled: false, + params: { + query: actionId, + }, + type: 'phrase', + key: 'action_id', + }, + query: { + match_phrase: { + action_id: actionId, + }, + }, + }, + ...(agentIdsQuery + ? [ + { + $state: { store: FilterStateStore.APP_STATE }, + meta: { + alias: 'agent IDs', + disabled: false, + index: 'filter-index-pattern-0', + key: 'query', + negate: false, + type: 'custom', + value: JSON.stringify(agentIdsQuery), + }, + query: agentIdsQuery, + }, + ] + : []), + ], + query: { language: 'kuery', query: '' }, + visualization: xyConfig, + }, + }; +} + +const VIEW_IN_LENS = i18n.translate( + 'xpack.osquery.pack.queriesTable.viewLensResultsActionAriaLabel', + { + defaultMessage: 'View in Lens', + } +); + +export const ViewResultsInLensAction = React.memo(ViewResultsInLensActionComponent); diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 4a6a204cec0b4..b870d1385752f 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -12,6 +12,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm as useHookForm, FormProvider } from 'react-hook-form'; import { isEmpty, find, pickBy } from 'lodash'; +import { AddToCaseWrapper } from '../../cases/add_to_cases'; import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline'; import { QueryPackSelectable } from './query_pack_selectable'; import type { SavedQuerySOFormData } from '../../saved_queries/form/use_saved_query_form'; @@ -25,7 +26,6 @@ import type { AgentSelection } from '../../agents/types'; import { LiveQueryQueryField } from './live_query_query_field'; import { AgentsTableField } from './agents_table_field'; import { savedQueryDataSerializer } from '../../saved_queries/form/use_saved_query_form'; -import { AddToCaseButton } from '../../cases/add_to_cases_button'; import { PackFieldWrapper } from '../../shared_components/osquery_response_action_type/pack_field_wrapper'; export interface LiveQueryFormFields { @@ -213,7 +213,7 @@ const LiveQueryFormComponent: React.FC = ({ (payload) => { if (liveQueryActionId) { return ( - ({ match_phrase: { 'agent.id': agentId } })), - }, - } - : undefined; - - return { - visualizationType: 'lnsPie', - title: `Action ${actionId} results`, - references: [ - { - id: logsDataView.id, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: logsDataView.id, - name: 'indexpattern-datasource-layer-layer1', - type: 'index-pattern', - }, - { - name: 'filter-index-pattern-0', - id: logsDataView.id, - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: dataLayer, - }, - }, - }, - filters: [ - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - index: 'filter-index-pattern-0', - negate: false, - alias: null, - disabled: false, - params: { - query: actionId, - }, - type: 'phrase', - key: 'action_id', - }, - query: { - match_phrase: { - action_id: actionId, - }, - }, - }, - ...(agentIdsQuery - ? [ - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - alias: 'agent IDs', - disabled: false, - index: 'filter-index-pattern-0', - key: 'query', - negate: false, - type: 'custom', - value: JSON.stringify(agentIdsQuery), - }, - query: agentIdsQuery, - }, - ] - : []), - ], - query: { language: 'kuery', query: '' }, - visualization: xyConfig, - }, - }; -} - -const ViewResultsInLensActionComponent: React.FC = ({ - actionId, - agentIds, - buttonType, - endDate, - startDate, - mode, -}) => { - const lensService = useKibana().services.lens; - const isLensAvailable = lensService?.canUseEditor(); - const { data: logsDataView } = useLogsDataView({ skip: !actionId }); - - const handleClick = useCallback( - (event) => { - event.preventDefault(); - - if (logsDataView) { - lensService?.navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: startDate ?? 'now-1d', - to: endDate ?? 'now', - mode: mode ?? (startDate || endDate) ? 'absolute' : 'relative', - }, - attributes: getLensAttributes(logsDataView, actionId, agentIds), - }, - { - openInNewTab: true, - skipAppLeave: true, - } - ); - } - }, - [actionId, agentIds, endDate, lensService, logsDataView, mode, startDate] - ); - - const isDisabled = useMemo(() => !actionId || !logsDataView, [actionId, logsDataView]); - - if (!isLensAvailable) { - return null; - } - - if (buttonType === ViewResultsActionButtonType.button) { - return ( - - {VIEW_IN_LENS} - - ); - } - - return ( - - - - ); -}; - -export const ViewResultsInLensAction = React.memo(ViewResultsInLensActionComponent); - -const ViewResultsInDiscoverActionComponent: React.FC = ({ - actionId, - agentIds, - buttonType, - endDate, - startDate, -}) => { - const { discover, application } = useKibana().services; - const locator = discover?.locator; - const discoverPermissions = application.capabilities.discover; - const { data: logsDataView } = useLogsDataView({ skip: !actionId }); - - const [discoverUrl, setDiscoverUrl] = useState(''); - - useEffect(() => { - const getDiscoverUrl = async () => { - if (!locator || !logsDataView) return; - - const agentIdsQuery = agentIds?.length - ? { - bool: { - minimum_should_match: 1, - should: agentIds.map((agentId) => ({ match_phrase: { 'agent.id': agentId } })), - }, - } - : null; - - const newUrl = await locator.getUrl({ - indexPatternId: logsDataView.id, - filters: [ - { - meta: { - index: logsDataView.id, - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'action_id', - params: { query: actionId }, - }, - query: { match_phrase: { action_id: actionId } }, - $state: { store: FilterStateStore.APP_STATE }, - }, - ...(agentIdsQuery - ? [ - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - alias: 'agent IDs', - disabled: false, - index: logsDataView.id, - key: 'query', - negate: false, - type: 'custom', - value: JSON.stringify(agentIdsQuery), - }, - query: agentIdsQuery, - }, - ] - : []), - ], - refreshInterval: { - pause: true, - value: 0, - }, - timeRange: - startDate && endDate - ? { - to: endDate, - from: startDate, - mode: 'absolute', - } - : { - to: 'now', - from: 'now-1d', - mode: 'relative', - }, - }); - setDiscoverUrl(newUrl); - }; - - getDiscoverUrl(); - }, [actionId, agentIds, endDate, startDate, locator, logsDataView]); - - if (!discoverPermissions.show) { - return null; - } - - if (buttonType === ViewResultsActionButtonType.button) { - return ( - - {VIEW_IN_DISCOVER} - - ); - } - - return ( - - - - ); -}; - -export const ViewResultsInDiscoverAction = React.memo(ViewResultsInDiscoverActionComponent); - interface DocsColumnResultsProps { count?: number; isLive?: boolean; @@ -450,72 +99,6 @@ const AgentsColumnResults: React.FC = ({ ); -interface PackViewInActionProps { - item: { - id: string; - interval: number; - action_id?: string; - agents: string[]; - }; - actionId?: string; -} - -const PackViewInDiscoverActionComponent: React.FC = ({ item }) => { - const { action_id: actionId, agents: agentIds, interval } = item; - const { data: lastResultsData } = usePackQueryLastResults({ - actionId, - interval, - }); - - const startDate = lastResultsData?.['@timestamp'] - ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() - : `now-${interval}s`; - const endDate = lastResultsData?.['@timestamp'] - ? moment(lastResultsData?.['@timestamp'][0]).toISOString() - : 'now'; - - return ( - - ); -}; - -const PackViewInDiscoverAction = React.memo(PackViewInDiscoverActionComponent); - -const PackViewInLensActionComponent: React.FC = ({ item }) => { - const { action_id: actionId, agents: agentIds, interval } = item; - const { data: lastResultsData } = usePackQueryLastResults({ - actionId, - interval, - }); - - const startDate = lastResultsData?.['@timestamp'] - ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() - : `now-${interval}s`; - const endDate = lastResultsData?.['@timestamp'] - ? moment(lastResultsData?.['@timestamp'][0]).toISOString() - : 'now'; - - return ( - - ); -}; - -const PackViewInLensAction = React.memo(PackViewInLensActionComponent); - type PackQueryStatusItem = Partial<{ action_id: string; id: string; @@ -542,10 +125,12 @@ interface PackQueriesStatusTableProps { actionId, isIcon, isDisabled, + queryId, }: { actionId?: string; isIcon?: boolean; isDisabled?: boolean; + queryId?: string; }) => ReactElement; showResultsHeader?: boolean; } @@ -562,10 +147,6 @@ const PackQueriesStatusTableComponent: React.FC = ( showResultsHeader, }) => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); - const { cases, timelines, appName } = useKibana().services; - const casePermissions = cases.helpers.canUseCases(); - const CasesContext = cases.ui.getCasesContext(); - const renderIDColumn = useCallback( (id: string) => ( @@ -619,11 +200,15 @@ const PackQueriesStatusTableComponent: React.FC = ( const renderLensResultsAction = useCallback((item) => , []); const handleAddToCase = useCallback( - (payload: { actionId: string; isIcon?: boolean }) => + (payload: { actionId?: string; isIcon?: boolean; queryId: string }) => // eslint-disable-next-line react/display-name () => { if (addToCase) { - return addToCase({ actionId: payload.actionId, isIcon: payload?.isIcon }); + return addToCase({ + actionId: payload.actionId, + isIcon: payload.isIcon, + queryId: payload.queryId, + }); } return <>; @@ -648,7 +233,7 @@ const PackQueriesStatusTableComponent: React.FC = ( agentIds={agentIds} failedAgentsCount={item?.failed ?? 0} addToTimeline={addToTimeline} - addToCase={addToCase && handleAddToCase({ actionId: item.action_id })} + addToCase={addToCase && handleAddToCase({ queryId: item.action_id, actionId })} /> @@ -658,7 +243,7 @@ const PackQueriesStatusTableComponent: React.FC = ( return itemIdToExpandedRowMapValues; }); }, - [startDate, expirationDate, agentIds, addToTimeline, addToCase, handleAddToCase] + [startDate, expirationDate, agentIds, addToTimeline, addToCase, handleAddToCase, actionId] ); const renderToggleResultsAction = useCallback( @@ -677,28 +262,37 @@ const PackQueriesStatusTableComponent: React.FC = ( const getItemId = useCallback((item: PackItem) => get(item, 'id'), []); - const columns = useMemo(() => { - const resultActions = [ - { - render: renderDiscoverResultsAction, - }, - { - render: renderLensResultsAction, - }, - { - available: () => !!addToCase, - render: (item: { action_id: string }) => - addToCase && - addToCase({ actionId: item.action_id, isIcon: true, isDisabled: !item.action_id }), - }, - { - available: () => addToTimeline && timelines && appName === SECURITY_APP_NAME, - render: (item: { action_id: string }) => - addToTimeline && addToTimeline({ query: ['action_id', item.action_id], isIcon: true }), - }, - ]; + const renderResultActions = useCallback( + (row: { action_id: string }) => { + const resultActions = [ + { + render: renderDiscoverResultsAction, + }, + { + render: renderLensResultsAction, + }, + { + render: (item: { action_id: string }) => + addToTimeline && addToTimeline({ query: ['action_id', item.action_id], isIcon: true }), + }, + { + render: (item: { action_id: string }) => + addToCase && + addToCase({ + actionId, + queryId: item.action_id, + isIcon: true, + isDisabled: !item.action_id, + }), + }, + ]; - return [ + return resultActions.map((action) => action.render(row)); + }, + [actionId, addToCase, addToTimeline, renderDiscoverResultsAction, renderLensResultsAction] + ); + const columns = useMemo( + () => [ { field: 'id', name: i18n.translate('xpack.osquery.pack.queriesTable.idColumnTitle', { @@ -734,7 +328,7 @@ const PackQueriesStatusTableComponent: React.FC = ( defaultMessage: 'View results', }), width: '90px', - actions: resultActions, + render: renderResultActions, }, { id: 'actions', @@ -747,21 +341,16 @@ const PackQueriesStatusTableComponent: React.FC = ( }, ], }, - ]; - }, [ - renderDiscoverResultsAction, - renderLensResultsAction, - renderIDColumn, - renderQueryColumn, - renderDocsColumn, - renderAgentsColumn, - renderToggleResultsAction, - addToCase, - addToTimeline, - timelines, - appName, - ]); - + ], + [ + renderIDColumn, + renderQueryColumn, + renderDocsColumn, + renderAgentsColumn, + renderResultActions, + renderToggleResultsAction, + ] + ); const sorting = useMemo( () => ({ sort: { @@ -798,7 +387,7 @@ const PackQueriesStatusTableComponent: React.FC = ( ); return ( - + <> {showResultsHeader && ( )} @@ -811,7 +400,7 @@ const PackQueriesStatusTableComponent: React.FC = ( itemIdToExpandedRowMap={itemIdToExpandedRowMap} isExpandable /> - + ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx b/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx index 854548fdb58e4..b4892a0387e8e 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx @@ -35,6 +35,7 @@ const StyledIconsList = styled(EuiFlexItem)` export const PackResultsHeader = ({ actionId, addToCase }: PackResultsHeadersProps) => ( <> + diff --git a/x-pack/plugins/osquery/public/packs/form/index.tsx b/x-pack/plugins/osquery/public/packs/form/index.tsx index 594c539c389cf..ac3892fe9748b 100644 --- a/x-pack/plugins/osquery/public/packs/form/index.tsx +++ b/x-pack/plugins/osquery/public/packs/form/index.tsx @@ -155,7 +155,7 @@ const PackFormComponent: React.FC = ({ - + diff --git a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx index d709b4f639e6d..65d829e7b7e82 100644 --- a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx @@ -79,7 +79,7 @@ const QueryFlyoutComponent: React.FC = ({ resetField('version', { defaultValue: savedQuery.version ? [savedQuery.version] : [] }); resetField('interval', { defaultValue: savedQuery.interval ? savedQuery.interval : 3600 }); resetField('snapshot', { defaultValue: savedQuery.snapshot ?? true }); - resetField('removed'); + resetField('removed', { defaultValue: savedQuery.removed }); resetField('ecs_mapping', { defaultValue: savedQuery.ecs_mapping ?? {} }); } }, diff --git a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx index a4c58074c76e1..bf35a529137f8 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx @@ -10,13 +10,18 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { AddToCaseButton } from '../../../cases/add_to_cases_button'; +import styled from 'styled-components'; +import { AddToCaseWrapper } from '../../../cases/add_to_cases'; import { useRouterNavigate } from '../../../common/lib/kibana'; import { WithHeaderLayout } from '../../../components/layouts'; import { useLiveQueryDetails } from '../../../actions/use_live_query_details'; import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; import { PackQueriesStatusTable } from '../../../live_queries/form/pack_queries_status_table'; +const StyledTableWrapper = styled(EuiFlexItem)` + padding-left: 10px; +`; + const LiveQueryDetailsPageComponent = () => { const { actionId } = useParams<{ actionId: string }>(); useBreadcrumbs('live_query_details', { liveQueryId: actionId }); @@ -55,7 +60,7 @@ const LiveQueryDetailsPageComponent = () => { }, [data?.status]); const addToCaseButton = useCallback( (payload) => ( - { return ( - + + + ); }; diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx index 49501bc24b708..f4de8b04cb472 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -10,13 +10,10 @@ import React, { useMemo } from 'react'; import type { ReactElement } from 'react'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; -import { useKibana } from '../../../common/lib/kibana'; import type { AddToTimelinePayload } from '../../../timelines/get_add_to_timeline'; import { ResultsTable } from '../../../results/results_table'; import { ActionResultsSummary } from '../../../action_results/action_results_summary'; -const CASES_OWNER: string[] = []; - interface ResultTabsProps { actionId: string; agentIds?: string[]; @@ -38,10 +35,6 @@ const ResultTabsComponent: React.FC = ({ addToTimeline, addToCase, }) => { - const { cases } = useKibana().services; - const casePermissions = cases.helpers.canUseCases(); - const CasesContext = cases.ui.getCasesContext(); - const tabs = useMemo( () => [ { @@ -85,16 +78,14 @@ const ResultTabsComponent: React.FC = ({ ); return ( - - - + ); }; diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index 3392f2af037c2..76bdd77d17b17 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -72,11 +72,6 @@ export const savedQueryDataSerializer = (payload: SavedQueryFormData): SavedQuer draft.interval = draft.interval + ''; } - if (draft.snapshot) { - delete draft.snapshot; - delete draft.removed; - } - return draft; }); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx index 6bb8d6f5a563d..3095428d478dc 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -6,17 +6,12 @@ */ import { EuiLoadingContent, EuiEmptyPrompt, EuiCode } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { OsqueryEmptyPrompt, OsqueryNotAvailablePrompt } from '../prompts'; import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline'; -import { - AGENT_STATUS_ERROR, - EMPTY_PROMPT, - NOT_AVAILABLE, - PERMISSION_DENIED, - SHORT_EMPTY_TITLE, -} from './translations'; +import { AGENT_STATUS_ERROR, PERMISSION_DENIED, SHORT_EMPTY_TITLE } from './translations'; import { useKibana } from '../../common/lib/kibana'; import { LiveQuery } from '../../live_queries'; import { OsqueryIcon } from '../../components/osquery_icon'; @@ -39,22 +34,11 @@ const OsqueryActionComponent: React.FC = ({ }) => { const permissions = useKibana().services.application.capabilities.osquery; - const emptyPrompt = useMemo( - () => ( - } - title={

    {SHORT_EMPTY_TITLE}

    } - titleSize="xs" - body={

    {EMPTY_PROMPT}

    } - /> - ), - [] - ); const { osqueryAvailable, agentFetched, isLoading, policyFetched, policyLoading, agentData } = useIsOsqueryAvailable(agentId); if (agentId && agentFetched && !agentData) { - return emptyPrompt; + return ; } if ( @@ -91,14 +75,7 @@ const OsqueryActionComponent: React.FC = ({ } if (agentId && !osqueryAvailable) { - return ( - } - title={

    {SHORT_EMPTY_TITLE}

    } - titleSize="xs" - body={

    {NOT_AVAILABLE}

    } - /> - ); + return ; } if (agentId && agentData?.status !== 'online') { diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx index e33207a12d907..772df753c8978 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx @@ -20,7 +20,7 @@ import { DETAILS_ID, DETAILS_QUERY, DETAILS_TIMESTAMP, - mockCasesContext, + getMockedKibanaConfig, } from './test_utils'; jest.mock('../../common/lib/kibana'); @@ -43,34 +43,8 @@ const defaultProps = { queryId: '', }; const mockKibana = (permissionType: unknown = defaultPermissions) => { - useKibanaMock.mockReturnValue({ - services: { - application: { - capabilities: permissionType, - }, - cases: { - helpers: { - canUseCases: jest.fn(), - }, - ui: { - getCasesContext: jest.fn().mockImplementation(() => mockCasesContext), - }, - }, - data: { - dataViews: { - getCanSaveSync: jest.fn(), - hasData: { - hasESData: jest.fn(), - hasUserDataView: jest.fn(), - hasDataView: jest.fn(), - }, - }, - }, - notifications: { - toasts: jest.fn(), - }, - }, - } as unknown as ReturnType); + const mockedKibana = getMockedKibanaConfig(permissionType); + useKibanaMock.mockReturnValue(mockedKibana); }; const renderWithContext = (Element: React.ReactElement) => diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx index 998de8a15cfca..b0d0691c14f7d 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx @@ -6,9 +6,10 @@ */ import { EuiComment, EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import { FormattedRelative } from '@kbn/i18n-react'; +import { AddToCaseWrapper } from '../../cases/add_to_cases'; import type { OsqueryActionResultsProps } from './types'; import { useLiveQueryDetails } from '../../actions/use_live_query_details'; import { ATTACHED_QUERY } from '../../agents/translations'; @@ -22,7 +23,6 @@ interface OsqueryResultProps extends Omit export const OsqueryResult = ({ actionId, - queryId, ruleName, addToTimeline, agentIds, @@ -30,10 +30,22 @@ export const OsqueryResult = ({ }: OsqueryResultProps) => { const { data } = useLiveQueryDetails({ actionId, - // isLive, - // ...(queryId ? { queryIds: [queryId] } : {}), }); + const addToCaseButton = useCallback( + (payload) => ( + + ), + [data?.agents, actionId] + ); + return (
    @@ -51,6 +63,7 @@ export const OsqueryResult = ({ expirationDate={data?.expiration} agentIds={agentIds} addToTimeline={addToTimeline} + addToCase={addToCaseButton} /> diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx index 9e2b74e000705..5190d10dc1c7e 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx @@ -17,7 +17,7 @@ import * as useAllLiveQueries from '../../actions/use_all_live_queries'; import * as useLiveQueryDetails from '../../actions/use_live_query_details'; import { PERMISSION_DENIED } from '../osquery_action/translations'; import * as privileges from '../../action_results/use_action_privileges'; -import { defaultLiveQueryDetails, DETAILS_QUERY, mockCasesContext } from './test_utils'; +import { defaultLiveQueryDetails, DETAILS_QUERY, getMockedKibanaConfig } from './test_utils'; jest.mock('../../common/lib/kibana'); @@ -49,34 +49,8 @@ const defaultPermissions = { }; const mockKibana = (permissionType: unknown = defaultPermissions) => { - useKibanaMock.mockReturnValue({ - services: { - application: { - capabilities: permissionType, - }, - cases: { - helpers: { - canUseCases: jest.fn(), - }, - ui: { - getCasesContext: jest.fn().mockImplementation(() => mockCasesContext), - }, - }, - data: { - dataViews: { - getCanSaveSync: jest.fn(), - hasData: { - hasESData: jest.fn(), - hasUserDataView: jest.fn(), - hasDataView: jest.fn(), - }, - }, - }, - notifications: { - toasts: jest.fn(), - }, - }, - } as unknown as ReturnType); + const mockedKibana = getMockedKibanaConfig(permissionType); + useKibanaMock.mockReturnValue(mockedKibana); }; const renderWithContext = (Element: React.ReactElement) => diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx index 4de58a4ed9aad..c1c991becc8ac 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import type { useKibana } from '../../common/lib/kibana'; export const DETAILS_QUERY = 'select * from uptime'; export const DETAILS_ID = 'test-id'; @@ -38,4 +39,41 @@ export const defaultLiveQueryDetails = { }, } as never; +export const getMockedKibanaConfig = (permissionType: unknown) => + ({ + services: { + application: { + capabilities: permissionType, + }, + cases: { + helpers: { + canUseCases: jest.fn().mockImplementation(() => ({ + read: true, + update: true, + push: true, + })), + }, + ui: { + getCasesContext: jest.fn().mockImplementation(() => mockCasesContext), + }, + hooks: { + getUseCasesAddToExistingCaseModal: jest.fn(), + }, + }, + data: { + dataViews: { + getCanSaveSync: jest.fn(), + hasData: { + hasESData: jest.fn(), + hasUserDataView: jest.fn(), + hasDataView: jest.fn(), + }, + }, + }, + notifications: { + toasts: jest.fn(), + }, + }, + } as unknown as ReturnType); + export const mockCasesContext: React.FC = (props) => <>{props?.children ?? null}; diff --git a/x-pack/plugins/osquery/public/shared_components/prompts.tsx b/x-pack/plugins/osquery/public/shared_components/prompts.tsx new file mode 100644 index 0000000000000..992de7c9ab14b --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/prompts.tsx @@ -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 React from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { OsqueryIcon } from '../components/osquery_icon'; +import { EMPTY_PROMPT, NOT_AVAILABLE, SHORT_EMPTY_TITLE } from './osquery_action/translations'; + +export const OsqueryEmptyPrompt = () => ( + } + title={

    {SHORT_EMPTY_TITLE}

    } + titleSize="xs" + body={

    {EMPTY_PROMPT}

    } + /> +); + +export const OsqueryNotAvailablePrompt = () => ( + } + title={

    {SHORT_EMPTY_TITLE}

    } + titleSize="xs" + body={

    {NOT_AVAILABLE}

    } + /> +); diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index c60c6d476974b..3dda3cdb36b0d 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -144,7 +144,10 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte } set(draft, `inputs[0].config.osquery.value.packs.${packSO.attributes.name}`, { - queries: convertSOQueriesToPack(queries, { removeMultiLines: true }), + queries: convertSOQueriesToPack(queries, { + removeMultiLines: true, + removeResultType: true, + }), }); return draft; diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index c113cbeb36c32..5b0d76131ee28 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -285,6 +285,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte { queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, { removeMultiLines: true, + removeResultType: true, }), } ); @@ -315,7 +316,9 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte draft, `inputs[0].config.osquery.value.packs.${updatedPackSO.attributes.name}`, { - queries: updatedPackSO.attributes.queries, + queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, { + removeResultType: true, + }), } ); diff --git a/x-pack/plugins/osquery/server/routes/pack/utils.test.ts b/x-pack/plugins/osquery/server/routes/pack/utils.test.ts index 05aff28073937..3ddb074a6edbd 100644 --- a/x-pack/plugins/osquery/server/routes/pack/utils.test.ts +++ b/x-pack/plugins/osquery/server/routes/pack/utils.test.ts @@ -42,15 +42,45 @@ describe('Pack utils', () => { describe('convertSOQueriesToPack', () => { test('converts to pack with empty ecs_mapping', () => { const convertedQueries = convertSOQueriesToPack(getTestQueries()); - expect(convertedQueries).toStrictEqual(getTestQueries({})); + expect(convertedQueries).toStrictEqual(getTestQueries()); }); test('converts to pack with converting query to single line', () => { const convertedQueries = convertSOQueriesToPack(getTestQueries(), { removeMultiLines: true }); - expect(convertedQueries).toStrictEqual(oneLiner); + expect(convertedQueries).toStrictEqual({ + ...oneLiner, + }); }); test('converts to object with pack names after query.id', () => { const convertedQueries = convertSOQueriesToPack(getTestQueries({ id: 'testId' })); expect(convertedQueries).toStrictEqual(getTestQueries({}, 'testId')); }); + test('converts with results snapshot set false', () => { + const convertedQueries = convertSOQueriesToPack( + getTestQueries({ snapshot: false, removed: true }), + { removeResultType: true } + ); + expect(convertedQueries).toStrictEqual(getTestQueries({ removed: true, snapshot: false })); + }); + test('converts with results snapshot set true and removed false', () => { + const convertedQueries = convertSOQueriesToPack( + getTestQueries({ snapshot: true, removed: true }), + { removeResultType: true } + ); + expect(convertedQueries).toStrictEqual(getTestQueries({})); + }); + test('converts with results snapshot set true but removed false', () => { + const convertedQueries = convertSOQueriesToPack( + getTestQueries({ snapshot: true, removed: false }), + { removeResultType: true } + ); + expect(convertedQueries).toStrictEqual(getTestQueries({})); + }); + test('converts with both results set to false', () => { + const convertedQueries = convertSOQueriesToPack( + getTestQueries({ snapshot: false, removed: false }), + { removeResultType: true } + ); + expect(convertedQueries).toStrictEqual(getTestQueries({ removed: false, snapshot: false })); + }); }); }); diff --git a/x-pack/plugins/osquery/server/routes/pack/utils.ts b/x-pack/plugins/osquery/server/routes/pack/utils.ts index fe1ded84f4466..4342cdb3ead8e 100644 --- a/x-pack/plugins/osquery/server/routes/pack/utils.ts +++ b/x-pack/plugins/osquery/server/routes/pack/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty, pick, reduce } from 'lodash'; +import { isEmpty, pick, reduce, isArray } from 'lodash'; import { DEFAULT_PLATFORM } from '../../../common/constants'; import { removeMultilines } from '../../../common/utils/build_query/remove_multilines'; import { convertECSMappingToArray, convertECSMappingToObject } from '../utils'; @@ -37,18 +37,29 @@ export const convertPackQueriesToSO = (queries) => }> ); -// @ts-expect-error update types -export const convertSOQueriesToPack = (queries, options?: { removeMultiLines?: boolean }) => +export const convertSOQueriesToPack = ( + // @ts-expect-error update types + queries, + options?: { removeMultiLines?: boolean; removeResultType?: boolean } +) => reduce( queries, // eslint-disable-next-line @typescript-eslint/naming-convention - (acc, { id: queryId, ecs_mapping, query, platform, ...rest }, key) => { + (acc, { id: queryId, ecs_mapping, query, platform, removed, snapshot, ...rest }, key) => { + const resultType = !snapshot ? { removed, snapshot } : {}; const index = queryId ? queryId : key; acc[index] = { ...rest, query: options?.removeMultiLines ? removeMultilines(query) : query, - ...(!isEmpty(ecs_mapping) ? { ecs_mapping: convertECSMappingToObject(ecs_mapping) } : {}), + ...(!isEmpty(ecs_mapping) + ? isArray(ecs_mapping) + ? { ecs_mapping: convertECSMappingToObject(ecs_mapping) } + : { ecs_mapping } + : {}), ...(platform === DEFAULT_PLATFORM || platform === undefined ? {} : { platform }), + ...(options?.removeResultType + ? resultType + : { ...(snapshot ? { snapshot } : {}), ...(removed ? { removed } : {}) }), }; return acc; diff --git a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts index 206719927281b..4ad6146328a1c 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty, pickBy, some } from 'lodash'; +import { isEmpty, pickBy, some, isBoolean } from 'lodash'; import type { IRouter } from '@kbn/core/server'; import { PLUGIN_ID } from '../../../common'; import type { CreateSavedQueryRequestSchemaDecoded } from '../../../common/schemas/routes/saved_query/create_saved_query_request_schema'; @@ -76,7 +76,7 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp updated_by: currentUser, updated_at: new Date().toISOString(), }, - (value) => !isEmpty(value) || value === false + (value) => !isEmpty(value) || isBoolean(value) ) ); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx index 7b96f3886159c..0d0143eab2e32 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx @@ -29,7 +29,7 @@ import { LabelField } from './label_field'; import OsqueryLogo from './osquery_icon/osquery.svg'; import { OsqueryFlyout } from '../../../../../detections/components/osquery/osquery_flyout'; import { BasicAlertDataContext } from '../../../event_details/investigation_guide_view'; -import { convertECSMappingToObject } from './utils'; +import { OsqueryNotAvailablePrompt } from './not_available_prompt'; const StyledEuiButton = styled(EuiButton)` > span > img { @@ -49,7 +49,12 @@ const OsqueryEditorComponent = ({ }; }>) => { const isEditMode = node != null; - const { osquery } = useKibana().services; + const { + osquery, + application: { + capabilities: { osquery: osqueryPermissions }, + }, + } = useKibana().services; const formMethods = useForm<{ label: string; query: string; @@ -70,7 +75,7 @@ const OsqueryEditorComponent = ({ { query: data.query, label: data.label, - ecs_mapping: convertECSMappingToObject(data.ecs_mapping), + ecs_mapping: data.ecs_mapping, }, (value) => !isEmpty(value) ) @@ -83,6 +88,17 @@ const OsqueryEditorComponent = ({ [onSave] ); + const noOsqueryPermissions = useMemo( + () => + (!osqueryPermissions.runSavedQueries || !osqueryPermissions.readSavedQueries) && + !osqueryPermissions.writeLiveQueries, + [ + osqueryPermissions.readSavedQueries, + osqueryPermissions.runSavedQueries, + osqueryPermissions.writeLiveQueries, + ] + ); + const OsqueryActionForm = useMemo(() => { if (osquery?.LiveQueryField) { const { LiveQueryField } = osquery; @@ -98,6 +114,10 @@ const OsqueryEditorComponent = ({ return null; }, [formMethods, osquery]); + if (noOsqueryPermissions) { + return ; + } + return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/not_available_prompt.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/not_available_prompt.tsx new file mode 100644 index 0000000000000..90db73811bd4f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/not_available_prompt.tsx @@ -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 { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { i18n } from '@kbn/i18n'; + +export const PERMISSION_DENIED = i18n.translate( + 'xpack.securitySolution.markdown.osquery.permissionDenied', + { + defaultMessage: 'Permission denied', + } +); + +export const OsqueryNotAvailablePrompt = () => ( + {PERMISSION_DENIED}

    } + titleSize="xs" + body={ +

    + {'osquery'}, + }} + /> +

    + } + /> +); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/utils.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/utils.ts deleted file mode 100644 index 77e2f14c51420..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty, reduce } from 'lodash'; - -export const convertECSMappingToObject = ( - ecsMapping: Array<{ - key: string; - result: { - type: string; - value: string; - }; - }> -) => - reduce( - ecsMapping, - (acc, value) => { - if (!isEmpty(value?.key) && !isEmpty(value.result?.type) && !isEmpty(value.result?.value)) { - acc[value.key] = { - [value.result.type]: value.result.value, - }; - } - - return acc; - }, - {} as Record - ); diff --git a/x-pack/test/osquery_cypress/artifact_manager.ts b/x-pack/test/osquery_cypress/artifact_manager.ts index bef18d0177220..7ee2680e21f83 100644 --- a/x-pack/test/osquery_cypress/artifact_manager.ts +++ b/x-pack/test/osquery_cypress/artifact_manager.ts @@ -6,5 +6,5 @@ */ export async function getLatestVersion(): Promise { - return '8.5.0-SNAPSHOT'; + return '8.6.0-SNAPSHOT'; } From 659ba3b69e34f072bdeef626e7830689307c25f2 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 28 Sep 2022 15:33:11 -0500 Subject: [PATCH 145/172] [data views] Allow noop on REST API update (#141875) * allow data view update noop --- .../rest_api_routes/update_data_view.ts | 36 +++++++++---------- .../update_data_view/errors.ts | 6 ++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/plugins/data_views/server/rest_api_routes/update_data_view.ts b/src/plugins/data_views/server/rest_api_routes/update_data_view.ts index 0e9f0983ac64d..1ac504ac652b8 100644 --- a/src/plugins/data_views/server/rest_api_routes/update_data_view.ts +++ b/src/plugins/data_views/server/rest_api_routes/update_data_view.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { IRouter, StartServicesAccessor } from '@kbn/core/server'; -import { DataViewsService } from '../../common/data_views'; +import { DataViewsService, DataView } from '../../common/data_views'; import { DataViewSpec } from '../../common/types'; import { handleErrors } from './util/handle_errors'; import { fieldSpecSchema, runtimeFieldSchema, serializedFieldFormatSchema } from './util/schemas'; @@ -71,46 +71,46 @@ export const updateDataView = async ({ name, } = spec; - let changeCount = 0; + let isChanged = false; let doRefreshFields = false; if (title !== undefined && title !== dataView.title) { - changeCount++; + isChanged = true; dataView.title = title; } if (timeFieldName !== undefined && timeFieldName !== dataView.timeFieldName) { - changeCount++; + isChanged = true; dataView.timeFieldName = timeFieldName; } if (sourceFilters !== undefined) { - changeCount++; + isChanged = true; dataView.sourceFilters = sourceFilters; } if (fieldFormats !== undefined) { - changeCount++; + isChanged = true; dataView.fieldFormatMap = fieldFormats; } if (type !== undefined) { - changeCount++; + isChanged = true; dataView.type = type; } if (typeMeta !== undefined) { - changeCount++; + isChanged = true; dataView.typeMeta = typeMeta; } if (name !== undefined) { - changeCount++; + isChanged = true; dataView.name = name; } if (fields !== undefined) { - changeCount++; + isChanged = true; doRefreshFields = true; dataView.fields.replaceAll( Object.values(fields || {}).map((field) => ({ @@ -122,19 +122,19 @@ export const updateDataView = async ({ } if (runtimeFieldMap !== undefined) { - changeCount++; + isChanged = true; dataView.replaceAllRuntimeFields(runtimeFieldMap); } - if (changeCount < 1) { - throw new Error('Index pattern change set is empty.'); - } - - await dataViewsService.updateSavedObject(dataView); + if (isChanged) { + const result = (await dataViewsService.updateSavedObject(dataView)) as DataView; - if (doRefreshFields && refreshFields) { - await dataViewsService.refreshFields(dataView); + if (doRefreshFields && refreshFields) { + await dataViewsService.refreshFields(dataView); + } + return result; } + return dataView; }; diff --git a/test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts b/test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts index 54f61cba1cfbe..670b003a3d24f 100644 --- a/test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts +++ b/test/api_integration/apis/data_views/data_views_crud/update_data_view/errors.ts @@ -53,7 +53,7 @@ export default function ({ getService }: FtrProviderContext) { ); }); - it('returns error when update patch is empty', async () => { + it('returns success when update patch is empty', async () => { const title1 = `foo-${Date.now()}-${Math.random()}*`; const response = await supertest.post(config.path).send({ [config.serviceKey]: { @@ -65,9 +65,7 @@ export default function ({ getService }: FtrProviderContext) { [config.serviceKey]: {}, }); - expect(response2.status).to.be(400); - expect(response2.body.statusCode).to.be(400); - expect(response2.body.message).to.be('Index pattern change set is empty.'); + expect(response2.status).to.be(200); }); }); }); From 630c96bfd9c62f0aaad7c0d3c40cacdcaf161de1 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 28 Sep 2022 23:18:02 +0200 Subject: [PATCH 146/172] [ML] Replace the local storage hook (#141968) * replace useStorage usage * fix test mock * unit tests * update tests * test for storage event --- .../full_time_range_selector.test.tsx | 2 +- .../full_time_range_selector.tsx | 2 +- .../components/job_selector/job_selector.tsx | 2 +- .../application/contexts/ml/use_storage.ts | 50 ------ .../contexts/storage/storage_context.test.tsx | 144 ++++++++++++++++++ .../contexts/storage/storage_context.tsx | 28 +++- .../public/application/explorer/explorer.tsx | 2 +- .../components/getting_started_callout.tsx | 2 +- .../series_controls/series_controls.tsx | 2 +- 9 files changed, 170 insertions(+), 64 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts create mode 100644 x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index 0d20267f33ecb..7566d3664af61 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -20,7 +20,7 @@ jest.mock('./full_time_range_selector_service', () => ({ mockSetFullTimeRange(indexPattern, query), })); -jest.mock('../../contexts/ml/use_storage', () => { +jest.mock('../../contexts/storage', () => { return { useStorage: jest.fn(() => 'exclude-frozen'), }; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index 06bb873971ba0..9b9154eb33660 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -23,7 +23,7 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; import { setFullTimeRange } from './full_time_range_selector_service'; -import { useStorage } from '../../contexts/ml/use_storage'; +import { useStorage } from '../../contexts/storage'; import { ML_FROZEN_TIER_PREFERENCE } from '../../../../common/types/storage'; import { GetTimeFieldRangeResponse } from '../../services/ml_api_service'; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx index 79d33ef9cd2ab..16fbc81b23f12 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -28,7 +28,7 @@ import { JobSelectorFlyoutProps, } from './job_selector_flyout'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; -import { useStorage } from '../../contexts/ml/use_storage'; +import { useStorage } from '../../contexts/storage'; import { ML_APPLY_TIME_RANGE_CONFIG } from '../../../../common/types/storage'; interface GroupObj { diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts deleted file mode 100644 index 761434a7bb3b4..0000000000000 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState } from 'react'; -import { isDefined } from '../../../../common/types/guards'; -import { useMlKibana } from '../kibana'; -import type { MlStorageKey } from '../../../../common/types/storage'; -import { TMlStorageMapped } from '../../../../common/types/storage'; - -/** - * Hook for accessing and changing a value in the storage. - * @param key - Storage key - * @param initValue - */ -export function useStorage>( - key: K, - initValue?: T -): [ - typeof initValue extends undefined - ? TMlStorageMapped - : Exclude, undefined>, - (value: TMlStorageMapped) => void -] { - const { - services: { storage }, - } = useMlKibana(); - - const [val, setVal] = useState(storage.get(key) ?? initValue); - - const setStorage = useCallback((value: TMlStorageMapped): void => { - try { - if (isDefined(value)) { - storage.set(key, value); - setVal(value); - } else { - storage.remove(key); - setVal(initValue); - } - } catch (e) { - throw new Error('Unable to update storage with provided value'); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return [val, setStorage]; -} diff --git a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx b/x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx new file mode 100644 index 0000000000000..6aeeb396f38c3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx @@ -0,0 +1,144 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { MlStorageContextProvider, useStorage } from './storage_context'; +import { MlStorageKey } from '../../../../common/types/storage'; + +const mockSet = jest.fn(); +const mockRemove = jest.fn(); + +jest.mock('../kibana', () => ({ + useMlKibana: () => { + return { + services: { + storage: { + set: mockSet, + get: jest.fn((key: MlStorageKey) => { + switch (key) { + case 'ml.gettingStarted.isDismissed': + return true; + default: + return; + } + }), + remove: mockRemove, + }, + }, + }; + }, +})); + +describe('useStorage', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('returns the default value', () => { + const { result } = renderHook(() => useStorage('ml.jobSelectorFlyout.applyTimeRange', true), { + wrapper: MlStorageContextProvider, + }); + + expect(result.current[0]).toBe(true); + }); + + test('returns the value from storage', () => { + const { result } = renderHook(() => useStorage('ml.gettingStarted.isDismissed', false), { + wrapper: MlStorageContextProvider, + }); + + expect(result.current[0]).toBe(true); + }); + + test('updates the storage value', async () => { + const { result, waitForNextUpdate } = renderHook( + () => useStorage('ml.gettingStarted.isDismissed'), + { + wrapper: MlStorageContextProvider, + } + ); + + const [value, setValue] = result.current; + + expect(value).toBe(true); + + await act(async () => { + setValue(false); + await waitForNextUpdate(); + }); + + expect(result.current[0]).toBe(false); + expect(mockSet).toHaveBeenCalledWith('ml.gettingStarted.isDismissed', false); + }); + + test('removes the storage value', async () => { + const { result, waitForNextUpdate } = renderHook( + () => useStorage('ml.gettingStarted.isDismissed'), + { + wrapper: MlStorageContextProvider, + } + ); + + const [value, setValue] = result.current; + + expect(value).toBe(true); + + await act(async () => { + setValue(undefined); + await waitForNextUpdate(); + }); + + expect(result.current[0]).toBe(undefined); + expect(mockRemove).toHaveBeenCalledWith('ml.gettingStarted.isDismissed'); + }); + + test('updates the value on storage event', async () => { + const { result, waitForNextUpdate } = renderHook( + () => useStorage('ml.gettingStarted.isDismissed'), + { + wrapper: MlStorageContextProvider, + } + ); + + expect(result.current[0]).toBe(true); + + await act(async () => { + window.dispatchEvent( + new StorageEvent('storage', { + key: 'test_key', + newValue: 'test_value', + }) + ); + }); + + expect(result.current[0]).toBe(true); + + await act(async () => { + window.dispatchEvent( + new StorageEvent('storage', { + key: 'ml.gettingStarted.isDismissed', + newValue: null, + }) + ); + await waitForNextUpdate(); + }); + + expect(result.current[0]).toBe(undefined); + + await act(async () => { + window.dispatchEvent( + new StorageEvent('storage', { + key: 'ml.gettingStarted.isDismissed', + newValue: 'false', + }) + ); + await waitForNextUpdate(); + }); + + expect(result.current[0]).toBe(false); + }); +}); diff --git a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx b/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx index ccd46c446ed2c..c2b00a176c08c 100644 --- a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx +++ b/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx @@ -69,7 +69,8 @@ export const MlStorageContextProvider: FC = ({ children }) => { setState((prev) => { return { ...prev, - [event.key as MlStorageKey]: event.newValue, + [event.key as MlStorageKey]: + typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue, }; }); } else { @@ -106,21 +107,32 @@ export const MlStorageContextProvider: FC = ({ children }) => { * @param key * @param initValue */ -export function useStorage( +export function useStorage>( key: K, - initValue?: TMlStorageMapped -): [TMlStorageMapped | undefined, (value: TMlStorageMapped) => void] { - const { value, setValue } = useContext(MlStorageContext); + initValue?: T +): [ + typeof initValue extends undefined + ? TMlStorageMapped | undefined + : Exclude, undefined>, + (value: TMlStorageMapped) => void +] { + const { value, setValue, removeValue } = useContext(MlStorageContext); const resultValue = useMemo(() => { - return (value?.[key] ?? initValue) as TMlStorageMapped; + return (value?.[key] ?? initValue) as typeof initValue extends undefined + ? TMlStorageMapped | undefined + : Exclude, undefined>; }, [value, key, initValue]); const setVal = useCallback( (v: TMlStorageMapped) => { - setValue(key, v); + if (isDefined(v)) { + setValue(key, v); + } else { + removeValue(key); + } }, - [setValue, key] + [setValue, removeValue, key] ); return [resultValue, setVal]; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.tsx b/x-pack/plugins/ml/public/application/explorer/explorer.tsx index b9feffb1ec116..5fcab05f068b8 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer.tsx @@ -78,7 +78,7 @@ import { useMlKibana, useMlLocator } from '../contexts/kibana'; import { useMlContext } from '../contexts/ml'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage'; -import { useStorage } from '../contexts/ml/use_storage'; +import { useStorage } from '../contexts/storage'; interface ExplorerPageProps { jobSelectorProps: JobSelectorProps; diff --git a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx index 457bca80ee2c0..7b1f380c90658 100644 --- a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx @@ -9,7 +9,7 @@ import React, { FC } from 'react'; import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useMlKibana } from '../../contexts/kibana'; -import { useStorage } from '../../contexts/ml/use_storage'; +import { useStorage } from '../../contexts/storage'; import { ML_GETTING_STARTED_CALLOUT_DISMISSED } from '../../../../common/types/storage'; const feedbackLink = 'https://www.elastic.co/community/'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx index 0df10505e73cc..23084c8d8886d 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx @@ -26,7 +26,7 @@ import { PartitionFieldConfig, PartitionFieldsConfig, } from '../../../../../common/types/storage'; -import { useStorage } from '../../../contexts/ml/use_storage'; +import { useStorage } from '../../../contexts/storage'; import { EntityFieldType } from '../../../../../common/types/anomalies'; import { FieldDefinition } from '../../../services/results_service/result_service_rx'; import { getViewableDetectors } from '../../timeseriesexplorer_utils/get_viewable_detectors'; From 598aa024bfabfb7a658afc13ad438dbdd304a32d Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 28 Sep 2022 14:29:56 -0700 Subject: [PATCH 147/172] [8.5][Elastic Defend onboarding] Disabling protections for Cloud (#141879) --- .../endpoint_policy_create_extension.tsx | 55 +------------------ .../handlers/create_default_policy.ts | 31 ++--------- .../handlers/validate_integration_config.ts | 12 ---- .../server/fleet_integration/types.ts | 6 -- 4 files changed, 7 insertions(+), 97 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx index d1223ff14826e..0617707505e52 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx @@ -8,7 +8,6 @@ import React, { memo, useState, useEffect, useRef, useCallback } from 'react'; import { EuiForm, - EuiCheckbox, EuiRadio, EuiSelect, EuiText, @@ -19,7 +18,6 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; import type { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/public'; -import { useLicense } from '../../../../../../common/hooks/use_license'; import { ALL_EVENTS, CLOUD_SECURITY, @@ -28,7 +26,6 @@ import { EDR_ESSENTIAL, ENDPOINT, INTERACTIVE_ONLY, - PREVENT_MALICIOUS_BEHAVIOR, } from './translations'; const PREFIX = 'endpoint_policy_create_extension'; @@ -70,11 +67,8 @@ const HelpTextWithPadding = styled.div` */ export const EndpointPolicyCreateExtension = memo( ({ newPolicy, onChange }) => { - const isPlatinumPlus = useLicense().isPlatinumPlus(); - // / Endpoint Radio Options (NGAV and EDRs) const [endpointPreset, setEndpointPreset] = useState('NGAV'); - const [behaviorProtectionChecked, setBehaviorProtectionChecked] = useState(false); const [selectedCloudEvent, setSelectedCloudEvent] = useState('ALL_EVENTS'); const [selectedEnvironment, setSelectedEnvironment] = useState('endpoint'); const initialRender = useRef(true); @@ -130,11 +124,6 @@ export const EndpointPolicyCreateExtension = memo) => { setSelectedEnvironment(e?.target?.value as Environment); @@ -170,9 +152,6 @@ export const EndpointPolicyCreateExtension = memo) => { setEndpointPreset(e.target.value as EndpointPreset); }, []); - const onChangeMaliciousBehavior = useCallback((e: React.ChangeEvent) => { - setBehaviorProtectionChecked((checked) => !checked); - }, []); const getEndpointPresetsProps = useCallback( (preset: EndpointPreset) => ({ @@ -217,7 +196,7 @@ export const EndpointPolicyCreateExtension = memo ), @@ -327,36 +306,6 @@ export const EndpointPolicyCreateExtension = memo - {isPlatinumPlus && ( - <> - - -

    - -

    -
    - - -

    - -

    -
    - - - - )} )} diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index 8ffd33e070d98..a2da989a9c08d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -13,11 +13,7 @@ import type { LicenseService } from '../../../common/license/license'; import { isAtLeast } from '../../../common/license/license'; import { ProtectionModes } from '../../../common/endpoint/types'; import type { PolicyConfig } from '../../../common/endpoint/types'; -import type { - AnyPolicyCreateConfig, - PolicyCreateCloudConfig, - PolicyCreateEndpointConfig, -} from '../types'; +import type { AnyPolicyCreateConfig, PolicyCreateEndpointConfig } from '../types'; import { ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL, ENDPOINT_CONFIG_PRESET_NGAV } from '../constants'; /** @@ -32,7 +28,7 @@ export const createDefaultPolicy = ( : policyConfigFactoryWithoutPaidFeatures(); if (config?.type === 'cloud') { - return getCloudPolicyWithIntegrationConfig(policy, config); + return getCloudPolicyConfig(policy); } return getEndpointPolicyWithIntegrationConfig(policy, config); @@ -95,37 +91,20 @@ const getEndpointPolicyWithIntegrationConfig = ( /** * Retrieve policy for cloud based on the on the cloud integration config */ -const getCloudPolicyWithIntegrationConfig = ( - policy: PolicyConfig, - config: PolicyCreateCloudConfig -): PolicyConfig => { - /** - * Check if the protection is supported, then retrieve Behavior Protection mode based on cloud settings - */ - const getBehaviorProtectionMode = () => { - if (!policy.linux.behavior_protection.supported) { - return ProtectionModes.off; - } - - return config.cloudConfig.preventions.behavior_protection - ? ProtectionModes.prevent - : ProtectionModes.off; - }; - +const getCloudPolicyConfig = (policy: PolicyConfig): PolicyConfig => { + // Disabling all protections, since it's not yet supported on Cloud integrations const protections = { - // Disabling memory_protection, since it's not supported on Cloud integrations memory_protection: { supported: false, mode: ProtectionModes.off, }, malware: { ...policy.linux.malware, - // Disabling Malware protection, since it's not supported on Cloud integrations due to performance reasons mode: ProtectionModes.off, }, behavior_protection: { ...policy.linux.behavior_protection, - mode: getBehaviorProtectionMode(), + mode: ProtectionModes.off, }, }; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts index 76bd3e8af93fb..bc58358565817 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts @@ -46,18 +46,6 @@ const validateEndpointIntegrationConfig = ( } }; const validateCloudIntegrationConfig = (config: PolicyCreateCloudConfig, logger: Logger): void => { - if (!config?.cloudConfig?.preventions) { - logger.warn( - 'missing cloudConfig preventions: {preventions : behavior_protection: true / false}' - ); - throwError('invalid value for cloudConfig: missing preventions '); - } - if (typeof config.cloudConfig.preventions.behavior_protection !== 'boolean') { - logger.warn( - `invalid value for cloudConfig preventions behavior_protection: ${config.cloudConfig.preventions.behavior_protection}` - ); - throwError('invalid value for cloudConfig preventions behavior_protection'); - } if (!config?.eventFilters) { logger.warn( `eventFilters is required for cloud integration: {eventFilters : nonInteractiveSession: true / false}` diff --git a/x-pack/plugins/security_solution/server/fleet_integration/types.ts b/x-pack/plugins/security_solution/server/fleet_integration/types.ts index 2d012832f1ae2..0c7a7c17951e6 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/types.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/types.ts @@ -18,12 +18,6 @@ export interface PolicyCreateEventFilters { export interface PolicyCreateCloudConfig { type: 'cloud'; - cloudConfig: { - preventions: { - malware: boolean; - behavior_protection: boolean; - }; - }; eventFilters?: PolicyCreateEventFilters; } From 74a12296237adeffdb3980b46e6a293cc7845f94 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Wed, 28 Sep 2022 17:36:32 -0400 Subject: [PATCH 148/172] [Synthetics] Validation for multi url hosts/urls + more (#141668) * synthetics - project monitors - add additional validation for urls/hosts and request.check.body * add normalizing for service.name and ssl.supported_protocols and additional tests * adjust types * adjust tests * adjust types * remove apmServiceName from browser monitors * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * adjust tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/formatters/browser/formatters.ts | 1 - .../common/formatters/common/formatters.ts | 1 + .../monitor_management/monitor_types.ts | 2 +- .../monitor_types_project.ts | 1 - .../fleet_package/browser/normalizers.ts | 1 - .../fleet_package/common/normalizers.ts | 1 + .../synthetics_service/formatters/browser.ts | 1 - .../synthetics_service/formatters/common.ts | 1 + .../normalizers/browser_monitor.test.ts | 16 +- .../normalizers/browser_monitor.ts | 23 +- .../normalizers/common_fields.test.ts | 66 +++++ .../normalizers/common_fields.ts | 65 ++++- .../normalizers/http_monitor.test.ts | 242 ++++++++++++++++ .../normalizers/http_monitor.ts | 63 ++++- .../normalizers/icmp_monitor.test.ts | 227 +++++++++++++++ .../normalizers/icmp_monitor.ts | 33 ++- .../project_monitor/normalizers/index.ts | 20 +- .../normalizers/tcp_monitor.test.ts | 265 ++++++++++++++++++ .../normalizers/tcp_monitor.ts | 39 ++- .../project_monitor_formatter.ts | 13 +- .../apis/uptime/rest/add_monitor_project.ts | 21 +- .../rest/fixtures/project_http_monitor.json | 7 +- .../rest/fixtures/project_icmp_monitor.json | 5 +- .../rest/fixtures/project_tcp_monitor.json | 9 +- 24 files changed, 1029 insertions(+), 94 deletions(-) create mode 100644 x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts create mode 100644 x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts create mode 100644 x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts create mode 100644 x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts index f05003f650deb..78e55e97d5b0c 100644 --- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts @@ -74,7 +74,6 @@ export const browserFormatters: BrowserFormatMap = { [ConfigKey.IGNORE_HTTPS_ERRORS]: null, [ConfigKey.PROJECT_ID]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: null, - [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, [ConfigKey.TEXT_ASSERTION]: null, ...commonFormatters, diff --git a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts index 89bf8793302ba..751721e5d7036 100644 --- a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts @@ -29,6 +29,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.MONITOR_SOURCE_TYPE]: null, [ConfigKey.FORM_MONITOR_TYPE]: null, [ConfigKey.JOURNEY_ID]: null, + [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, }; export const arrayToJsonFormatter = (value: string[] = []) => diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 58e3a31df8a37..46bc0f135452f 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -83,6 +83,7 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.MONITOR_SOURCE_TYPE]: SourceTypeCodec, [ConfigKey.CONFIG_ID]: t.string, [ConfigKey.JOURNEY_ID]: t.string, + [ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string, }), ]); @@ -218,7 +219,6 @@ export const EncryptedBrowserSimpleFieldsCodec = t.intersection([ [ConfigKey.PLAYWRIGHT_OPTIONS]: t.string, [ConfigKey.PROJECT_ID]: t.string, [ConfigKey.ORIGINAL_SPACE]: t.string, - [ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string, [ConfigKey.TEXT_ASSERTION]: t.string, }), ]), diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts index 6abcf4b832135..cba2f506189e6 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts @@ -30,7 +30,6 @@ export const ProjectMonitorCodec = t.intersection([ screenshot: ScreenshotOptionCodec, tags: t.union([t.string, t.array(t.string)]), ignoreHTTPSErrors: t.boolean, - apmServiceName: t.string, playwrightOptions: t.record(t.string, t.unknown), filter: t.interface({ match: t.string, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts index a8b59b16a3460..61f978327b8a5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts @@ -109,7 +109,6 @@ export const browserNormalizers: BrowserNormalizerMap = { [ConfigKey.IGNORE_HTTPS_ERRORS]: getBrowserNormalizer(ConfigKey.IGNORE_HTTPS_ERRORS), [ConfigKey.PROJECT_ID]: getBrowserNormalizer(ConfigKey.PROJECT_ID), [ConfigKey.PLAYWRIGHT_OPTIONS]: getBrowserNormalizer(ConfigKey.PLAYWRIGHT_OPTIONS), - [ConfigKey.CUSTOM_HEARTBEAT_ID]: getBrowserNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID), [ConfigKey.ORIGINAL_SPACE]: getBrowserNormalizer(ConfigKey.ORIGINAL_SPACE), [ConfigKey.TEXT_ASSERTION]: getBrowserNormalizer(ConfigKey.TEXT_ASSERTION), ...commonNormalizers, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts index 14fab3caeb132..630ac70a8e055 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts @@ -93,4 +93,5 @@ export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.MONITOR_SOURCE_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_SOURCE_TYPE), [ConfigKey.FORM_MONITOR_TYPE]: getCommonNormalizer(ConfigKey.FORM_MONITOR_TYPE), [ConfigKey.JOURNEY_ID]: getCommonNormalizer(ConfigKey.JOURNEY_ID), + [ConfigKey.CUSTOM_HEARTBEAT_ID]: getCommonNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID), }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts index 9c9d24a3f58f4..7095e437aea29 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts @@ -69,7 +69,6 @@ export const browserFormatters: BrowserFormatMap = { [ConfigKey.PROJECT_ID]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: (fields) => stringToObjectFormatter(fields[ConfigKey.PLAYWRIGHT_OPTIONS] || ''), - [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, [ConfigKey.TEXT_ASSERTION]: null, ...commonFormatters, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts index 16a0829cbb710..f9c3125041b44 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts @@ -31,6 +31,7 @@ export const commonFormatters: CommonFormatMap = { fields[ConfigKey.MONITOR_SOURCE_TYPE] || SourceType.UI, [ConfigKey.FORM_MONITOR_TYPE]: null, [ConfigKey.JOURNEY_ID]: null, + [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, }; export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts index ed02f1037e32b..9b32b61a59b35 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts @@ -67,9 +67,7 @@ describe('browser normalizers', () => { locations: ['us_central'], tags: ['tag1', 'tag2'], ignoreHTTPSErrors: true, - apmServiceName: 'cart-service', - type: DataStream.BROWSER, - }, + } as ProjectMonitor, // test that normalizers defaults to browser when type is omitted { id: 'test-id-2', screenshot: ScreenshotOption.ON, @@ -86,7 +84,6 @@ describe('browser normalizers', () => { locations: ['us_central', 'us_east'], tags: ['tag3', 'tag4'], ignoreHTTPSErrors: false, - apmServiceName: 'bean-service', type: DataStream.BROWSER, }, { @@ -106,7 +103,6 @@ describe('browser normalizers', () => { privateLocations: ['Germany'], tags: ['tag3', 'tag4'], ignoreHTTPSErrors: false, - apmServiceName: 'bean-service', type: DataStream.BROWSER, }, ]; @@ -118,6 +114,7 @@ describe('browser normalizers', () => { monitors, projectId, namespace: 'test-space', + version: '8.5.0', }); expect(actual).toEqual([ { @@ -145,7 +142,7 @@ describe('browser normalizers', () => { unit: 'm', }, screenshots: 'off', - 'service.name': 'cart-service', + 'service.name': '', 'source.project.content': 'test content 1', tags: ['tag1', 'tag2'], 'throttling.config': '5d/10u/20l', @@ -162,6 +159,7 @@ describe('browser normalizers', () => { timeout: null, }, unsupportedKeys: [], + errors: [], }, { normalizedFields: { @@ -201,7 +199,7 @@ describe('browser normalizers', () => { unit: 'm', }, screenshots: 'on', - 'service.name': 'bean-service', + 'service.name': '', 'source.project.content': 'test content 2', tags: ['tag3', 'tag4'], 'throttling.config': '10d/15u/18l', @@ -217,6 +215,7 @@ describe('browser normalizers', () => { timeout: null, }, unsupportedKeys: [], + errors: [], }, { normalizedFields: { @@ -263,7 +262,7 @@ describe('browser normalizers', () => { unit: 'm', }, screenshots: 'on', - 'service.name': 'bean-service', + 'service.name': '', 'source.project.content': 'test content 3', tags: ['tag3', 'tag4'], 'throttling.config': '10d/15u/18l', @@ -279,6 +278,7 @@ describe('browser normalizers', () => { timeout: null, }, unsupportedKeys: [], + errors: [], }, ]); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts index aae7031435c74..8eba2cd2651db 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts @@ -10,20 +10,14 @@ import { ConfigKey, DataStream, FormMonitorType, - Locations, - PrivateLocation, - ProjectMonitor, } from '../../../../common/runtime_types'; -import { getNormalizeCommonFields, getValueInSeconds } from './common_fields'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; - -export interface NormalizedProjectProps { - locations: Locations; - privateLocations: PrivateLocation[]; - monitor: ProjectMonitor; - projectId: string; - namespace: string; -} +import { + NormalizedProjectProps, + NormalizerResult, + getNormalizeCommonFields, + getValueInSeconds, +} from './common_fields'; export const getNormalizeBrowserFields = ({ locations = [], @@ -31,7 +25,8 @@ export const getNormalizeBrowserFields = ({ monitor, projectId, namespace, -}: NormalizedProjectProps): { normalizedFields: BrowserFields; unsupportedKeys: string[] } => { + version, +}: NormalizedProjectProps): NormalizerResult => { const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER]; const commonFields = getNormalizeCommonFields({ @@ -40,6 +35,7 @@ export const getNormalizeBrowserFields = ({ monitor, projectId, namespace, + version, }); const normalizedFields = { @@ -81,5 +77,6 @@ export const getNormalizeBrowserFields = ({ ...normalizedFields, }, unsupportedKeys: [], + errors: [], }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts new file mode 100644 index 0000000000000..fee6f0ca2637c --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { flattenAndFormatObject } from './common_fields'; + +describe('normalizeYamlConfig', () => { + it('does not continue flattening when encountering an array', () => { + const array = ['value1', 'value2']; + const supportedKeys: string[] = []; + const nestedObject = { + a: { + nested: { + key: array, + }, + }, + }; + expect(flattenAndFormatObject(nestedObject, '', supportedKeys)).toEqual({ + 'a.nested.key': array, + }); + }); + + it('does not continue flattening when encountering a supported key', () => { + const supportedKeys: string[] = ['a.supported.key']; + const object = { + with: { + further: { + nesting: '', + }, + }, + }; + const nestedObject = { + a: { + supported: { + key: object, + }, + }, + }; + expect(flattenAndFormatObject(nestedObject, '', supportedKeys)).toEqual({ + 'a.supported.key': object, + }); + }); + + it('flattens objects', () => { + const supportedKeys: string[] = []; + const nestedObject = { + a: { + nested: { + key: 'value1', + }, + }, + b: { + nested: { + key: 'value2', + }, + }, + }; + expect(flattenAndFormatObject(nestedObject, '', supportedKeys)).toEqual({ + 'a.nested.key': 'value1', + 'b.nested.key': 'value2', + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts index 31aebd0e8586e..56045712606a1 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts @@ -20,7 +20,27 @@ import { } from '../../../../common/runtime_types'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; import { DEFAULT_COMMON_FIELDS } from '../../../../common/constants/monitor_defaults'; -import { NormalizedProjectProps } from '.'; + +export interface NormalizedProjectProps { + locations: Locations; + privateLocations: PrivateLocation[]; + monitor: ProjectMonitor; + projectId: string; + namespace: string; + version: string; +} + +export interface Error { + id: string; + reason: string; + details: string; +} + +export interface NormalizerResult { + normalizedFields: MonitorTypeFields; + unsupportedKeys: string[]; + errors: Error[]; +} export const getNormalizeCommonFields = ({ locations = [], @@ -28,7 +48,7 @@ export const getNormalizeCommonFields = ({ monitor, projectId, namespace, -}: NormalizedProjectProps): CommonFields => { +}: NormalizedProjectProps): Partial => { const defaultFields = DEFAULT_COMMON_FIELDS; const normalizedFields = { @@ -45,18 +65,16 @@ export const getNormalizeCommonFields = ({ privateLocations, publicLocations: locations, }), - [ConfigKey.APM_SERVICE_NAME]: - monitor.apmServiceName || defaultFields[ConfigKey.APM_SERVICE_NAME], [ConfigKey.TAGS]: getOptionalListField(monitor.tags) || defaultFields[ConfigKey.TAGS], [ConfigKey.NAMESPACE]: formatKibanaNamespace(namespace) || defaultFields[ConfigKey.NAMESPACE], [ConfigKey.ORIGINAL_SPACE]: namespace || defaultFields[ConfigKey.NAMESPACE], [ConfigKey.CUSTOM_HEARTBEAT_ID]: getCustomHeartbeatId(monitor, projectId, namespace), [ConfigKey.ENABLED]: monitor.enabled ?? defaultFields[ConfigKey.ENABLED], + [ConfigKey.TIMEOUT]: monitor.timeout + ? getValueInSeconds(monitor.timeout) + : defaultFields[ConfigKey.TIMEOUT], }; - return { - ...defaultFields, - ...normalizedFields, - }; + return normalizedFields; }; export const getCustomHeartbeatId = ( @@ -94,6 +112,35 @@ export const getMonitorLocations = ({ ) as BrowserFields[ConfigKey.LOCATIONS]; }; +export const getUnsupportedKeysError = ( + monitor: ProjectMonitor, + unsupportedKeys: string[], + version: string +) => ({ + id: monitor.id, + reason: 'Unsupported Heartbeat option', + details: `The following Heartbeat options are not supported for ${ + monitor.type + } project monitors in ${version}: ${unsupportedKeys.join( + '|' + )}. You monitor was not created or updated.`, +}); + +export const getMultipleUrlsOrHostsError = ( + monitor: ProjectMonitor, + key: 'hosts' | 'urls', + version: string +) => ({ + id: monitor.id, + reason: 'Unsupported Heartbeat option', + details: `Multiple ${key} are not supported for ${ + monitor.type + } project monitors in ${version}. Please set only 1 ${key.slice( + 0, + -1 + )} per monitor. You monitor was not created or updated.`, +}); + export const getValueInSeconds = (value: string) => { const keyMap = { h: 60 * 60, @@ -136,7 +183,7 @@ export const getOptionalArrayField = (value: string[] | string = '') => { * @param {Object} [monitor] * @returns {Object} Returns an object containing synthetics-compatible configuration keys */ -const flattenAndFormatObject = (obj: Record, prefix = '', keys: string[]) => +export const flattenAndFormatObject = (obj: Record, prefix = '', keys: string[]) => Object.keys(obj).reduce>((acc, k) => { const pre = prefix.length ? prefix + '.' : ''; const key = pre + k; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts new file mode 100644 index 0000000000000..922c5ca6dab05 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts @@ -0,0 +1,242 @@ +/* + * 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 { Locations, LocationStatus, PrivateLocation } from '../../../../common/runtime_types'; +import { normalizeProjectMonitors } from '.'; + +describe('http normalizers', () => { + describe('normalize push monitors', () => { + const projectId = 'test-project-id'; + const locations: Locations = [ + { + id: 'us_central', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + id: 'us_east', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + ]; + const privateLocations: PrivateLocation[] = [ + { + id: 'germany', + label: 'Germany', + isServiceManaged: false, + concurrentMonitors: 1, + agentPolicyId: 'germany', + }, + ]; + const monitors = [ + { + locations: ['localhost'], + type: 'http', + enabled: false, + id: 'my-monitor-2', + name: 'My Monitor 2', + urls: ['http://localhost:9200', 'http://anotherurl:9200'], + schedule: 60, + timeout: '80s', + 'check.request': { + method: 'POST', + body: { + json: 'body', + }, + headers: { + 'a-header': 'a-header-value', + }, + }, + response: { + include_body: 'always', + }, + 'response.include_headers': false, + 'check.response': { + status: [200], + body: ['Saved', 'saved'], + }, + unsupportedKey: { + nestedUnsupportedKey: 'unsupportedValue', + }, + service: { + name: 'test service', + }, + ssl: { + supported_protocols: ['TLSv1.2', 'TLSv1.3'], + }, + }, + { + locations: ['localhost'], + type: 'http', + enabled: false, + id: 'my-monitor-3', + name: 'My Monitor 3', + urls: ['http://localhost:9200'], + schedule: 60, + timeout: '80s', + 'check.request': { + method: 'POST', + body: 'sometextbody', + headers: { + 'a-header': 'a-header-value', + }, + }, + response: { + include_body: 'always', + }, + tags: 'tag2,tag2', + 'response.include_headers': false, + 'check.response': { + status: [200], + body: { + positive: ['Saved', 'saved'], + }, + }, + 'service.name': 'test service', + 'ssl.supported_protocols': 'TLSv1.2,TLSv1.3', + }, + ]; + + it('properly normalizes http monitors', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors, + projectId, + namespace: 'test-space', + version: '8.5.0', + }); + expect(actual).toEqual([ + { + errors: [ + { + details: + 'Multiple urls are not supported for http project monitors in 8.5.0. Please set only 1 url per monitor. You monitor was not created or updated.', + id: 'my-monitor-2', + reason: 'Unsupported Heartbeat option', + }, + { + details: + 'The following Heartbeat options are not supported for http project monitors in 8.5.0: check.response.body|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.', + id: 'my-monitor-2', + reason: 'Unsupported Heartbeat option', + }, + ], + normalizedFields: { + __ui: { + is_tls_enabled: false, + }, + 'check.request.body': { + type: 'json', + value: '{"json":"body"}', + }, + 'check.request.headers': { + 'a-header': 'a-header-value', + }, + 'check.request.method': 'POST', + 'check.response.body.negative': [], + 'check.response.body.positive': [], + 'check.response.headers': {}, + 'check.response.status': ['200'], + config_id: '', + custom_heartbeat_id: 'my-monitor-2-test-project-id-test-space', + enabled: false, + form_monitor_type: 'http', + journey_id: 'my-monitor-2', + locations: [], + max_redirects: '0', + name: 'My Monitor 2', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + password: '', + project_id: 'test-project-id', + proxy_url: '', + 'response.include_body': 'always', + 'response.include_headers': false, + schedule: { + number: '60', + unit: 'm', + }, + 'service.name': 'test service', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.supported_protocols': ['TLSv1.2', 'TLSv1.3'], + 'ssl.verification_mode': 'full', + tags: [], + timeout: '80', + type: 'http', + urls: 'http://localhost:9200', + username: '', + }, + unsupportedKeys: ['check.response.body', 'unsupportedKey.nestedUnsupportedKey'], + }, + { + errors: [], + normalizedFields: { + __ui: { + is_tls_enabled: false, + }, + 'check.request.body': { + type: 'text', + value: 'sometextbody', + }, + 'check.request.headers': { + 'a-header': 'a-header-value', + }, + 'check.request.method': 'POST', + 'check.response.body.negative': [], + 'check.response.body.positive': ['Saved', 'saved'], + 'check.response.headers': {}, + 'check.response.status': ['200'], + config_id: '', + custom_heartbeat_id: 'my-monitor-3-test-project-id-test-space', + enabled: false, + form_monitor_type: 'http', + journey_id: 'my-monitor-3', + locations: [], + max_redirects: '0', + name: 'My Monitor 3', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + password: '', + project_id: 'test-project-id', + proxy_url: '', + 'response.include_body': 'always', + 'response.include_headers': false, + schedule: { + number: '60', + unit: 'm', + }, + 'service.name': 'test service', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.supported_protocols': ['TLSv1.2', 'TLSv1.3'], + 'ssl.verification_mode': 'full', + tags: ['tag2', 'tag2'], + timeout: '80', + type: 'http', + urls: 'http://localhost:9200', + username: '', + }, + unsupportedKeys: [], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts index 6f637d818667a..d994e61517822 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts @@ -4,16 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getNormalizeCommonFields } from './common_fields'; -import { NormalizedProjectProps } from './browser_monitor'; +import { get } from 'lodash'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; import { ConfigKey, DataStream, FormMonitorType, HTTPFields, + Mode, + TLSVersion, } from '../../../../common/runtime_types/monitor_management'; -import { normalizeYamlConfig, getValueInSeconds, getOptionalArrayField } from './common_fields'; +import { + NormalizedProjectProps, + NormalizerResult, + getNormalizeCommonFields, + normalizeYamlConfig, + getOptionalListField, + getOptionalArrayField, + getUnsupportedKeysError, + getMultipleUrlsOrHostsError, +} from './common_fields'; export const getNormalizeHTTPFields = ({ locations = [], @@ -21,18 +31,30 @@ export const getNormalizeHTTPFields = ({ monitor, projectId, namespace, -}: NormalizedProjectProps): { normalizedFields: HTTPFields; unsupportedKeys: string[] } => { + version, +}: NormalizedProjectProps): NormalizerResult => { const defaultFields = DEFAULT_FIELDS[DataStream.HTTP]; + const errors = []; const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); - const commonFields = getNormalizeCommonFields({ locations, privateLocations, monitor, projectId, namespace, + version, }); + /* Check if monitor has multiple urls */ + const urls = getOptionalListField(monitor.urls); + if (urls.length > 1) { + errors.push(getMultipleUrlsOrHostsError(monitor, 'urls', version)); + } + + if (unsupportedKeys.length) { + errors.push(getUnsupportedKeysError(monitor, unsupportedKeys, version)); + } + const normalizedFields = { ...yamlConfig, ...commonFields, @@ -41,9 +63,13 @@ export const getNormalizeHTTPFields = ({ [ConfigKey.URLS]: getOptionalArrayField(monitor.urls) || defaultFields[ConfigKey.URLS], [ConfigKey.MAX_REDIRECTS]: monitor[ConfigKey.MAX_REDIRECTS] || defaultFields[ConfigKey.MAX_REDIRECTS], - [ConfigKey.TIMEOUT]: monitor.timeout - ? getValueInSeconds(monitor.timeout) - : defaultFields[ConfigKey.TIMEOUT], + [ConfigKey.REQUEST_BODY_CHECK]: getRequestBodyField( + (yamlConfig as Record)[ConfigKey.REQUEST_BODY_CHECK] as string, + defaultFields[ConfigKey.REQUEST_BODY_CHECK] + ), + [ConfigKey.TLS_VERSION]: get(monitor, ConfigKey.TLS_VERSION) + ? (getOptionalListField(get(monitor, ConfigKey.TLS_VERSION)) as TLSVersion[]) + : defaultFields[ConfigKey.TLS_VERSION], }; return { normalizedFields: { @@ -51,5 +77,26 @@ export const getNormalizeHTTPFields = ({ ...normalizedFields, }, unsupportedKeys, + errors, + }; +}; + +export const getRequestBodyField = ( + value: string, + defaultValue: HTTPFields[ConfigKey.REQUEST_BODY_CHECK] +): HTTPFields[ConfigKey.REQUEST_BODY_CHECK] => { + let parsedValue: string; + let type: Mode; + + if (typeof value === 'object') { + parsedValue = JSON.stringify(value); + type = Mode.JSON; + } else { + parsedValue = value; + type = Mode.PLAINTEXT; + } + return { + type, + value: parsedValue || defaultValue.value, }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts new file mode 100644 index 0000000000000..e32ddf4f328a1 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts @@ -0,0 +1,227 @@ +/* + * 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 { Locations, LocationStatus, PrivateLocation } from '../../../../common/runtime_types'; +import { normalizeProjectMonitors } from '.'; + +describe('http normalizers', () => { + describe('normalize push monitors', () => { + const projectId = 'test-project-id'; + const locations: Locations = [ + { + id: 'us_central', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + id: 'us_east', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + ]; + const privateLocations: PrivateLocation[] = [ + { + id: 'germany', + label: 'Germany', + isServiceManaged: false, + concurrentMonitors: 1, + agentPolicyId: 'germany', + }, + ]; + const monitors = [ + { + locations: ['us_central'], + type: 'icmp', + id: 'Cloudflare-DNS', + name: 'Cloudflare DNS', + hosts: ['1.1.1.1'], + schedule: 1, + tags: ['service:smtp', 'org:google'], + privateLocations: ['Test private location 0'], + timeout: '1m', + wait: '30s', + 'service.name': 'test service', + }, + { + locations: ['us_central'], + type: 'icmp', + id: 'Cloudflare-DNS-2', + name: 'Cloudflare DNS 2', + hosts: '1.1.1.1', + schedule: 1, + tags: 'tag1,tag2', + privateLocations: ['Test private location 0'], + wait: '1m', + service: { + name: 'test service', + }, + }, + { + locations: ['us_central'], + type: 'icmp', + id: 'Cloudflare-DNS-3', + name: 'Cloudflare DNS 3', + hosts: '1.1.1.1,2.2.2.2', + schedule: 1, + tags: 'tag1,tag2', + privateLocations: ['Test private location 0'], + unsupportedKey: { + nestedUnsupportedKey: 'unnsuportedValue', + }, + }, + ]; + + it('properly normalizes http monitors', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors, + projectId, + namespace: 'test-space', + version: '8.5.0', + }); + expect(actual).toEqual([ + { + errors: [], + normalizedFields: { + config_id: '', + custom_heartbeat_id: 'Cloudflare-DNS-test-project-id-test-space', + enabled: true, + form_monitor_type: 'icmp', + hosts: '1.1.1.1', + journey_id: 'Cloudflare-DNS', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'Cloudflare DNS', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': 'test service', + tags: ['service:smtp', 'org:google'], + timeout: '60', + type: 'icmp', + wait: '30', + }, + unsupportedKeys: [], + }, + { + errors: [], + normalizedFields: { + config_id: '', + custom_heartbeat_id: 'Cloudflare-DNS-2-test-project-id-test-space', + enabled: true, + form_monitor_type: 'icmp', + hosts: '1.1.1.1', + journey_id: 'Cloudflare-DNS-2', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'Cloudflare DNS 2', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': 'test service', + tags: ['tag1', 'tag2'], + timeout: '16', + type: 'icmp', + wait: '60', + }, + unsupportedKeys: [], + }, + { + errors: [ + { + details: + 'Multiple hosts are not supported for icmp project monitors in 8.5.0. Please set only 1 host per monitor. You monitor was not created or updated.', + id: 'Cloudflare-DNS-3', + reason: 'Unsupported Heartbeat option', + }, + { + details: + 'The following Heartbeat options are not supported for icmp project monitors in 8.5.0: unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.', + id: 'Cloudflare-DNS-3', + reason: 'Unsupported Heartbeat option', + }, + ], + normalizedFields: { + config_id: '', + custom_heartbeat_id: 'Cloudflare-DNS-3-test-project-id-test-space', + enabled: true, + form_monitor_type: 'icmp', + hosts: '1.1.1.1', + journey_id: 'Cloudflare-DNS-3', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'Cloudflare DNS 3', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': '', + tags: ['tag1', 'tag2'], + timeout: '16', + type: 'icmp', + wait: '1', + }, + unsupportedKeys: ['unsupportedKey.nestedUnsupportedKey'], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts index 282475f94d7cd..ea4eb7dbdba84 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { getNormalizeCommonFields } from './common_fields'; -import { NormalizedProjectProps } from './browser_monitor'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; import { ConfigKey, @@ -14,7 +12,17 @@ import { FormMonitorType, ICMPFields, } from '../../../../common/runtime_types/monitor_management'; -import { normalizeYamlConfig, getValueInSeconds, getOptionalArrayField } from './common_fields'; +import { + NormalizerResult, + NormalizedProjectProps, + normalizeYamlConfig, + getNormalizeCommonFields, + getValueInSeconds, + getOptionalArrayField, + getOptionalListField, + getMultipleUrlsOrHostsError, + getUnsupportedKeysError, +} from './common_fields'; export const getNormalizeICMPFields = ({ locations = [], @@ -22,8 +30,10 @@ export const getNormalizeICMPFields = ({ monitor, projectId, namespace, -}: NormalizedProjectProps): { normalizedFields: ICMPFields; unsupportedKeys: string[] } => { + version, +}: NormalizedProjectProps): NormalizerResult => { const defaultFields = DEFAULT_FIELDS[DataStream.ICMP]; + const errors = []; const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); const commonFields = getNormalizeCommonFields({ @@ -32,8 +42,19 @@ export const getNormalizeICMPFields = ({ monitor, projectId, namespace, + version, }); + /* Check if monitor has multiple hosts */ + const hosts = getOptionalListField(monitor.hosts); + if (hosts.length > 1) { + errors.push(getMultipleUrlsOrHostsError(monitor, 'hosts', version)); + } + + if (unsupportedKeys.length) { + errors.push(getUnsupportedKeysError(monitor, unsupportedKeys, version)); + } + const normalizedFields = { ...yamlConfig, ...commonFields, @@ -41,9 +62,6 @@ export const getNormalizeICMPFields = ({ [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.ICMP, [ConfigKey.HOSTS]: getOptionalArrayField(monitor[ConfigKey.HOSTS]) || defaultFields[ConfigKey.HOSTS], - [ConfigKey.TIMEOUT]: monitor.timeout - ? getValueInSeconds(monitor.timeout) - : defaultFields[ConfigKey.TIMEOUT], [ConfigKey.WAIT]: monitor.wait ? getValueInSeconds(monitor.wait) || defaultFields[ConfigKey.WAIT] : defaultFields[ConfigKey.WAIT], @@ -54,5 +72,6 @@ export const getNormalizeICMPFields = ({ ...normalizedFields, }, unsupportedKeys, + errors, }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts index 82b2acfacbf5b..6bac17c0fb542 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts @@ -14,14 +14,7 @@ import { getNormalizeBrowserFields } from './browser_monitor'; import { getNormalizeICMPFields } from './icmp_monitor'; import { getNormalizeTCPFields } from './tcp_monitor'; import { getNormalizeHTTPFields } from './http_monitor'; - -export interface NormalizedProjectProps { - locations: Locations; - privateLocations: PrivateLocation[]; - monitor: ProjectMonitor; - projectId: string; - namespace: string; -} +import { NormalizedProjectProps } from './common_fields'; export const normalizeProjectMonitor = (props: NormalizedProjectProps) => { const { monitor } = props; @@ -50,14 +43,23 @@ export const normalizeProjectMonitors = ({ monitors = [], projectId, namespace, + version, }: { locations: Locations; privateLocations: PrivateLocation[]; monitors: ProjectMonitor[]; projectId: string; namespace: string; + version: string; }) => { return monitors.map((monitor) => { - return normalizeProjectMonitor({ monitor, locations, privateLocations, projectId, namespace }); + return normalizeProjectMonitor({ + monitor, + locations, + privateLocations, + projectId, + namespace, + version, + }); }); }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts new file mode 100644 index 0000000000000..094bf018ba127 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts @@ -0,0 +1,265 @@ +/* + * 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 { Locations, LocationStatus, PrivateLocation } from '../../../../common/runtime_types'; +import { normalizeProjectMonitors } from '.'; + +describe('http normalizers', () => { + describe('normalize push monitors', () => { + const projectId = 'test-project-id'; + const locations: Locations = [ + { + id: 'us_central', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + id: 'us_east', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + ]; + const privateLocations: PrivateLocation[] = [ + { + id: 'germany', + label: 'Germany', + isServiceManaged: false, + concurrentMonitors: 1, + agentPolicyId: 'germany', + }, + ]; + const monitors = [ + { + locations: ['us_central'], + type: 'tcp', + id: 'gmail-smtp', + name: 'GMail SMTP', + hosts: ['smtp.gmail.com:587'], + schedule: 1, + tags: ['service:smtp', 'org:google'], + privateLocations: ['BEEP'], + 'service.name': 'test service', + 'ssl.supported_protocols': ['TLSv1.2', 'TLSv1.3'], + }, + { + locations: ['us_central'], + type: 'tcp', + id: 'always-down', + name: 'Always Down', + hosts: 'localhost:18278', + schedule: 1, + tags: 'tag1,tag2', + privateLocations: ['BEEP'], + service: { + name: 'test service', + }, + ssl: { + supported_protocols: 'TLSv1.2,TLSv1.3', + }, + }, + { + locations: ['us_central'], + type: 'tcp', + id: 'always-down', + name: 'Always Down', + hosts: ['localhost', 'anotherhost'], + ports: ['5698'], + schedule: 1, + tags: 'tag1,tag2', + privateLocations: ['BEEP'], + unsupportedKey: { + nestedUnsupportedKey: 'unnsuportedValue', + }, + }, + ]; + + it('properly normalizes http monitors', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors, + projectId, + namespace: 'test-space', + version: '8.5.0', + }); + expect(actual).toEqual([ + { + errors: [], + normalizedFields: { + __ui: { + is_tls_enabled: false, + }, + 'check.receive': '', + 'check.send': '', + config_id: '', + custom_heartbeat_id: 'gmail-smtp-test-project-id-test-space', + enabled: true, + form_monitor_type: 'tcp', + hosts: 'smtp.gmail.com:587', + journey_id: 'gmail-smtp', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'GMail SMTP', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + proxy_url: '', + proxy_use_local_resolver: false, + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': 'test service', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.supported_protocols': ['TLSv1.2', 'TLSv1.3'], + 'ssl.verification_mode': 'full', + tags: ['service:smtp', 'org:google'], + timeout: '16', + type: 'tcp', + }, + unsupportedKeys: [], + }, + { + errors: [], + normalizedFields: { + __ui: { + is_tls_enabled: false, + }, + 'check.receive': '', + 'check.send': '', + config_id: '', + custom_heartbeat_id: 'always-down-test-project-id-test-space', + enabled: true, + form_monitor_type: 'tcp', + hosts: 'localhost:18278', + journey_id: 'always-down', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'Always Down', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + proxy_url: '', + proxy_use_local_resolver: false, + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': 'test service', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.supported_protocols': ['TLSv1.2', 'TLSv1.3'], + 'ssl.verification_mode': 'full', + tags: ['tag1', 'tag2'], + timeout: '16', + type: 'tcp', + }, + unsupportedKeys: [], + }, + { + errors: [ + { + details: + 'Multiple hosts are not supported for tcp project monitors in 8.5.0. Please set only 1 host per monitor. You monitor was not created or updated.', + id: 'always-down', + reason: 'Unsupported Heartbeat option', + }, + { + details: + 'The following Heartbeat options are not supported for tcp project monitors in 8.5.0: ports|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.', + id: 'always-down', + reason: 'Unsupported Heartbeat option', + }, + ], + normalizedFields: { + __ui: { + is_tls_enabled: false, + }, + 'check.receive': '', + 'check.send': '', + config_id: '', + custom_heartbeat_id: 'always-down-test-project-id-test-space', + enabled: true, + form_monitor_type: 'tcp', + hosts: 'localhost', + journey_id: 'always-down', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + status: 'ga', + url: 'test-url', + }, + ], + name: 'Always Down', + namespace: 'test_space', + origin: 'project', + original_space: 'test-space', + project_id: 'test-project-id', + proxy_url: '', + proxy_use_local_resolver: false, + schedule: { + number: '1', + unit: 'm', + }, + 'service.name': '', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + 'ssl.verification_mode': 'full', + tags: ['tag1', 'tag2'], + timeout: '16', + type: 'tcp', + }, + unsupportedKeys: ['ports', 'unsupportedKey.nestedUnsupportedKey'], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts index 8a85b2959d804..1045bc5ebff72 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts @@ -4,18 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { NormalizedProjectProps } from './browser_monitor'; +import { get } from 'lodash'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; -import { normalizeYamlConfig, getValueInSeconds } from './common_fields'; - import { ConfigKey, DataStream, FormMonitorType, TCPFields, + TLSVersion, } from '../../../../common/runtime_types/monitor_management'; -import { getNormalizeCommonFields, getOptionalArrayField } from './common_fields'; +import { + NormalizedProjectProps, + NormalizerResult, + normalizeYamlConfig, + getNormalizeCommonFields, + getOptionalArrayField, + getOptionalListField, + getMultipleUrlsOrHostsError, + getUnsupportedKeysError, +} from './common_fields'; export const getNormalizeTCPFields = ({ locations = [], @@ -23,8 +30,10 @@ export const getNormalizeTCPFields = ({ monitor, projectId, namespace, -}: NormalizedProjectProps): { normalizedFields: TCPFields; unsupportedKeys: string[] } => { + version, +}: NormalizedProjectProps): NormalizerResult => { const defaultFields = DEFAULT_FIELDS[DataStream.TCP]; + const errors = []; const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); const commonFields = getNormalizeCommonFields({ @@ -33,8 +42,19 @@ export const getNormalizeTCPFields = ({ monitor, projectId, namespace, + version, }); + /* Check if monitor has multiple hosts */ + const hosts = getOptionalListField(monitor.hosts); + if (hosts.length > 1) { + errors.push(getMultipleUrlsOrHostsError(monitor, 'hosts', version)); + } + + if (unsupportedKeys.length) { + errors.push(getUnsupportedKeysError(monitor, unsupportedKeys, version)); + } + const normalizedFields = { ...yamlConfig, ...commonFields, @@ -42,9 +62,9 @@ export const getNormalizeTCPFields = ({ [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.TCP, [ConfigKey.HOSTS]: getOptionalArrayField(monitor[ConfigKey.HOSTS]) || defaultFields[ConfigKey.HOSTS], - [ConfigKey.TIMEOUT]: monitor.timeout - ? getValueInSeconds(monitor.timeout) - : defaultFields[ConfigKey.TIMEOUT], + [ConfigKey.TLS_VERSION]: get(monitor, ConfigKey.TLS_VERSION) + ? (getOptionalListField(get(monitor, ConfigKey.TLS_VERSION)) as TLSVersion[]) + : defaultFields[ConfigKey.TLS_VERSION], }; return { normalizedFields: { @@ -52,5 +72,6 @@ export const getNormalizeTCPFields = ({ ...normalizedFields, }, unsupportedKeys, + errors, }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index aa0be87f0e818..1ca27869aa2cc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -197,22 +197,17 @@ export class ProjectMonitorFormatter { try { await this.validatePermissions({ monitor }); - const { normalizedFields: normalizedMonitor, unsupportedKeys } = normalizeProjectMonitor({ + const { normalizedFields: normalizedMonitor, errors } = normalizeProjectMonitor({ monitor, locations: this.locations, privateLocations: this.privateLocations, projectId: this.projectId, namespace: this.spaceId, + version: this.server.kibanaVersion, }); - if (unsupportedKeys.length) { - this.failedMonitors.push({ - id: monitor.id, - reason: 'Unsupported Heartbeat option', - details: `The following Heartbeat options are not supported for ${ - monitor.type - } project monitors in ${this.server.kibanaVersion}: ${unsupportedKeys.join('|')}`, - }); + if (errors.length) { + this.failedMonitors.push(...errors); this.handleStreamingMessage({ message: `${monitor.id}: failed to create or update monitor`, }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index 1110bbb875c73..105e6521c1da4 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -112,7 +112,12 @@ export default function ({ getService }: FtrProviderContext) { expect(messages[2].failedMonitors).eql([ { id: httpProjectMonitors.monitors[0].id, - details: `The following Heartbeat options are not supported for ${httpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: check.response.body|unsupportedKey.nestedUnsupportedKey`, + details: `Multiple urls are not supported for http project monitors in ${kibanaVersion}. Please set only 1 url per monitor. You monitor was not created or updated.`, + reason: 'Unsupported Heartbeat option', + }, + { + id: httpProjectMonitors.monitors[0].id, + details: `The following Heartbeat options are not supported for ${httpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: check.response.body|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, reason: 'Unsupported Heartbeat option', }, ]); @@ -200,7 +205,12 @@ export default function ({ getService }: FtrProviderContext) { expect(messages[2].failedMonitors).eql([ { id: tcpProjectMonitors.monitors[2].id, - details: `The following Heartbeat options are not supported for ${tcpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: ports|unsupportedKey.nestedUnsupportedKey`, + details: `Multiple hosts are not supported for tcp project monitors in ${kibanaVersion}. Please set only 1 host per monitor. You monitor was not created or updated.`, + reason: 'Unsupported Heartbeat option', + }, + { + id: tcpProjectMonitors.monitors[2].id, + details: `The following Heartbeat options are not supported for ${tcpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: ports|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, reason: 'Unsupported Heartbeat option', }, ]); @@ -284,7 +294,12 @@ export default function ({ getService }: FtrProviderContext) { expect(messages[2].failedMonitors).eql([ { id: icmpProjectMonitors.monitors[2].id, - details: `The following Heartbeat options are not supported for ${icmpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: unsupportedKey.nestedUnsupportedKey`, + details: `Multiple hosts are not supported for icmp project monitors in ${kibanaVersion}. Please set only 1 host per monitor. You monitor was not created or updated.`, + reason: 'Unsupported Heartbeat option', + }, + { + id: icmpProjectMonitors.monitors[2].id, + details: `The following Heartbeat options are not supported for ${icmpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, reason: 'Unsupported Heartbeat option', }, ]); diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json index fc754cbdcd038..42044c8ba9cf3 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json @@ -9,7 +9,8 @@ "id": "my-monitor-2", "name": "My Monitor 2", "urls": [ - "http://localhost:9200" + "http://localhost:9200", + "http://anotherurl:9200" ], "schedule": 60, "timeout": "80s", @@ -32,7 +33,6 @@ "saved" ] }, - "content": "", "unsupportedKey": { "nestedUnsupportedKey": "unsupportedValue" } @@ -69,8 +69,7 @@ "saved" ] } - }, - "content": "" + } } ] } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_icmp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_icmp_monitor.json index 8dec1b28d50c4..b19e910882582 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_icmp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_icmp_monitor.json @@ -14,7 +14,6 @@ "schedule": 1, "tags": [ "service:smtp", "org:google" ], "privateLocations": [ "Test private location 0" ], - "content": "", "wait": "30s" }, { @@ -26,7 +25,6 @@ "schedule": 1, "tags": "tag1,tag2", "privateLocations": [ "Test private location 0" ], - "content": "", "wait": "1m" }, { @@ -34,11 +32,10 @@ "type": "icmp", "id": "Cloudflare-DNS-3", "name": "Cloudflare DNS 3", - "hosts": "1.1.1.1", + "hosts": "1.1.1.1,2.2.2.2", "schedule": 1, "tags": "tag1,tag2", "privateLocations": [ "Test private location 0" ], - "content": "", "unsupportedKey": { "nestedUnsupportedKey": "unnsuportedValue" } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_tcp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_tcp_monitor.json index 30061673079d3..82d6c8c557e77 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_tcp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_tcp_monitor.json @@ -10,8 +10,7 @@ "hosts": [ "smtp.gmail.com:587" ], "schedule": 1, "tags": [ "service:smtp", "org:google" ], - "privateLocations": [ "BEEP" ], - "content": "" + "privateLocations": [ "BEEP" ] }, { "locations": [ "localhost" ], @@ -21,20 +20,18 @@ "hosts": "localhost:18278", "schedule": 1, "tags": "tag1,tag2", - "privateLocations": [ "BEEP" ], - "content": "" + "privateLocations": [ "BEEP" ] }, { "locations": [ "localhost" ], "type": "tcp", "id": "always-down", "name": "Always Down", - "hosts": "localhost", + "hosts": ["localhost", "anotherhost"], "ports": ["5698"], "schedule": 1, "tags": "tag1,tag2", "privateLocations": [ "BEEP" ], - "content": "", "unsupportedKey": { "nestedUnsupportedKey": "unnsuportedValue" } From e0b486051a7b360301e85c55fed995fc64b8273c Mon Sep 17 00:00:00 2001 From: JD Kurma Date: Wed, 28 Sep 2022 18:02:47 -0400 Subject: [PATCH 149/172] fix manifest url + more descriptive name (#142158) --- .../plugins/security_solution/server/lib/telemetry/artifact.ts | 2 +- .../server/lib/telemetry/tasks/configuration.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts index 46d0d6e665c4f..07ec2b6f2e49a 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts @@ -26,7 +26,7 @@ class Artifact implements IArtifact { this.receiver = receiver; this.esClusterInfo = await this.receiver.fetchClusterInfo(); const version = this.esClusterInfo?.version?.number; - this.manifestUrl = `${this.CDN_URL}/downloads/endpoint/manifest/artifacts-${version}.zip`; + this.manifestUrl = `${this.CDN_URL}/downloads/kibana/manifest/artifacts-${version}.zip`; } public async getArtifact(name: string): Promise { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts index f664fccbf75d8..d266d2f1c7699 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts @@ -29,7 +29,7 @@ export function createTelemetryConfigurationTaskConfig() { taskExecutionPeriod: TaskExecutionPeriod ) => { try { - const artifactName = 'telemetry-configuration-v1'; + const artifactName = 'telemetry-buffer-and-batch-sizes-v1'; const configArtifact = (await artifactService.getArtifact( artifactName )) as unknown as TelemetryConfiguration; From c71ab04eb577b935d80bf15e5afbb9294c799264 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 28 Sep 2022 18:26:49 -0400 Subject: [PATCH 150/172] [APM] Fixes service count API and modal scroll (#141725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [APM] Fixes multiple parallel search calls for service count to one call with a filter agg (#141242) * updates the description text when saving a group * fixes scrolling issue in save group modal * adds descripion for service count time range in service group list * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Add comment about only aggregating over metrics documents. * Update x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts PR feedback Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com> --- .../service_group_save/select_services.tsx | 134 +++++++++--------- .../service_list_preview.tsx | 2 +- .../service_groups_list/index.tsx | 6 + .../service_groups/get_services_counts.ts | 56 +++++--- .../apm/server/routes/service_groups/route.ts | 43 ++---- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 8 files changed, 120 insertions(+), 124 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/select_services.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/select_services.tsx index 251fa03735f16..7a97583bbd4ae 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/select_services.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/select_services.tsx @@ -36,7 +36,7 @@ const CentralizedContainer = styled.div` `; const MAX_CONTAINER_HEIGHT = 600; -const MODAL_HEADER_HEIGHT = 122; +const MODAL_HEADER_HEIGHT = 180; const MODAL_FOOTER_HEIGHT = 80; const suggestedFieldsWhitelist = [ @@ -118,10 +118,73 @@ export function SelectServices({ 'xpack.apm.serviceGroups.selectServicesForm.subtitle', { defaultMessage: - 'Use a query to select services for this group. Services that match this query within the last 24 hours will be assigned to the group.', + 'Use a query to select services for this group. The preview shows services that match this query within the last 24 hours.', } )} + + + { + setKuery(value); + }} + onChange={(value) => { + setStagedKuery(value); + }} + value={kuery} + suggestionFilter={(querySuggestion) => { + if ('field' in querySuggestion) { + const { + field: { + spec: { name: fieldName }, + }, + } = querySuggestion; + + return ( + fieldName.startsWith('label') || + suggestedFieldsWhitelist.includes(fieldName) + ); + } + return true; + }} + /> + + + { + setKuery(stagedKuery); + }} + iconType={!kuery ? 'search' : 'refresh'} + isDisabled={isServiceListPreviewLoading || !stagedKuery} + > + {!kuery + ? i18n.translate( + 'xpack.apm.serviceGroups.selectServicesForm.preview', + { defaultMessage: 'Preview' } + ) + : i18n.translate( + 'xpack.apm.serviceGroups.selectServicesForm.refresh', + { defaultMessage: 'Refresh' } + )} + + + + {kuery && data?.items && ( + + {i18n.translate( + 'xpack.apm.serviceGroups.selectServicesForm.matchingServiceCount', + { + defaultMessage: + '{servicesCount} {servicesCount, plural, =0 {services} one {service} other {services}} match the query', + values: { servicesCount: data?.items.length }, + } + )} + + )} - - - - { - setKuery(value); - }} - onChange={(value) => { - setStagedKuery(value); - }} - value={kuery} - suggestionFilter={(querySuggestion) => { - if ('field' in querySuggestion) { - const { - field: { - spec: { name: fieldName }, - }, - } = querySuggestion; - - return ( - fieldName.startsWith('label') || - suggestedFieldsWhitelist.includes(fieldName) - ); - } - return true; - }} - /> - - - { - setKuery(stagedKuery); - }} - iconType={!kuery ? 'search' : 'refresh'} - isDisabled={isServiceListPreviewLoading || !stagedKuery} - > - {!kuery - ? i18n.translate( - 'xpack.apm.serviceGroups.selectServicesForm.preview', - { defaultMessage: 'Preview' } - ) - : i18n.translate( - 'xpack.apm.serviceGroups.selectServicesForm.refresh', - { defaultMessage: 'Refresh' } - )} - - - - - {kuery && data?.items && ( - - - {i18n.translate( - 'xpack.apm.serviceGroups.selectServicesForm.matchingServiceCount', - { - defaultMessage: - '{servicesCount} {servicesCount, plural, =0 {services} one {service} other {services}} match the query', - values: { servicesCount: data?.items.length }, - } - )} - - - )} {!kuery && ( diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/service_list_preview.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/service_list_preview.tsx index eb366b84ceefd..66105c106805a 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/service_list_preview.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/service_list_preview.tsx @@ -37,7 +37,7 @@ type SORT_FIELD = 'serviceName' | 'environments' | 'agentName'; export function ServiceListPreview({ items, isLoading }: Props) { const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); const [sortDirection, setSortDirection] = useState( DEFAULT_SORT_DIRECTION diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/index.tsx index d0bf3a9f24b7c..734298dabe9eb 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/index.tsx @@ -156,6 +156,12 @@ export function ServiceGroupsList() { + + {i18n.translate('xpack.apm.serviceGroups.listDescription', { + defaultMessage: + 'Displayed service counts reflect the last 24 hours.', + })} + {items.length ? ( diff --git a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts index 880e3d2488898..c820cdd8445fc 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts @@ -7,50 +7,72 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { Setup } from '../../lib/helpers/setup_request'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; +import { SavedServiceGroup } from '../../../common/service_groups'; export async function getServicesCounts({ setup, - kuery, - maxNumberOfServices, start, end, + serviceGroups, }: { setup: Setup; - kuery: string; - maxNumberOfServices: number; start: number; end: number; + serviceGroups: SavedServiceGroup[]; }) { const { apmEventClient } = setup; - const response = await apmEventClient.search('get_services_count', { + const serviceGroupsKueryMap: Record = + serviceGroups.reduce((acc, sg) => { + return { + ...acc, + [sg.id]: kqlQuery(sg.kuery)[0], + }; + }, {}); + + const params = { apm: { - events: [ - ProcessorEvent.metric, - ProcessorEvent.transaction, - ProcessorEvent.span, - ProcessorEvent.error, - ], + // We're limiting the service count to only metrics documents. If a user + // actively disables system/app metrics and a service only ingests error + // events, that service will not be included in the service groups count. + // This is an edge case that only effects the count preview label. + events: [ProcessorEvent.metric], }, body: { track_total_hits: 0, size: 0, query: { bool: { - filter: [...rangeQuery(start, end), ...kqlQuery(kuery)], + filter: rangeQuery(start, end), }, }, aggs: { - services_count: { - cardinality: { - field: SERVICE_NAME, + service_groups: { + filters: { + filters: serviceGroupsKueryMap, + }, + aggs: { + services_count: { + cardinality: { + field: SERVICE_NAME, + }, + }, }, }, }, }, - }); + }; + const response = await apmEventClient.search('get_services_count', params); - return response?.aggregations?.services_count.value ?? 0; + const buckets: Record = + response?.aggregations?.service_groups.buckets ?? {}; + return Object.keys(buckets).reduce((acc, key) => { + return { + ...acc, + [key]: buckets[key].services_count.value, + }; + }, {}); } diff --git a/x-pack/plugins/apm/server/routes/service_groups/route.ts b/x-pack/plugins/apm/server/routes/service_groups/route.ts index ff37dc1f1c16a..4430aaae760eb 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/route.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/route.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common'; -import { keyBy, mapValues } from 'lodash'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; @@ -52,50 +51,26 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({ const { context, params } = resources; const { savedObjects: { client: savedObjectsClient }, - uiSettings: { client: uiSettingsClient }, } = await context.core; const { query: { start, end }, } = params; - const [setup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), - uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), - ]); + const setup = await setupRequest(resources); const serviceGroups = await getServiceGroups({ savedObjectsClient, }); - const serviceGroupsWithServiceCount = await Promise.all( - serviceGroups.map( - async ({ - id, - kuery, - }): Promise<{ id: string; servicesCount: number }> => { - const servicesCount = await getServicesCounts({ - setup, - kuery, - maxNumberOfServices, - start, - end, - }); - - return { - id, - servicesCount, - }; - } - ) - ); - - const servicesCounts = mapValues( - keyBy(serviceGroupsWithServiceCount, 'id'), - 'servicesCount' - ); - - return { servicesCounts }; + return { + servicesCounts: await getServicesCounts({ + setup, + serviceGroups, + start, + end, + }), + }; }, }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 612838bf5ea5f..e4db41419cb9b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7542,7 +7542,6 @@ "xpack.apm.serviceGroups.selectServicesForm.preview": "Aperçu", "xpack.apm.serviceGroups.selectServicesForm.refresh": "Actualiser", "xpack.apm.serviceGroups.selectServicesForm.saveGroup": "Enregistrer le groupe", - "xpack.apm.serviceGroups.selectServicesForm.subtitle": "Utilisez une requête pour sélectionner les services pour ce groupe. Les services qui correspondent à cette requête dans les dernières 24 heures seront affectés au groupe.", "xpack.apm.serviceGroups.selectServicesForm.title": "Sélectionner des services", "xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "Environnements", "xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "Nom", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1ace6b0a76659..557f4014f1e4c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7529,7 +7529,6 @@ "xpack.apm.serviceGroups.selectServicesForm.preview": "プレビュー", "xpack.apm.serviceGroups.selectServicesForm.refresh": "更新", "xpack.apm.serviceGroups.selectServicesForm.saveGroup": "グループを保存", - "xpack.apm.serviceGroups.selectServicesForm.subtitle": "クエリを使用してこのグループのサービスを選択します。過去24時間以内にこのクエリと一致したサービスはグループに割り当てられます。", "xpack.apm.serviceGroups.selectServicesForm.title": "サービスを選択", "xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "環境", "xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "名前", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2cdc7fa516263..7fb05ec21f71d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7546,7 +7546,6 @@ "xpack.apm.serviceGroups.selectServicesForm.preview": "预览", "xpack.apm.serviceGroups.selectServicesForm.refresh": "刷新", "xpack.apm.serviceGroups.selectServicesForm.saveGroup": "保存组", - "xpack.apm.serviceGroups.selectServicesForm.subtitle": "使用查询为该组选择服务。过去 24 小时内与此查询匹配的服务将分配给该组。", "xpack.apm.serviceGroups.selectServicesForm.title": "选择服务", "xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "环境", "xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "名称", From 92b686a76377e8dc264c0550d278fa134300dfd1 Mon Sep 17 00:00:00 2001 From: Klim Markelov Date: Thu, 29 Sep 2022 00:48:49 +0200 Subject: [PATCH 151/172] Change back the analytics js file url in integration tab (#142114) --- .../analytics_collection_integrate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx index 47f0f161263af..36b527097a304 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx @@ -49,7 +49,7 @@ export const AnalyticsCollectionIntegrate: React.FC From 07495a440774c5bc8d7af0a0310d4655139952f9 Mon Sep 17 00:00:00 2001 From: Karl Godard Date: Wed, 28 Sep 2022 18:00:20 -0700 Subject: [PATCH 152/172] fixes a few minor issues with playback in xtermjs (#142172) Co-authored-by: Karl Godard --- .../components/tty_player/hooks.test.tsx | 15 +++++++++++ .../public/components/tty_player/hooks.ts | 5 ++-- .../public/components/tty_player/index.tsx | 7 ++++- .../components/tty_search_bar/index.test.tsx | 4 +++ .../components/tty_search_bar/index.tsx | 27 ++++++++++++++----- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx index 004bd5bc757e0..8b2161c3b1216 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx @@ -167,6 +167,21 @@ describe('TTYPlayer/hooks', () => { expect(result.current.currentLine).toBe(initialProps.lines.length - 1); }); + it('should not print the first line twice after playback starts', async () => { + const { result, rerender } = renderHook((props) => useXtermPlayer(props), { + initialProps, + }); + + rerender({ ...initialProps, isPlaying: true }); + act(() => { + // advance render loop + jest.advanceTimersByTime(DEFAULT_TTY_PLAYSPEED_MS); + }); + rerender({ ...initialProps, isPlaying: false }); + + expect(result.current.terminal.buffer.active.getLine(0)?.translateToString(true)).toBe('256'); + }); + it('will allow a plain text search highlight on the last line printed', async () => { const { result: xTermResult } = renderHook((props) => useXtermPlayer(props), { initialProps, diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts index 08f163903b0a5..680d50283d5f1 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts @@ -281,13 +281,12 @@ export const useXtermPlayer = ({ useEffect(() => { if (isPlaying) { const timer = setTimeout(() => { - render(currentLine, false); - - if (currentLine === lines.length - 1) { + if (!hasNextPage && currentLine === lines.length - 1) { setIsPlaying(false); } else { const nextLine = Math.min(lines.length - 1, currentLine + TTY_LINES_PER_FRAME); setCurrentLine(nextLine); + render(nextLine, false); } }, playSpeed); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.tsx index ba57fc9d845cc..cb2746736c02f 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.tsx @@ -134,7 +134,12 @@ export const TTYPlayer = ({ - + diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx index 2f952b48e9d56..06fa17a6c151c 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx @@ -34,6 +34,7 @@ describe('TTYSearchBar component', () => { lines, seekToLine: jest.fn(), xTermSearchFn: jest.fn(), + setIsPlaying: jest.fn(), }; }); @@ -59,6 +60,7 @@ describe('TTYSearchBar component', () => { expect(props.xTermSearchFn).toHaveBeenCalledTimes(2); expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); + expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); it('calls seekToline and xTermSearchFn when currentMatch changes', async () => { @@ -85,6 +87,7 @@ describe('TTYSearchBar component', () => { expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '-h', 13); + expect(props.setIsPlaying).toHaveBeenCalledTimes(3); }); it('calls xTermSearchFn with empty query when search is cleared', async () => { @@ -101,5 +104,6 @@ describe('TTYSearchBar component', () => { await new Promise((r) => setTimeout(r, 100)); expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '', 0); + expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx index e41b3b967cfac..18b829127ab2d 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx @@ -19,15 +19,24 @@ export interface TTYSearchBarDeps { lines: IOLine[]; seekToLine(index: number): void; xTermSearchFn(query: string, index: number): void; + setIsPlaying(value: boolean): void; } -export const TTYSearchBar = ({ lines, seekToLine, xTermSearchFn }: TTYSearchBarDeps) => { +const STRIP_NEWLINES_REGEX = /^(\r\n|\r|\n|\n\r)/; + +export const TTYSearchBar = ({ + lines, + seekToLine, + xTermSearchFn, + setIsPlaying, +}: TTYSearchBarDeps) => { const [currentMatch, setCurrentMatch] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const jumpToMatch = useCallback( (match) => { if (match) { + setIsPlaying(false); const goToLine = lines.indexOf(match.line); seekToLine(goToLine); } @@ -40,7 +49,7 @@ export const TTYSearchBar = ({ lines, seekToLine, xTermSearchFn }: TTYSearchBarD clearTimeout(timeout); }; }, - [lines, seekToLine, xTermSearchFn, searchQuery] + [setIsPlaying, lines, seekToLine, xTermSearchFn, searchQuery] ); const searchResults = useMemo(() => { @@ -53,7 +62,7 @@ export const TTYSearchBar = ({ lines, seekToLine, xTermSearchFn }: TTYSearchBarD const cursorMovement = current.value.match(/^\x1b\[\d+;(\d+)(H|d)/); const regex = new RegExp(searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'ig'); const lineMatches = stripAnsi(current.value) - .replace(/^\r|\r?\n/, '') + .replace(STRIP_NEWLINES_REGEX, '') .matchAll(regex); if (lineMatches) { @@ -90,10 +99,14 @@ export const TTYSearchBar = ({ lines, seekToLine, xTermSearchFn }: TTYSearchBarD return matches; }, [searchQuery, lines, jumpToMatch, xTermSearchFn]); - const onSearch = useCallback((query) => { - setSearchQuery(query); - setCurrentMatch(null); - }, []); + const onSearch = useCallback( + (query) => { + setIsPlaying(false); + setSearchQuery(query); + setCurrentMatch(null); + }, + [setIsPlaying] + ); const onSetCurrentMatch = useCallback( (index) => { From a192d34a524cfd25d2aa1bf366dcb82c5b2c0424 Mon Sep 17 00:00:00 2001 From: Luke Gmys Date: Thu, 29 Sep 2022 06:10:55 +0200 Subject: [PATCH 153/172] [TIP] Extract useFilters logic to a shared context (#141766) --- .../mocks/mock_indicators_filters_context.tsx | 10 +- .../public/common/mocks/story_providers.tsx | 2 +- .../public/common/mocks/test_providers.tsx | 39 +++--- .../fields_table/fields_table.stories.tsx | 2 +- .../components/flyout/flyout.stories.tsx | 2 +- .../overview_tab/block/block.stories.tsx | 2 +- .../overview_tab/overview_tab.stories.tsx | 2 +- .../flyout/table_tab/table_tab.stories.tsx | 2 +- .../indicators_barchart_wrapper.stories.tsx | 11 +- .../indicators_barchart_wrapper.test.tsx | 10 +- .../indicators_barchart_wrapper.tsx | 19 +-- .../indicators_table.stories.tsx | 2 +- .../indicators_filters}/context.ts | 13 +- .../containers/indicators_filters/index.ts | 10 ++ .../indicators_filters/indicators_filters.tsx | 111 +++++++++++++++--- .../hooks/use_aggregated_indicators.test.tsx | 46 ++++---- .../hooks/use_aggregated_indicators.ts | 14 ++- .../indicators/hooks/use_indicators.test.tsx | 21 ++++ .../indicators/hooks/use_indicators.ts | 7 +- .../hooks/use_indicators_filters_context.ts | 5 +- .../indicators/indicators_page.test.tsx | 2 +- .../modules/indicators/indicators_page.tsx | 82 ++++++++----- .../filter_in/filter_in.stories.tsx | 2 +- .../filter_out/filter_out.stories.tsx | 2 +- .../hooks/use_filters/use_filters.test.tsx | 31 +++-- .../hooks/use_filters/use_filters.ts | 108 ++--------------- 26 files changed, 329 insertions(+), 228 deletions(-) rename x-pack/plugins/threat_intelligence/public/modules/indicators/{ => containers/indicators_filters}/context.ts (55%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx index 6e9c0b373a0fe..4b7832631fd16 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx @@ -6,11 +6,19 @@ */ import { FilterManager } from '@kbn/data-plugin/public'; -import { IndicatorsFiltersContextValue } from '../../modules/indicators/context'; +import { IndicatorsFiltersContextValue } from '../../modules/indicators/containers/indicators_filters/context'; export const mockIndicatorsFiltersContext: IndicatorsFiltersContextValue = { filterManager: { getFilters: () => [], setFilters: () => {}, } as unknown as FilterManager, + filters: [], + filterQuery: { + language: 'kuery', + query: '', + }, + handleSavedQuery: () => {}, + handleSubmitQuery: () => {}, + handleSubmitTimeRange: () => {}, }; diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx index eea2596327fb7..7cea653c45e5f 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx @@ -16,7 +16,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; import { SecuritySolutionContext } from '../../containers/security_solution_context'; import { getSecuritySolutionContextMock } from './mock_security_context'; -import { IndicatorsFiltersContext } from '../../modules/indicators/context'; +import { IndicatorsFiltersContext } from '../../modules/indicators/containers/indicators_filters/context'; import { FieldTypesContext } from '../../containers/field_types_provider'; import { generateFieldTypeMap } from './mock_field_type_map'; import { mockUiSettingsService } from './mock_kibana_ui_settings_service'; diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index a93b6bfe30469..c41f8972fd605 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -18,12 +18,13 @@ import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { MemoryRouter } from 'react-router-dom'; import { KibanaContext } from '../../hooks/use_kibana'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; import { mockUiSetting } from './mock_kibana_ui_settings_service'; import { SecuritySolutionContext } from '../../containers/security_solution_context'; -import { IndicatorsFiltersContext } from '../../modules/indicators/context'; +import { IndicatorsFiltersContext } from '../../modules/indicators/containers/indicators_filters/context'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; import { FieldTypesContext } from '../../containers/field_types_provider'; import { generateFieldTypeMap } from './mock_field_type_map'; @@ -128,23 +129,25 @@ export const mockedServices = { }; export const TestProvidersComponent: FC = ({ children }) => ( - - - - - - - - - {children} - - - - - - - - + + + + + + + + + + {children} + + + + + + + + + ); export type MockedSearch = jest.Mocked; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx index 8d3f13ae01af2..eb0ed8fb045ed 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx @@ -10,7 +10,7 @@ import { mockIndicatorsFiltersContext } from '../../../../../common/mocks/mock_i import { IndicatorFieldsTable } from '.'; import { generateMockIndicator } from '../../../../../../common/types/indicator'; import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; -import { IndicatorsFiltersContext } from '../../../context'; +import { IndicatorsFiltersContext } from '../../../containers/indicators_filters'; export default { component: IndicatorFieldsTable, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx index 12345056c7a9d..69236e778178b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx @@ -14,7 +14,7 @@ import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_s import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; import { IndicatorsFlyout } from '.'; -import { IndicatorsFiltersContext } from '../../context'; +import { IndicatorsFiltersContext } from '../../containers/indicators_filters'; export default { component: IndicatorsFlyout, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx index 1049518f4620f..e30d352c2644f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { IndicatorsFiltersContext } from '../../../../context'; +import { IndicatorsFiltersContext } from '../../../../containers/indicators_filters'; import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; import { generateMockIndicator } from '../../../../../../../common/types/indicator'; import { IndicatorBlock } from '.'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx index d543b6b6d1125..005edd9c4201d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx @@ -10,7 +10,7 @@ import { Story } from '@storybook/react'; import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; import { IndicatorsFlyoutOverview } from '.'; -import { IndicatorsFiltersContext } from '../../../context'; +import { IndicatorsFiltersContext } from '../../../containers/indicators_filters'; export default { component: IndicatorsFlyoutOverview, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx index 014d57b8ec113..60808a46356a8 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx @@ -14,7 +14,7 @@ import { mockUiSettingsService } from '../../../../../common/mocks/mock_kibana_u import { mockKibanaTimelinesService } from '../../../../../common/mocks/mock_kibana_timelines_service'; import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator'; import { IndicatorsFlyoutTable } from '.'; -import { IndicatorsFiltersContext } from '../../../context'; +import { IndicatorsFiltersContext } from '../../../containers/indicators_filters'; export default { component: IndicatorsFlyoutTable, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx index 213c750c5d1ed..f08e8f3b2f0e8 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.stories.tsx @@ -120,7 +120,16 @@ export const Default: Story = () => { - + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.test.tsx index 997cfc7922b9d..48d084b0e832a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.test.tsx @@ -13,6 +13,7 @@ import { TestProvidersComponent } from '../../../../common/mocks/test_providers' import { IndicatorsBarChartWrapper } from './indicators_barchart_wrapper'; import { DEFAULT_TIME_RANGE } from '../../../query_bar/hooks/use_filters/utils'; import { useFilters } from '../../../query_bar/hooks/use_filters'; +import moment from 'moment'; jest.mock('../../../query_bar/hooks/use_filters'); @@ -47,7 +48,14 @@ describe('', () => { it('should render barchart and field selector dropdown', () => { const component = render( - + ); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.tsx index fab1cfc5473d1..31148685e370d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/indicators_barchart_wrapper.tsx @@ -9,11 +9,12 @@ import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { TimeRange } from '@kbn/es-query'; +import { TimeRangeBounds } from '@kbn/data-plugin/common'; import { SecuritySolutionDataViewBase } from '../../../../types'; import { RawIndicatorFieldId } from '../../../../../common/types/indicator'; -import { useAggregatedIndicators } from '../../hooks/use_aggregated_indicators'; import { IndicatorsFieldSelector } from '../indicators_field_selector/indicators_field_selector'; import { IndicatorsBarChart } from '../indicators_barchart/indicators_barchart'; +import { ChartSeries } from '../../services/fetch_aggregated_indicators'; const DEFAULT_FIELD = RawIndicatorFieldId.Feed; @@ -26,6 +27,14 @@ export interface IndicatorsBarChartWrapperProps { * List of fields coming from the Security Solution sourcerer data view, passed down to the {@link IndicatorFieldSelector} to populate the dropdown. */ indexPattern: SecuritySolutionDataViewBase; + + series: ChartSeries[]; + + dateRange: TimeRangeBounds; + + field: string; + + onFieldChange: (value: string) => void; } /** @@ -33,11 +42,7 @@ export interface IndicatorsBarChartWrapperProps { * and handles retrieving aggregated indicator data. */ export const IndicatorsBarChartWrapper = memo( - ({ timeRange, indexPattern }) => { - const { dateRange, indicators, selectedField, onFieldChange } = useAggregatedIndicators({ - timeRange, - }); - + ({ timeRange, indexPattern, series, dateRange, field, onFieldChange }) => { return ( <> @@ -60,7 +65,7 @@ export const IndicatorsBarChartWrapper = memo( {timeRange ? ( - + ) : ( <> )} diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx index 034fc13630433..6505996a26a7d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx @@ -11,7 +11,7 @@ import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indi import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; import { IndicatorsTable } from './indicators_table'; -import { IndicatorsFiltersContext } from '../../context'; +import { IndicatorsFiltersContext } from '../../containers/indicators_filters/context'; import { DEFAULT_COLUMNS } from './hooks/use_column_settings'; export default { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/context.ts similarity index 55% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/context.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/context.ts index b6a4d17754f17..b075668c5015e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/context.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/context.ts @@ -6,13 +6,18 @@ */ import { createContext } from 'react'; -import { FilterManager } from '@kbn/data-plugin/public'; +import { FilterManager, SavedQuery } from '@kbn/data-plugin/public'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; export interface IndicatorsFiltersContextValue { - /** - * FilterManager is used to interact with KQL bar. - */ + timeRange?: TimeRange; + filters: Filter[]; + filterQuery: Query; + handleSavedQuery: (savedQuery: SavedQuery | undefined) => void; + handleSubmitTimeRange: (timeRange?: TimeRange) => void; + handleSubmitQuery: (filterQuery: Query) => void; filterManager: FilterManager; + savedQuery?: SavedQuery; } export const IndicatorsFiltersContext = createContext( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts new file mode 100644 index 0000000000000..4ef23e3e95001 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/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. + */ + +export * from './indicators_filters'; + +export * from './context'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx index 0fdd5c60ce5ce..2e8a2b55e4384 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx @@ -5,26 +5,105 @@ * 2.0. */ -import React, { ReactNode, VFC } from 'react'; -import { FilterManager } from '@kbn/data-plugin/public'; -import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from '../../context'; - -export interface IndicatorsFiltersProps { - /** - * Get {@link FilterManager} from the useFilters hook and save it in context to use within the indicators table. - */ - filterManager: FilterManager; - /** - * Component(s) to be displayed inside - */ - children: ReactNode; -} +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { SavedQuery } from '@kbn/data-plugin/common'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; +import deepEqual from 'fast-deep-equal'; +import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from './context'; +import { useKibana } from '../../../../hooks/use_kibana'; + +import { + DEFAULT_QUERY, + DEFAULT_TIME_RANGE, + encodeState, + FILTERS_QUERYSTRING_NAMESPACE, + stateFromQueryParams, +} from '../../../query_bar/hooks/use_filters/utils'; /** * Container used to wrap components and share the {@link FilterManager} through React context. */ -export const IndicatorsFilters: VFC = ({ filterManager, children }) => { - const contextValue: IndicatorsFiltersContextValue = { filterManager }; +export const IndicatorsFilters: FC = ({ children }) => { + const { pathname: browserPathName, search } = useLocation(); + const history = useHistory(); + const [savedQuery, setSavedQuery] = useState(undefined); + + const { + services: { + data: { + query: { filterManager }, + }, + }, + } = useKibana(); + + // Filters are picked using the UI widgets + const [filters, setFilters] = useState([]); + + // Time range is self explanatory + const [timeRange, setTimeRange] = useState(DEFAULT_TIME_RANGE); + + // filterQuery is raw kql query that user can type in to filter results + const [filterQuery, setFilterQuery] = useState(DEFAULT_QUERY); + + // Serialize filters into query string + useEffect(() => { + const filterStateAsString = encodeState({ filters, filterQuery, timeRange }); + if (!deepEqual(filterManager.getFilters(), filters)) { + filterManager.setFilters(filters); + } + + history.replace({ + pathname: browserPathName, + search: `${FILTERS_QUERYSTRING_NAMESPACE}=${filterStateAsString}`, + }); + }, [browserPathName, filterManager, filterQuery, filters, history, timeRange]); + + // Sync filterManager to local state (after they are changed from the ui) + useEffect(() => { + const subscription = filterManager.getUpdates$().subscribe(() => { + setFilters(filterManager.getFilters()); + }); + + return () => subscription.unsubscribe(); + }, [filterManager]); + + // Update local state with filter values from the url (on initial mount) + useEffect(() => { + const { + filters: filtersFromQuery, + timeRange: timeRangeFromQuery, + filterQuery: filterQueryFromQuery, + } = stateFromQueryParams(search); + + setTimeRange(timeRangeFromQuery); + setFilterQuery(filterQueryFromQuery); + setFilters(filtersFromQuery); + + // We only want to have it done on initial render with initial 'search' value; + // that is why 'search' is ommited from the deps array + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filterManager]); + + const onSavedQuery = useCallback( + (newSavedQuery: SavedQuery | undefined) => setSavedQuery(newSavedQuery), + [] + ); + + const contextValue: IndicatorsFiltersContextValue = useMemo( + () => ({ + timeRange, + filters, + filterQuery, + handleSavedQuery: onSavedQuery, + handleSubmitTimeRange: setTimeRange, + handleSubmitQuery: setFilterQuery, + filterManager, + savedQuery, + }), + [filterManager, filterQuery, filters, onSavedQuery, savedQuery, timeRange] + ); return ( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx index 6b3feb7406906..85c703cf5dca1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx @@ -12,33 +12,22 @@ import { mockedTimefilterService, TestProvidersComponent, } from '../../../common/mocks/test_providers'; -import { useFilters } from '../../query_bar/hooks/use_filters'; import { createFetchAggregatedIndicators } from '../services/fetch_aggregated_indicators'; jest.mock('../services/fetch_aggregated_indicators'); -jest.mock('../../query_bar/hooks/use_filters'); const useAggregatedIndicatorsParams: UseAggregatedIndicatorsParam = { timeRange: DEFAULT_TIME_RANGE, + filters: [], + filterQuery: { language: 'kuery', query: '' }, }; -const stub = () => {}; - const renderUseAggregatedIndicators = () => - renderHook((props) => useAggregatedIndicators(props), { + renderHook((props: UseAggregatedIndicatorsParam) => useAggregatedIndicators(props), { initialProps: useAggregatedIndicatorsParams, wrapper: TestProvidersComponent, }); -const initialFiltersValue = { - filters: [], - filterQuery: { language: 'kuery', query: '' }, - filterManager: {} as any, - handleSavedQuery: stub, - handleSubmitQuery: stub, - handleSubmitTimeRange: stub, -}; - describe('useAggregatedIndicators()', () => { beforeEach(jest.clearAllMocks); @@ -56,14 +45,12 @@ describe('useAggregatedIndicators()', () => { (createFetchAggregatedIndicators as MockedCreateFetchAggregatedIndicators).mockReturnValue( aggregatedIndicatorsQuery ); - - (useFilters as jest.MockedFunction).mockReturnValue(initialFiltersValue); }); it('should create and call the aggregatedIndicatorsQuery correctly', async () => { aggregatedIndicatorsQuery.mockResolvedValue([]); - const { rerender } = renderUseAggregatedIndicators(); + const { result, rerender } = renderUseAggregatedIndicators(); // indicators service and the query should be called just once expect( @@ -81,14 +68,12 @@ describe('useAggregatedIndicators()', () => { expect.any(AbortSignal) ); - // After filter values change, the hook will be re-rendered and should call the query function again, with - // updated values - (useFilters as jest.MockedFunction).mockReturnValue({ - ...initialFiltersValue, - filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" }, - }); - - await act(async () => rerender()); + await act(async () => + rerender({ + filterQuery: { language: 'kuery', query: "threat.indicator.type: 'file'" }, + filters: [], + }) + ); expect(aggregatedIndicatorsQuery).toHaveBeenCalledTimes(2); expect(aggregatedIndicatorsQuery).toHaveBeenLastCalledWith( @@ -97,5 +82,16 @@ describe('useAggregatedIndicators()', () => { }), expect.any(AbortSignal) ); + expect(result.current).toMatchInlineSnapshot(` + Object { + "dateRange": Object { + "max": "2022-01-02T00:00:00.000Z", + "min": "2022-01-01T00:00:00.000Z", + }, + "onFieldChange": [Function], + "selectedField": "threat.feed.name", + "series": Array [], + } + `); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts index 2609dbda5eb11..98e672ac3ad91 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts @@ -5,12 +5,11 @@ * 2.0. */ -import { TimeRange } from '@kbn/es-query'; +import { useQuery } from '@tanstack/react-query'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; import { useMemo, useState } from 'react'; import { TimeRangeBounds } from '@kbn/data-plugin/common'; -import { useQuery } from '@tanstack/react-query'; import { useInspector } from '../../../hooks/use_inspector'; -import { useFilters } from '../../query_bar/hooks/use_filters'; import { RawIndicatorFieldId } from '../../../../common/types/indicator'; import { useKibana } from '../../../hooks/use_kibana'; import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils'; @@ -27,13 +26,15 @@ export interface UseAggregatedIndicatorsParam { * to query indicators for the Indicators barchart. */ timeRange?: TimeRange; + filters: Filter[]; + filterQuery: Query; } export interface UseAggregatedIndicatorsValue { /** * Array of {@link ChartSeries}, ready to be used in the Indicators barchart. */ - indicators: ChartSeries[]; + series: ChartSeries[]; /** * Callback used by the IndicatorsFieldSelector component to query a new set of * aggregated indicators. @@ -54,6 +55,8 @@ const DEFAULT_FIELD = RawIndicatorFieldId.Feed; export const useAggregatedIndicators = ({ timeRange = DEFAULT_TIME_RANGE, + filters, + filterQuery, }: UseAggregatedIndicatorsParam): UseAggregatedIndicatorsValue => { const { services: { @@ -66,7 +69,6 @@ export const useAggregatedIndicators = ({ const { inspectorAdapters } = useInspector(); const [field, setField] = useState(DEFAULT_FIELD); - const { filters, filterQuery } = useFilters(); const aggregatedIndicatorsQuery = useMemo( () => @@ -105,7 +107,7 @@ export const useAggregatedIndicators = ({ return { dateRange, - indicators: data || [], + series: data || [], onFieldChange: setField, selectedField: field, }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx index 0b06208130780..7292dbcd1b03a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx @@ -98,6 +98,27 @@ describe('useIndicators()', () => { }), expect.any(AbortSignal) ); + + expect(hookResult.result.current).toMatchInlineSnapshot(` + Object { + "handleRefresh": [Function], + "indicatorCount": 0, + "indicators": Array [], + "isFetching": true, + "isLoading": true, + "onChangeItemsPerPage": [Function], + "onChangePage": [Function], + "pagination": Object { + "pageIndex": 0, + "pageSize": 50, + "pageSizeOptions": Array [ + 10, + 25, + 50, + ], + }, + } + `); }); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts index 5303b5dae06cb..1855d411eb8c7 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts @@ -8,6 +8,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Filter, Query, TimeRange } from '@kbn/es-query'; import { useQuery } from '@tanstack/react-query'; +import { EuiDataGridSorting } from '@elastic/eui'; import { useInspector } from '../../../hooks/use_inspector'; import { Indicator } from '../../../../common/types/indicator'; import { useKibana } from '../../../hooks/use_kibana'; @@ -22,11 +23,15 @@ export interface UseIndicatorsParams { filterQuery: Query; filters: Filter[]; timeRange?: TimeRange; - sorting: any[]; + sorting: EuiDataGridSorting['columns']; } export interface UseIndicatorsValue { handleRefresh: () => void; + + /** + * Array of {@link Indicator} ready to render inside the IndicatorTable component + */ indicators: Indicator[]; indicatorCount: number; pagination: Pagination; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts index e8cc4b18474bf..e4c7c48d03d1b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts @@ -6,7 +6,10 @@ */ import { useContext } from 'react'; -import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from '../context'; +import { + IndicatorsFiltersContext, + IndicatorsFiltersContextValue, +} from '../containers/indicators_filters/context'; /** * Hook to retrieve {@link IndicatorsFiltersContext} (contains FilterManager) diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx index 11bdb8fc8e6ed..7740345f468d4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx @@ -27,7 +27,7 @@ describe('', () => { useAggregatedIndicators as jest.MockedFunction ).mockReturnValue({ dateRange: { min: moment(), max: moment() }, - indicators: [], + series: [], selectedField: '', onFieldChange: () => {}, }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index f51e062e1c3cb..fff2caad5715f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -7,7 +7,6 @@ import React, { FC, VFC } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { IndicatorsFilters } from './containers/indicators_filters/indicators_filters'; import { IndicatorsBarChartWrapper } from './components/indicators_barchart_wrapper/indicators_barchart_wrapper'; import { IndicatorsTable } from './components/indicators_table/indicators_table'; import { useIndicators } from './hooks/use_indicators'; @@ -19,14 +18,18 @@ import { useSourcererDataView } from './hooks/use_sourcerer_data_view'; import { FieldTypesProvider } from '../../containers/field_types_provider'; import { InspectorProvider } from '../../containers/inspector'; import { useColumnSettings } from './components/indicators_table/hooks/use_column_settings'; +import { useAggregatedIndicators } from './hooks/use_aggregated_indicators'; +import { IndicatorsFilters } from './containers/indicators_filters'; const queryClient = new QueryClient(); const IndicatorsPageProviders: FC = ({ children }) => ( - - {children} - + + + {children} + + ); @@ -46,43 +49,68 @@ const IndicatorsPageContent: VFC = () => { savedQuery, } = useFilters(); - const { handleRefresh, ...indicators } = useIndicators({ + const { + handleRefresh, + indicatorCount, + indicators, + isLoading, + onChangeItemsPerPage, + onChangePage, + pagination, + } = useIndicators({ filters, filterQuery, timeRange, sorting: columnSettings.sorting.columns, }); + const { dateRange, series, selectedField, onFieldChange } = useAggregatedIndicators({ + timeRange, + filters, + filterQuery, + }); + return ( - - - + + + + + - - - - - + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx index 1e6c7d0c2614e..08297774e51f6 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Story } from '@storybook/react'; import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { IndicatorsFiltersContext } from '../../../indicators/context'; +import { IndicatorsFiltersContext } from '../../../indicators/containers/indicators_filters/context'; import { FilterIn } from '.'; export default { diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx index a2f0061d36a94..cf625c23754a3 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Story } from '@storybook/react'; import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { IndicatorsFiltersContext } from '../../../indicators/context'; +import { IndicatorsFiltersContext } from '../../../indicators/containers/indicators_filters/context'; import { FilterOut } from '.'; export default { diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.test.tsx index b9c16240df4b0..4d34c9ee6ff0f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.test.tsx @@ -9,28 +9,37 @@ import { mockUseKibanaForFilters } from '../../../../common/mocks/mock_use_kiban import { renderHook, act, RenderHookResult, Renderer } from '@testing-library/react-hooks'; import { useFilters, UseFiltersValue } from './use_filters'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useHistory } from 'react-router-dom'; import { Filter } from '@kbn/es-query'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; +import React, { FC } from 'react'; +import { IndicatorsFilters } from '../../../indicators/containers/indicators_filters'; jest.mock('react-router-dom', () => ({ - useLocation: jest.fn().mockReturnValue({ - search: '', - }), + MemoryRouter: ({ children }: any) => <>{children}, + useLocation: jest.fn().mockReturnValue({ pathname: '', search: '' }), useHistory: jest.fn().mockReturnValue({ replace: jest.fn() }), })); +const FiltersWrapper: FC = ({ children }) => ( + + {children}{' '} + +); + +const renderUseFilters = () => renderHook(() => useFilters(), { wrapper: FiltersWrapper }); + describe('useFilters()', () => { let hookResult: RenderHookResult<{}, UseFiltersValue, Renderer>; let mockRef: ReturnType; + afterAll(() => jest.unmock('react-router-dom')); + describe('when mounted', () => { beforeEach(async () => { mockRef = mockUseKibanaForFilters(); - hookResult = renderHook(() => useFilters(), { - wrapper: TestProvidersComponent, - }); + hookResult = renderUseFilters(); }); it('should have valid initial filterQuery value', () => { @@ -44,9 +53,7 @@ describe('useFilters()', () => { '?indicators=(filterQuery:(language:kuery,query:%27threat.indicator.type%20:%20"file"%20%27),filters:!(),timeRange:(from:now/d,to:now/d))', }); - hookResult = renderHook(() => useFilters(), { - wrapper: TestProvidersComponent, - }); + hookResult = renderUseFilters(); expect(hookResult.result.current.filterQuery).toMatchObject({ language: 'kuery', @@ -61,9 +68,7 @@ describe('useFilters()', () => { beforeEach(async () => { mockRef = mockUseKibanaForFilters(); - hookResult = renderHook(() => useFilters(), { - wrapper: TestProvidersComponent, - }); + hookResult = renderUseFilters(); (useHistory as jest.Mock).mockReturnValue({ replace: historyReplace }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts index 73ce49691d5a6..c50a10c17b709 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts @@ -5,110 +5,24 @@ * 2.0. */ -import { Filter, Query, TimeRange } from '@kbn/es-query'; -import { useCallback, useEffect, useState } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; -import deepEqual from 'fast-deep-equal'; -import type { FilterManager, SavedQuery } from '@kbn/data-plugin/public'; -import { useKibana } from '../../../../hooks/use_kibana'; +import { useContext } from 'react'; import { - DEFAULT_QUERY, - DEFAULT_TIME_RANGE, - encodeState, - FILTERS_QUERYSTRING_NAMESPACE, - stateFromQueryParams, -} from './utils'; + IndicatorsFiltersContext, + IndicatorsFiltersContextValue, +} from '../../../indicators/containers/indicators_filters'; -export interface UseFiltersValue { - timeRange?: TimeRange; - filters: Filter[]; - filterQuery: Query; - handleSavedQuery: (savedQuery: SavedQuery | undefined) => void; - handleSubmitTimeRange: (timeRange?: TimeRange) => void; - handleSubmitQuery: (filterQuery: Query) => void; - filterManager: FilterManager; - savedQuery?: SavedQuery; -} +export type UseFiltersValue = IndicatorsFiltersContextValue; /** * Custom react hook housing logic for KQL bar * @returns Filters and TimeRange for use with KQL bar */ -export const useFilters = (): UseFiltersValue => { - const { pathname: browserPathName, search } = useLocation(); - const history = useHistory(); - const [savedQuery, setSavedQuery] = useState(undefined); +export const useFilters = () => { + const contextValue = useContext(IndicatorsFiltersContext); - const { - services: { - data: { - query: { filterManager }, - }, - }, - } = useKibana(); + if (!contextValue) { + throw new Error('Filters can only be used inside IndicatorFiltersContext'); + } - // Filters are picked using the UI widgets - const [filters, setFilters] = useState([]); - - // Time range is self explanatory - const [timeRange, setTimeRange] = useState(DEFAULT_TIME_RANGE); - - // filterQuery is raw kql query that user can type in to filter results - const [filterQuery, setFilterQuery] = useState(DEFAULT_QUERY); - - // Serialize filters into query string - useEffect(() => { - const filterStateAsString = encodeState({ filters, filterQuery, timeRange }); - if (!deepEqual(filterManager.getFilters(), filters)) { - filterManager.setFilters(filters); - } - - history.replace({ - pathname: browserPathName, - search: `${FILTERS_QUERYSTRING_NAMESPACE}=${filterStateAsString}`, - }); - }, [browserPathName, filterManager, filterQuery, filters, history, timeRange]); - - // Sync filterManager to local state (after they are changed from the ui) - useEffect(() => { - const subscription = filterManager.getUpdates$().subscribe(() => { - setFilters(filterManager.getFilters()); - }); - - return () => subscription.unsubscribe(); - }, [filterManager]); - - // Update local state with filter values from the url (on initial mount) - useEffect(() => { - const { - filters: filtersFromQuery, - timeRange: timeRangeFromQuery, - filterQuery: filterQueryFromQuery, - } = stateFromQueryParams(search); - - setTimeRange(timeRangeFromQuery); - setFilterQuery(filterQueryFromQuery); - setFilters(filtersFromQuery); - - // We only want to have it done on initial render with initial 'search' value; - // that is why 'search' is ommited from the deps array - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filterManager]); - - const onSavedQuery = useCallback( - (newSavedQuery: SavedQuery | undefined) => setSavedQuery(newSavedQuery), - [] - ); - - return { - timeRange, - filters, - filterQuery, - handleSavedQuery: onSavedQuery, - handleSubmitTimeRange: setTimeRange, - handleSubmitQuery: setFilterQuery, - filterManager, - savedQuery, - }; + return contextValue; }; From 45e80b2a477f99c63dc3245efa471bd81c12268a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:44:53 -0600 Subject: [PATCH 154/172] [api-docs] Daily api_docs build (#142178) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.devdocs.json | 139 +- api_docs/alerting.mdx | 4 +- api_docs/apm.devdocs.json | 24 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.devdocs.json | 32 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.devdocs.json | 253 +- api_docs/core.mdx | 4 +- api_docs/custom_integrations.devdocs.json | 23 +- api_docs/custom_integrations.mdx | 4 +- api_docs/dashboard.devdocs.json | 3835 ++++++----------- api_docs/dashboard.mdx | 10 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_query.devdocs.json | 8 - api_docs/data_query.mdx | 2 +- api_docs/data_search.devdocs.json | 12 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 14 +- api_docs/deprecations_by_plugin.mdx | 14 +- api_docs/deprecations_by_team.mdx | 7 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.devdocs.json | 4 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.devdocs.json | 6 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.devdocs.json | 8 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.devdocs.json | 66 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.devdocs.json | 6 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...equest_handler_context_server.devdocs.json | 283 ++ ...re_http_request_handler_context_server.mdx | 33 + .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- .../kbn_core_injected_metadata_browser.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- ...ore_saved_objects_api_browser.devdocs.json | 6 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- ...core_saved_objects_api_server.devdocs.json | 6 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.devdocs.json | 6 +- api_docs/kbn_rule_data_utils.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ion_exception_list_components.devdocs.json | 1209 ++++++ ...ritysolution_exception_list_components.mdx | 39 + api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.devdocs.json | 8 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.devdocs.json | 26 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 20 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.devdocs.json | 4 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.devdocs.json | 35 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.devdocs.json | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.devdocs.json | 8 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 8 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.devdocs.json | 4 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.devdocs.json | 16 +- api_docs/triggers_actions_ui.mdx | 4 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 207 +- api_docs/visualizations.mdx | 4 +- 432 files changed, 3973 insertions(+), 3212 deletions(-) create mode 100644 api_docs/kbn_core_http_request_handler_context_server.devdocs.json create mode 100644 api_docs/kbn_core_http_request_handler_context_server.mdx create mode 100644 api_docs/kbn_securitysolution_exception_list_components.devdocs.json create mode 100644 api_docs/kbn_securitysolution_exception_list_components.mdx diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 4d0790ec95184..929e069b2bf52 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 88e4157414a7f..43856104f3281 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index c3bd7eb349a07..7694fbde6190c 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index a19bdb320740d..9971fcee0a58a 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -2666,8 +2666,6 @@ "Alert", "; scheduleActions: (actionGroup: ActionGroupIds, context?: Context) => ", "Alert", - "; scheduleActionsWithSubGroup: (actionGroup: ActionGroupIds, subgroup: string, context?: Context) => ", - "Alert", "; setContext: (context: Context) => ", "Alert", "; getContext: () => Context; hasContext: () => boolean; }" @@ -2832,7 +2830,11 @@ "section": "def-common.IExecutionErrorsResult", "text": "IExecutionErrorsResult" }, - ">; bulkEdit: ; getGlobalExecutionKpiWithAuth: ({ dateStart, dateEnd, filter, }: ", + "GetGlobalExecutionKPIParams", + ") => Promise<{ success: number; unknown: number; failure: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getRuleExecutionKPI: ({ id, dateStart, dateEnd, filter }: ", + "GetRuleExecutionKPIParams", + ") => Promise<{ success: number; unknown: number; failure: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkEdit: ; }; observability: { setup: { getScopedAnnotationsClient: (requestContext: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { licensing: Promise<", { "pluginId": "licensing", @@ -5696,13 +5684,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { licensing: Promise<", { "pluginId": "licensing", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d9218c6f9c48f..934ffbbd33801 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index b574eadcc22f7..12a25911e2da1 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.devdocs.json b/api_docs/bfetch.devdocs.json index 74f1cf1aee455..f060159bc0f3a 100644 --- a/api_docs/bfetch.devdocs.json +++ b/api_docs/bfetch.devdocs.json @@ -357,13 +357,7 @@ "(path: string, params: (request: ", "KibanaRequest", ", context: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ") => ", { "pluginId": "bfetch", @@ -375,13 +369,7 @@ ", method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | undefined, pluginRouter?: ", "IRouter", "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", "> | undefined) => void" ], "path": "src/plugins/bfetch/server/plugin.ts", @@ -414,13 +402,7 @@ "(request: ", "KibanaRequest", ", context: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ") => ", { "pluginId": "bfetch", @@ -461,13 +443,7 @@ "signature": [ "IRouter", "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", "> | undefined" ], "path": "src/plugins/bfetch/server/plugin.ts", diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index e8fa3ead871d7..c4055d6564051 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 9c484f7dfc615..4ef1b1b03a0ec 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 2ec486a4957e7..6df29978541a0 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index b204ddb7b0ee0..718e93231dffe 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index fdf2f80ec284b..74692e016ccf0 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 33e6421081033..9b5efb2aa51f4 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 8cdffb406e1d3..1ccfb42f61d02 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index e365d6a87afe3..00e0779286aae 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 69b2dfc22a263..e1427ab3d19f0 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 607bae8fad1fc..67fa46292601f 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -10389,7 +10389,7 @@ "\nThe outcome for a successful `resolve` call is one of the following values:\n\n * `'exactMatch'` -- One document exactly matched the given ID.\n * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different\n than the given ID.\n * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the\n `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID." ], "signature": [ - "\"exactMatch\" | \"aliasMatch\" | \"conflict\"" + "\"conflict\" | \"exactMatch\" | \"aliasMatch\"" ], "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", "deprecated": false, @@ -10850,6 +10850,14 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx" }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx" @@ -11184,11 +11192,15 @@ }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts" + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts" + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" }, { "plugin": "dashboard", @@ -11232,43 +11244,35 @@ }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "ml", @@ -11278,6 +11282,14 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-server-internal", "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts" @@ -15356,9 +15368,9 @@ "label": "SavedObjectsFindOptions", "description": [], "signature": [ - "{ type: string | string[]; filter?: any; search?: string | undefined; aggs?: Record | undefined; fields?: string[] | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; searchFields?: string[] | undefined; hasReference?: ", + "> | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; searchFields?: string[] | undefined; hasReference?: ", "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", @@ -21534,13 +21546,7 @@ "signature": [ "HttpServicePreboot", "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ">" ], "path": "src/core/server/index.ts", @@ -21575,7 +21581,10 @@ "description": [ "\nThe `core` context provided to route handler.\n\nProvides the following clients and services:\n - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client\n which uses the credentials of the incoming request\n - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing\n all the registered types.\n - {@link IScopedClusterClient | elasticsearch.client} - Elasticsearch\n data client which uses the credentials of the incoming request\n - {@link IUiSettingsClient | uiSettings.client} - uiSettings client\n which uses the credentials of the incoming request" ], - "path": "src/core/server/core_route_handler_context.ts", + "signature": [ + "CoreRequestHandlerContext" + ], + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -21589,7 +21598,7 @@ "signature": [ "SavedObjectsRequestHandlerContext" ], - "path": "src/core/server/core_route_handler_context.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21603,7 +21612,7 @@ "signature": [ "ElasticsearchRequestHandlerContext" ], - "path": "src/core/server/core_route_handler_context.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21617,7 +21626,7 @@ "signature": [ "UiSettingsRequestHandlerContext" ], - "path": "src/core/server/core_route_handler_context.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21631,7 +21640,7 @@ "signature": [ "DeprecationsRequestHandlerContext" ], - "path": "src/core/server/core_route_handler_context.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -21802,13 +21811,7 @@ "signature": [ "HttpServiceSetup", "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", "> & { resources: ", { "pluginId": "core", @@ -25950,21 +25953,9 @@ ], "signature": [ "(route: ", "RouteConfig", ", handler: ", @@ -39420,6 +39411,37 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.PrebootCoreRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "PrebootCoreRequestHandlerContext", + "description": [], + "signature": [ + "PrebootCoreRequestHandlerContext" + ], + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.PrebootCoreRequestHandlerContext.uiSettings", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [], + "signature": [ + "PrebootUiSettingsRequestHandlerContext" + ], + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.PrebootPlugin", @@ -39523,6 +39545,41 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.PrebootRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "PrebootRequestHandlerContext", + "description": [], + "signature": [ + "PrebootRequestHandlerContext", + " extends ", + "RequestHandlerContextBase" + ], + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.PrebootRequestHandlerContext.core", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "Promise<", + "PrebootCoreRequestHandlerContext", + ">" + ], + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.RegisterDeprecationsConfig", @@ -39588,17 +39645,11 @@ "\nBase context passed to a route handler, containing the `core` context part.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " extends ", "RequestHandlerContextBase" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -39611,16 +39662,10 @@ "description": [], "signature": [ "Promise<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreRequestHandlerContext", - "text": "CoreRequestHandlerContext" - }, + "CoreRequestHandlerContext", ">" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -40487,6 +40532,14 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx" }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx" @@ -40821,11 +40874,15 @@ }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts" + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts" + "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" }, { "plugin": "dashboard", @@ -40869,43 +40926,35 @@ }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/common/saved_dashboard_references.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, { "plugin": "ml", @@ -40915,6 +40964,14 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-server-internal", "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts" @@ -45734,11 +45791,11 @@ }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/migrations_730.ts" + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts" }, { "plugin": "dashboard", - "path": "src/plugins/dashboard/server/saved_objects/migrations_730.ts" + "path": "src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts" }, { "plugin": "@kbn/core-saved-objects-migration-server-internal", @@ -46719,7 +46776,7 @@ "\nThe outcome for a successful `resolve` call is one of the following values:\n\n * `'exactMatch'` -- One document exactly matched the given ID.\n * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different\n than the given ID.\n * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the\n `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID." ], "signature": [ - "\"exactMatch\" | \"aliasMatch\" | \"conflict\"" + "\"conflict\" | \"exactMatch\" | \"aliasMatch\"" ], "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", "deprecated": false, @@ -50176,16 +50233,10 @@ "\nMixin allowing plugins to define their own request handler contexts.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; }" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-http-request-handler-context-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -53662,9 +53713,9 @@ "label": "SavedObjectsCreatePointInTimeFinderOptions", "description": [], "signature": [ - "{ type: string | string[]; filter?: any; search?: string | undefined; aggs?: Record | undefined; fields?: string[] | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", + "> | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", "SortOrder", " | undefined; searchFields?: string[] | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", "SavedObjectsFindOptionsReference", diff --git a/api_docs/core.mdx b/api_docs/core.mdx index d554613d1ec1e..798c6c53ce345 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2684 | 0 | 35 | 0 | +| 2688 | 0 | 30 | 0 | ## Client diff --git a/api_docs/custom_integrations.devdocs.json b/api_docs/custom_integrations.devdocs.json index 7a339106c7a6d..03090384a0559 100644 --- a/api_docs/custom_integrations.devdocs.json +++ b/api_docs/custom_integrations.devdocs.json @@ -438,7 +438,7 @@ "label": "categories", "description": [], "signature": [ - "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" + "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -488,7 +488,7 @@ "\nA category applicable to an Integration." ], "signature": [ - "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" + "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -728,7 +728,7 @@ "label": "categories", "description": [], "signature": [ - "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" + "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -838,7 +838,7 @@ "label": "id", "description": [], "signature": [ - "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" + "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -860,7 +860,7 @@ "\nThe list of all available categories." ], "signature": [ - "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" + "(\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\")[]" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -877,7 +877,7 @@ "\nA category applicable to an Integration." ], "signature": [ - "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" + "\"custom\" | \"enterprise_search\" | \"sample_data\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"microsoft_365\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\" | \"communications\" | \"file_storage\" | \"language_client\" | \"upload_file\" | \"website_search\" | \"geo\"" ], "path": "src/plugins/custom_integrations/common/index.ts", "deprecated": false, @@ -1097,6 +1097,17 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "customIntegrations", + "id": "def-common.INTEGRATION_CATEGORY_DISPLAY.infrastructure", + "type": "string", + "tags": [], + "label": "infrastructure", + "description": [], + "path": "src/plugins/custom_integrations/common/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "customIntegrations", "id": "def-common.INTEGRATION_CATEGORY_DISPLAY.kubernetes", diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index b25052db9516d..77c928f34e584 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 103 | 0 | 84 | 1 | +| 104 | 0 | 85 | 1 | ## Client diff --git a/api_docs/dashboard.devdocs.json b/api_docs/dashboard.devdocs.json index 447c0d0af07ee..c185507fd9101 100644 --- a/api_docs/dashboard.devdocs.json +++ b/api_docs/dashboard.devdocs.json @@ -1,33 +1,99 @@ { "id": "dashboard", "client": { - "classes": [ + "classes": [], + "functions": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer", - "type": "Class", + "id": "def-public.cleanEmptyKeys", + "type": "Function", "tags": [], - "label": "DashboardContainer", + "label": "cleanEmptyKeys", "description": [], "signature": [ + "(stateObj: Record) => Record" + ], + "path": "src/plugins/dashboard/public/locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainer", - "text": "DashboardContainer" - }, - " extends ", + "parentPluginId": "dashboard", + "id": "def-public.cleanEmptyKeys.$1", + "type": "Object", + "tags": [], + "label": "stateObj", + "description": [], + "signature": [ + "Record" + ], + "path": "src/plugins/dashboard/public/locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.createDashboardEditUrl", + "type": "Function", + "tags": [], + "label": "createDashboardEditUrl", + "description": [], + "signature": [ + "(id: string | undefined, editMode: boolean | undefined) => string" + ], + "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.Container", - "text": "Container" + "parentPluginId": "dashboard", + "id": "def-public.createDashboardEditUrl.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false }, - "<", - "InheritedChildInput", - ", ", + { + "parentPluginId": "dashboard", + "id": "def-public.createDashboardEditUrl.$2", + "type": "CompoundType", + "tags": [], + "label": "editMode", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardContainerInput", + "type": "Interface", + "tags": [], + "label": "DashboardContainerInput", + "description": [], + "signature": [ { "pluginId": "dashboard", "scope": "public", @@ -35,1674 +101,1479 @@ "section": "def-public.DashboardContainerInput", "text": "DashboardContainerInput" }, - ", ", + " extends ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" + "section": "def-public.ContainerInput", + "text": "ContainerInput" }, - ">" + "<{}>" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "\"dashboard\"" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.controlGroup", + "id": "def-public.DashboardContainerInput.controlGroupInput", "type": "Object", "tags": [], - "label": "controlGroup", + "label": "controlGroupInput", "description": [], "signature": [ { "pluginId": "controls", - "scope": "public", + "scope": "common", "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupContainer", - "text": "ControlGroupContainer" + "section": "def-common.PersistableControlGroupInput", + "text": "PersistableControlGroupInput" }, " | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.getAllDataViews", - "type": "Function", + "id": "def-public.DashboardContainerInput.refreshConfig", + "type": "Object", "tags": [], - "label": "getAllDataViews", - "description": [ - "\nGets all the dataviews that are actively being used in the dashboard" - ], + "label": "refreshConfig", + "description": [], "signature": [ - "() => ", { - "pluginId": "dataViews", + "pluginId": "data", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibDataQueryPluginApi", + "section": "def-common.RefreshInterval", + "text": "RefreshInterval" }, - "[]" + " | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [ - "An array of dataviews" - ] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.setAllDataViews", - "type": "Function", + "id": "def-public.DashboardContainerInput.isEmbeddedExternally", + "type": "CompoundType", "tags": [], - "label": "setAllDataViews", - "description": [ - "\nUse this to set the dataviews that are used in the dashboard when they change/update" - ], + "label": "isEmbeddedExternally", + "description": [], "signature": [ - "(newDataViews: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - "[]) => void" + "boolean | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.setAllDataViews.$1", - "type": "Array", - "tags": [], - "label": "newDataViews", - "description": [ - "The new array of dataviews that will overwrite the old dataviews array" - ], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - "[]" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.getPanelCount", - "type": "Function", + "id": "def-public.DashboardContainerInput.isFullScreenMode", + "type": "boolean", "tags": [], - "label": "getPanelCount", + "label": "isFullScreenMode", "description": [], - "signature": [ - "() => number" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.getPanelTitles", - "type": "Function", + "id": "def-public.DashboardContainerInput.expandedPanelId", + "type": "string", "tags": [], - "label": "getPanelTitles", + "label": "expandedPanelId", "description": [], "signature": [ - "() => Promise" + "string | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.Unnamed", - "type": "Function", + "id": "def-public.DashboardContainerInput.timeRange", + "type": "Object", "tags": [], - "label": "Constructor", + "label": "timeRange", "description": [], "signature": [ - "any" + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.Unnamed.$1", - "type": "Object", - "tags": [], - "label": "initialInput", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" - } - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.Unnamed.$2", - "type": "Object", - "tags": [], - "label": "parent", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.Container", - "text": "Container" - }, - "<{}, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerInput", - "text": "ContainerInput" - }, - "<{}>, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" - }, - "> | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.Unnamed.$3", - "type": "CompoundType", - "tags": [], - "label": "controlGroup", - "description": [], - "signature": [ - { - "pluginId": "controls", - "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupContainer", - "text": "ControlGroupContainer" - }, - " | ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ErrorEmbeddable", - "text": "ErrorEmbeddable" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.createNewPanelState", - "type": "Function", + "id": "def-public.DashboardContainerInput.timeslice", + "type": "Object", "tags": [], - "label": "createNewPanelState", + "label": "timeslice", "description": [], "signature": [ - ">(factory: ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableFactory", - "text": "EmbeddableFactory" - }, - ", partial?: Partial) => ", - "DashboardPanelState", - "" + "[number, number] | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.createNewPanelState.$1", - "type": "Object", - "tags": [], - "label": "factory", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableFactory", - "text": "EmbeddableFactory" - }, - "" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.createNewPanelState.$2", - "type": "Object", - "tags": [], - "label": "partial", - "description": [], - "signature": [ - "Partial" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.showPlaceholderUntil", - "type": "Function", + "id": "def-public.DashboardContainerInput.timeRestore", + "type": "boolean", "tags": [], - "label": "showPlaceholderUntil", + "label": "timeRestore", "description": [], - "signature": [ - "(newStateComplete: Promise>>, placementMethod?: ", - "PanelPlacementMethod", - " | undefined, placementArgs?: TPlacementMethodArgs | undefined) => void" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.showPlaceholderUntil.$1", - "type": "Object", - "tags": [], - "label": "newStateComplete", - "description": [], - "signature": [ - "Promise>>" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.showPlaceholderUntil.$2", - "type": "Function", - "tags": [], - "label": "placementMethod", - "description": [], - "signature": [ - "PanelPlacementMethod", - " | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.showPlaceholderUntil.$3", - "type": "Uncategorized", - "tags": [], - "label": "placementArgs", - "description": [], - "signature": [ - "TPlacementMethodArgs | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.replacePanel", - "type": "Function", + "id": "def-public.DashboardContainerInput.description", + "type": "string", "tags": [], - "label": "replacePanel", + "label": "description", "description": [], "signature": [ - "(previousPanelState: ", - "DashboardPanelState", - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ">, newPanelState: Partial<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.PanelState", - "text": "PanelState" - }, - "<{ id: string; }>>, generateNewId?: boolean | undefined) => void" + "string | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.replacePanel.$1", - "type": "Object", - "tags": [], - "label": "previousPanelState", - "description": [], - "signature": [ - "DashboardPanelState", - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ">" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.replacePanel.$2", - "type": "Object", - "tags": [], - "label": "newPanelState", - "description": [], - "signature": [ - "Partial<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.PanelState", - "text": "PanelState" - }, - "<{ id: string; }>>" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.replacePanel.$3", - "type": "CompoundType", - "tags": [], - "label": "generateNewId", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.addOrUpdateEmbeddable", - "type": "Function", + "id": "def-public.DashboardContainerInput.useMargins", + "type": "boolean", "tags": [], - "label": "addOrUpdateEmbeddable", + "label": "useMargins", "description": [], - "signature": [ - " = ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - ">(type: string, explicitInput: Partial, embeddableId?: string | undefined) => Promise" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.addOrUpdateEmbeddable.$1", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.addOrUpdateEmbeddable.$2", - "type": "Object", - "tags": [], - "label": "explicitInput", - "description": [], - "signature": [ - "Partial" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.addOrUpdateEmbeddable.$3", - "type": "string", - "tags": [], - "label": "embeddableId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.render", - "type": "Function", + "id": "def-public.DashboardContainerInput.syncColors", + "type": "CompoundType", "tags": [], - "label": "render", + "label": "syncColors", "description": [], "signature": [ - "(dom: HTMLElement) => void" + "boolean | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.render.$1", - "type": "Object", - "tags": [], - "label": "dom", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.destroy", - "type": "Function", + "id": "def-public.DashboardContainerInput.syncTooltips", + "type": "CompoundType", "tags": [], - "label": "destroy", + "label": "syncTooltips", "description": [], "signature": [ - "() => void" + "boolean | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.getInheritedInput", - "type": "Function", + "id": "def-public.DashboardContainerInput.viewMode", + "type": "Enum", "tags": [], - "label": "getInheritedInput", + "label": "viewMode", "description": [], "signature": [ - "(id: string) => ", - "InheritedChildInput" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainer.getInheritedInput.$1", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.ViewMode", + "text": "ViewMode" } ], - "returnComment": [] - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition", - "type": "Class", - "tags": [], - "label": "DashboardContainerFactoryDefinition", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerFactoryDefinition", - "text": "DashboardContainerFactoryDefinition" - }, - " implements ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableFactoryDefinition", - "text": "EmbeddableFactoryDefinition" - }, - "<", - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" - }, - ", ", - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainer", - "text": "DashboardContainer" + "path": "src/plugins/dashboard/public/types.ts", + "deprecated": false, + "trackAdoption": false }, - ", unknown>" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.isContainerType", - "type": "boolean", + "id": "def-public.DashboardContainerInput.filters", + "type": "Array", "tags": [], - "label": "isContainerType", + "label": "filters", "description": [], "signature": [ - "true" + "Filter", + "[]" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.type", + "id": "def-public.DashboardContainerInput.title", "type": "string", "tags": [], - "label": "type", + "label": "title", "description": [], - "signature": [ - "\"dashboard\"" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.inject", - "type": "Function", + "id": "def-public.DashboardContainerInput.query", + "type": "Object", "tags": [], - "label": "inject", + "label": "query", "description": [], "signature": [ - "(state: ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableStateWithType", - "text": "EmbeddableStateWithType" - }, - ", references: ", - "SavedObjectReference", - "[]) => ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableStateWithType", - "text": "EmbeddableStateWithType" - } + "{ query: string | { [key: string]: any; }; language: string; }" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.inject.$1", - "type": "Uncategorized", - "tags": [], - "label": "state", - "description": [], - "signature": [ - "P" - ], - "path": "src/plugins/kibana_utils/common/persistable_state/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.inject.$2", - "type": "Array", - "tags": [], - "label": "references", - "description": [], - "signature": [ - "SavedObjectReference", - "[]" - ], - "path": "src/plugins/kibana_utils/common/persistable_state/types.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.extract", - "type": "Function", + "id": "def-public.DashboardContainerInput.panels", + "type": "Object", "tags": [], - "label": "extract", + "label": "panels", "description": [], "signature": [ - "(state: ", + "{ [panelId: string]: ", { - "pluginId": "embeddable", + "pluginId": "dashboard", "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableStateWithType", - "text": "EmbeddableStateWithType" + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" }, - ") => { state: ", + "<", { "pluginId": "embeddable", "scope": "common", "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableStateWithType", - "text": "EmbeddableStateWithType" + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" }, - "; references: ", - "SavedObjectReference", - "[]; }" + " & { [k: string]: unknown; }>; }" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.extract.$1", - "type": "Uncategorized", - "tags": [], - "label": "state", - "description": [], - "signature": [ - "P" - ], - "path": "src/plugins/kibana_utils/common/persistable_state/types.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.Unnamed", - "type": "Function", + "id": "def-public.DashboardContainerInput.executionContext", + "type": "Object", "tags": [], - "label": "Constructor", + "label": "executionContext", "description": [], "signature": [ - "any" + "KibanaExecutionContext", + " | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.Unnamed.$1", - "type": "Object", - "tags": [], - "label": "persistableStateService", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddablePersistableStateService", - "text": "EmbeddablePersistableStateService" - } - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardFeatureFlagConfig", + "type": "Interface", + "tags": [], + "label": "DashboardFeatureFlagConfig", + "description": [], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardFeatureFlagConfig.allowByValueEmbeddables", + "type": "boolean", + "tags": [], + "label": "allowByValueEmbeddables", + "description": [], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.SavedDashboardPanel", + "type": "Interface", + "tags": [], + "label": "SavedDashboardPanel", + "description": [ + "\nA saved dashboard panel parsed directly from the Dashboard Attributes panels JSON" + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-public.SavedDashboardPanel.embeddableConfig", + "type": "Object", + "tags": [], + "label": "embeddableConfig", + "description": [], + "signature": [ + "{ [key: string]: ", + "Serializable", + "; }" ], - "returnComment": [] + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.isEditable", - "type": "Function", + "id": "def-public.SavedDashboardPanel.id", + "type": "string", "tags": [], - "label": "isEditable", + "label": "id", "description": [], "signature": [ - "() => Promise" + "string | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.SavedDashboardPanel.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.getDisplayName", - "type": "Function", + "id": "def-public.SavedDashboardPanel.panelRefName", + "type": "string", "tags": [], - "label": "getDisplayName", + "label": "panelRefName", "description": [], "signature": [ - "() => string" + "string | undefined" ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.getDefaultInput", - "type": "Function", + "id": "def-public.SavedDashboardPanel.gridData", + "type": "Object", "tags": [], - "label": "getDefaultInput", + "label": "gridData", "description": [], "signature": [ - "() => Partial<", { "pluginId": "dashboard", - "scope": "public", + "scope": "common", "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" - }, - ">" + "section": "def-common.GridData", + "text": "GridData" + } ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.create", - "type": "Function", + "id": "def-public.SavedDashboardPanel.panelIndex", + "type": "string", "tags": [], - "label": "create", + "label": "panelIndex", "description": [], - "signature": [ - "(initialInput: ", - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" - }, - ", parent?: ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.Container", - "text": "Container" - }, - "<{}, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerInput", - "text": "ContainerInput" - }, - "<{}>, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" - }, - "> | undefined) => Promise<", - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainer", - "text": "DashboardContainer" - }, - " | ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ErrorEmbeddable", - "text": "ErrorEmbeddable" - }, - ">" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.create.$1", - "type": "Object", - "tags": [], - "label": "initialInput", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" - } - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerFactoryDefinition.create.$2", - "type": "Object", - "tags": [], - "label": "parent", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.Container", - "text": "Container" - }, - "<{}, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerInput", - "text": "ContainerInput" - }, - "<{}>, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" - }, - "> | undefined" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.SavedDashboardPanel.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.SavedDashboardPanel.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "signature": [ + "string | undefined" ], - "returnComment": [] + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false } ], - "functions": [ + "enums": [], + "misc": [ { "parentPluginId": "dashboard", - "id": "def-public.cleanEmptyKeys", - "type": "Function", + "id": "def-public.DASHBOARD_CONTAINER_TYPE", + "type": "string", "tags": [], - "label": "cleanEmptyKeys", + "label": "DASHBOARD_CONTAINER_TYPE", "description": [], "signature": [ - "(stateObj: Record) => Record" + "\"dashboard\"" ], - "path": "src/plugins/dashboard/public/locator.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.cleanEmptyKeys.$1", - "type": "Object", - "tags": [], - "label": "stateObj", - "description": [], - "signature": [ - "Record" - ], - "path": "src/plugins/dashboard/public/locator.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "dashboard", - "id": "def-public.createDashboardEditUrl", - "type": "Function", + "id": "def-public.DashboardAppLocator", + "type": "Type", "tags": [], - "label": "createDashboardEditUrl", + "label": "DashboardAppLocator", "description": [], "signature": [ - "(id: string | undefined, editMode: boolean | undefined) => string" - ], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { - "parentPluginId": "dashboard", - "id": "def-public.createDashboardEditUrl.$1", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.LocatorPublic", + "text": "LocatorPublic" }, + "<", { - "parentPluginId": "dashboard", - "id": "def-public.createDashboardEditUrl.$2", - "type": "CompoundType", - "tags": [], - "label": "editMode", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } + "pluginId": "dashboard", + "scope": "public", + "docId": "kibDashboardPluginApi", + "section": "def-public.DashboardAppLocatorParams", + "text": "DashboardAppLocatorParams" + }, + ">" ], - "returnComment": [], + "path": "src/plugins/dashboard/public/locator.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false - } - ], - "interfaces": [ + }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput", - "type": "Interface", + "id": "def-public.DashboardAppLocatorParams", + "type": "Type", "tags": [], - "label": "DashboardContainerInput", - "description": [], + "label": "DashboardAppLocatorParams", + "description": [ + "\nWe use `type` instead of `interface` to avoid having to extend this type with\n`SerializableRecord`. See https://github.com/microsoft/TypeScript/issues/15300." + ], "signature": [ + "{ dashboardId?: string | undefined; timeRange?: ", + "TimeRange", + " | undefined; refreshInterval?: ", { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardContainerInput", - "text": "DashboardContainerInput" + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.RefreshInterval", + "text": "RefreshInterval" }, - " extends ", + " | undefined; filters?: ", + "Filter", + "[] | undefined; query?: ", + "Query", + " | undefined; useHash?: boolean | undefined; preserveSavedFilters?: boolean | undefined; viewMode?: ", { "pluginId": "embeddable", - "scope": "public", + "scope": "common", "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerInput", - "text": "ContainerInput" + "section": "def-common.ViewMode", + "text": "ViewMode" }, - "<{}>" + " | undefined; searchSessionId?: string | undefined; panels?: (", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" + }, + " & ", + "SerializableRecord", + ")[] | undefined; savedQuery?: string | undefined; tags?: string[] | undefined; options?: ", + "DashboardOptions", + " | undefined; controlGroupInput?: ", + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.SerializableControlGroupInput", + "text": "SerializableControlGroupInput" + }, + " | undefined; }" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/locator.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardConstants", + "type": "Object", + "tags": [], + "label": "DashboardConstants", + "description": [], + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.controlGroupInput", - "type": "Object", + "id": "def-public.DashboardConstants.LANDING_PAGE_PATH", + "type": "string", "tags": [], - "label": "controlGroupInput", + "label": "LANDING_PAGE_PATH", "description": [], - "signature": [ - { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.PersistableControlGroupInput", - "text": "PersistableControlGroupInput" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.refreshConfig", - "type": "Object", + "id": "def-public.DashboardConstants.CREATE_NEW_DASHBOARD_URL", + "type": "string", "tags": [], - "label": "refreshConfig", + "label": "CREATE_NEW_DASHBOARD_URL", "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.RefreshInterval", - "text": "RefreshInterval" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.isEmbeddedExternally", - "type": "CompoundType", + "id": "def-public.DashboardConstants.VIEW_DASHBOARD_URL", + "type": "string", "tags": [], - "label": "isEmbeddedExternally", + "label": "VIEW_DASHBOARD_URL", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.isFullScreenMode", - "type": "boolean", + "id": "def-public.DashboardConstants.PRINT_DASHBOARD_URL", + "type": "string", "tags": [], - "label": "isFullScreenMode", + "label": "PRINT_DASHBOARD_URL", "description": [], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.expandedPanelId", + "id": "def-public.DashboardConstants.ADD_EMBEDDABLE_ID", "type": "string", "tags": [], - "label": "expandedPanelId", + "label": "ADD_EMBEDDABLE_ID", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.timeRange", - "type": "Object", + "id": "def-public.DashboardConstants.ADD_EMBEDDABLE_TYPE", + "type": "string", "tags": [], - "label": "timeRange", + "label": "ADD_EMBEDDABLE_TYPE", "description": [], - "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.timeslice", - "type": "Object", + "id": "def-public.DashboardConstants.DASHBOARDS_ID", + "type": "string", "tags": [], - "label": "timeslice", + "label": "DASHBOARDS_ID", "description": [], - "signature": [ - "[number, number] | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.timeRestore", - "type": "boolean", + "id": "def-public.DashboardConstants.DASHBOARD_ID", + "type": "string", "tags": [], - "label": "timeRestore", + "label": "DASHBOARD_ID", "description": [], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.description", + "id": "def-public.DashboardConstants.DASHBOARD_SAVED_OBJECT_TYPE", "type": "string", "tags": [], - "label": "description", + "label": "DASHBOARD_SAVED_OBJECT_TYPE", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.useMargins", - "type": "boolean", + "id": "def-public.DashboardConstants.SEARCH_SESSION_ID", + "type": "string", "tags": [], - "label": "useMargins", + "label": "SEARCH_SESSION_ID", "description": [], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.syncColors", - "type": "CompoundType", + "id": "def-public.DashboardConstants.CHANGE_CHECK_DEBOUNCE", + "type": "number", "tags": [], - "label": "syncColors", + "label": "CHANGE_CHECK_DEBOUNCE", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.syncTooltips", - "type": "CompoundType", + "id": "def-public.DashboardConstants.CHANGE_APPLY_DEBOUNCE", + "type": "number", "tags": [], - "label": "syncTooltips", + "label": "CHANGE_APPLY_DEBOUNCE", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/public/dashboard_constants.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + } + ], + "setup": { + "parentPluginId": "dashboard", + "id": "def-public.DashboardSetup", + "type": "Interface", + "tags": [], + "label": "DashboardSetup", + "description": [], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardSetup.locator", + "type": "Object", + "tags": [], + "label": "locator", + "description": [], + "signature": [ + { + "pluginId": "dashboard", + "scope": "public", + "docId": "kibDashboardPluginApi", + "section": "def-public.DashboardAppLocator", + "text": "DashboardAppLocator" + }, + " | undefined" + ], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "dashboard", + "id": "def-public.DashboardStart", + "type": "Interface", + "tags": [], + "label": "DashboardStart", + "description": [], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardStart.getDashboardContainerByValueRenderer", + "type": "Function", + "tags": [], + "label": "getDashboardContainerByValueRenderer", + "description": [], + "signature": [ + "() => React.FC" + ], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardStart.locator", + "type": "Object", + "tags": [], + "label": "locator", + "description": [], + "signature": [ + { + "pluginId": "dashboard", + "scope": "public", + "docId": "kibDashboardPluginApi", + "section": "def-public.DashboardAppLocator", + "text": "DashboardAppLocator" + }, + " | undefined" + ], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-public.DashboardStart.dashboardFeatureFlagConfig", + "type": "Object", + "tags": [], + "label": "dashboardFeatureFlagConfig", + "description": [], + "signature": [ + { + "pluginId": "dashboard", + "scope": "public", + "docId": "kibDashboardPluginApi", + "section": "def-public.DashboardFeatureFlagConfig", + "text": "DashboardFeatureFlagConfig" + } + ], + "path": "src/plugins/dashboard/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "dashboard", + "id": "def-server.findByValueEmbeddables", + "type": "Function", + "tags": [], + "label": "findByValueEmbeddables", + "description": [], + "signature": [ + "(savedObjectClient: Pick<", + "ISavedObjectsRepository", + ", \"find\">, embeddableType: string) => Promise<{ [key: string]: ", + "Serializable", + "; }[]>" + ], + "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.viewMode", - "type": "Enum", + "id": "def-server.findByValueEmbeddables.$1", + "type": "Object", "tags": [], - "label": "viewMode", + "label": "savedObjectClient", "description": [], "signature": [ - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.ViewMode", - "text": "ViewMode" - } + "Pick<", + "ISavedObjectsRepository", + ", \"find\">" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.filters", - "type": "Array", + "id": "def-server.findByValueEmbeddables.$2", + "type": "string", "tags": [], - "label": "filters", + "label": "embeddableType", "description": [], "signature": [ - "Filter", - "[]" + "string" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "dashboard", + "id": "def-server.DashboardPluginSetup", + "type": "Interface", + "tags": [], + "label": "DashboardPluginSetup", + "description": [], + "path": "src/plugins/dashboard/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "dashboard", + "id": "def-server.DashboardPluginStart", + "type": "Interface", + "tags": [], + "label": "DashboardPluginStart", + "description": [], + "path": "src/plugins/dashboard/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "dashboard", + "id": "def-common.convertPanelMapToSavedPanels", + "type": "Function", + "tags": [], + "label": "convertPanelMapToSavedPanels", + "description": [], + "signature": [ + "(panels: ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelMap", + "text": "DashboardPanelMap" + }, + ", version: string) => ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" }, + "[]" + ], + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.title", - "type": "string", + "id": "def-common.convertPanelMapToSavedPanels.$1", + "type": "Object", "tags": [], - "label": "title", + "label": "panels", "description": [], - "path": "src/plugins/dashboard/public/types.ts", + "signature": [ + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelMap", + "text": "DashboardPanelMap" + } + ], + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.query", - "type": "Object", + "id": "def-common.convertPanelMapToSavedPanels.$2", + "type": "string", "tags": [], - "label": "query", + "label": "version", "description": [], "signature": [ - "{ query: string | { [key: string]: any; }; language: string; }" + "string" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.convertPanelStateToSavedDashboardPanel", + "type": "Function", + "tags": [], + "label": "convertPanelStateToSavedDashboardPanel", + "description": [], + "signature": [ + "(panelState: ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" + }, + "<", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.SavedObjectEmbeddableInput", + "text": "SavedObjectEmbeddableInput" }, + ">, version: string) => ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" + } + ], + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.panels", + "id": "def-common.convertPanelStateToSavedDashboardPanel.$1", "type": "Object", "tags": [], - "label": "panels", + "label": "panelState", "description": [], "signature": [ - "{ [panelId: string]: ", - "DashboardPanelState", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" + }, "<", { "pluginId": "embeddable", "scope": "common", "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" + "section": "def-common.SavedObjectEmbeddableInput", + "text": "SavedObjectEmbeddableInput" }, - " & { [k: string]: unknown; }>; }" + ">" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardContainerInput.executionContext", - "type": "Object", + "id": "def-common.convertPanelStateToSavedDashboardPanel.$2", + "type": "string", "tags": [], - "label": "executionContext", + "label": "version", "description": [], "signature": [ - "KibanaExecutionContext", - " | undefined" + "string" ], - "path": "src/plugins/dashboard/public/types.ts", + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardFeatureFlagConfig", - "type": "Interface", + "id": "def-common.convertSavedDashboardPanelToPanelState", + "type": "Function", "tags": [], - "label": "DashboardFeatureFlagConfig", + "label": "convertSavedDashboardPanelToPanelState", "description": [], - "path": "src/plugins/dashboard/public/plugin.tsx", + "signature": [ + "(savedDashboardPanel: ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" + }, + ") => ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" + }, + "" + ], + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardFeatureFlagConfig.allowByValueEmbeddables", - "type": "boolean", + "id": "def-common.convertSavedDashboardPanelToPanelState.$1", + "type": "Object", "tags": [], - "label": "allowByValueEmbeddables", + "label": "savedDashboardPanel", "description": [], - "path": "src/plugins/dashboard/public/plugin.tsx", + "signature": [ + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" + } + ], + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject", - "type": "Interface", + "id": "def-common.convertSavedPanelsToPanelMap", + "type": "Function", "tags": [], - "label": "DashboardSavedObject", + "label": "convertSavedPanelsToPanelMap", "description": [], "signature": [ + "(panels?: ", { "pluginId": "dashboard", - "scope": "public", + "scope": "common", "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardSavedObject", - "text": "DashboardSavedObject" + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" }, - " extends ", + "[] | undefined) => ", { - "pluginId": "savedObjects", - "scope": "public", - "docId": "kibSavedObjectsPluginApi", - "section": "def-public.SavedObject", - "text": "SavedObject" - }, - "<", - "SavedObjectAttributes", - ">" + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelMap", + "text": "DashboardPanelMap" + } ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.id", - "type": "string", + "id": "def-common.convertSavedPanelsToPanelMap.$1", + "type": "Array", "tags": [], - "label": "id", + "label": "panels", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.SavedDashboardPanel", + "text": "SavedDashboardPanel" + }, + "[] | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.createExtract", + "type": "Function", + "tags": [], + "label": "createExtract", + "description": [], + "signature": [ + "(persistableStateService: ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddablePersistableStateService", + "text": "EmbeddablePersistableStateService" }, + ") => (state: ", { - "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.timeRestore", - "type": "boolean", - "tags": [], - "label": "timeRestore", - "description": [], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableStateWithType", + "text": "EmbeddableStateWithType" + }, + ") => { state: ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableStateWithType", + "text": "EmbeddableStateWithType" }, + "; references: ", + "SavedObjectReference", + "[]; }" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.timeTo", - "type": "string", + "id": "def-common.createExtract.$1", + "type": "Object", "tags": [], - "label": "timeTo", + "label": "persistableStateService", "description": [], "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", - "deprecated": false, - "trackAdoption": false - }, + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddablePersistableStateService", + "text": "EmbeddablePersistableStateService" + } + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.createInject", + "type": "Function", + "tags": [], + "label": "createInject", + "description": [], + "signature": [ + "(persistableStateService: ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddablePersistableStateService", + "text": "EmbeddablePersistableStateService" + }, + ") => (state: ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableStateWithType", + "text": "EmbeddableStateWithType" + }, + ", references: ", + "SavedObjectReference", + "[]) => ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableStateWithType", + "text": "EmbeddableStateWithType" + } + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.timeFrom", - "type": "string", + "id": "def-common.createInject.$1", + "type": "Object", "tags": [], - "label": "timeFrom", + "label": "persistableStateService", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddablePersistableStateService", + "text": "EmbeddablePersistableStateService" + } + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_container_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.extractReferences", + "type": "Function", + "tags": [], + "label": "extractReferences", + "description": [], + "signature": [ + "({ attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ", + "ExtractDeps", + ") => SavedObjectAttributesAndReferences" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-common.extractReferences.$1", + "type": "Object", + "tags": [], + "label": "{ attributes, references = [] }", + "description": [], + "signature": [ + "SavedObjectAttributesAndReferences" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dashboard", + "id": "def-common.extractReferences.$2", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "ExtractDeps" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.injectReferences", + "type": "Function", + "tags": [], + "label": "injectReferences", + "description": [], + "signature": [ + "({ attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ", + "InjectDeps", + ") => ", + "SavedObjectAttributes" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-common.injectReferences.$1", + "type": "Object", + "tags": [], + "label": "{ attributes, references = [] }", + "description": [], + "signature": [ + "SavedObjectAttributesAndReferences" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dashboard", + "id": "def-common.injectReferences.$2", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "InjectDeps" + ], + "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes", + "type": "Interface", + "tags": [], + "label": "DashboardAttributes", + "description": [ + "\nThe attributes of the dashboard saved object. This interface should be the\nsource of truth for the latest dashboard attributes shape after all migrations." + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes.controlGroupInput", + "type": "CompoundType", + "tags": [], + "label": "controlGroupInput", + "description": [], + "signature": [ + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.RawControlGroupAttributes", + "text": "RawControlGroupAttributes" + }, + " | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.description", - "type": "string", + "id": "def-common.DashboardAttributes.refreshInterval", + "type": "Object", "tags": [], - "label": "description", + "label": "refreshInterval", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.RefreshInterval", + "text": "RefreshInterval" + }, + " | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.panelsJSON", - "type": "string", + "id": "def-common.DashboardAttributes.timeRestore", + "type": "boolean", "tags": [], - "label": "panelsJSON", + "label": "timeRestore", "description": [], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.optionsJSON", + "id": "def-common.DashboardAttributes.optionsJSON", "type": "string", "tags": [], "label": "optionsJSON", @@ -1710,687 +1581,501 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes.useMargins", + "type": "CompoundType", + "tags": [], + "label": "useMargins", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes.panelsJSON", + "type": "string", + "tags": [], + "label": "panelsJSON", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.uiStateJSON", + "id": "def-common.DashboardAttributes.timeFrom", "type": "string", "tags": [], - "label": "uiStateJSON", + "label": "timeFrom", "description": [], "signature": [ "string | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.lastSavedTitle", - "type": "string", + "id": "def-common.DashboardAttributes.version", + "type": "number", "tags": [], - "label": "lastSavedTitle", + "label": "version", "description": [], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.refreshInterval", - "type": "Object", + "id": "def-common.DashboardAttributes.timeTo", + "type": "string", "tags": [], - "label": "refreshInterval", + "label": "timeTo", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.RefreshInterval", - "text": "RefreshInterval" - }, - " | undefined" + "string | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardAttributes.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.searchSource", + "id": "def-common.DashboardAttributes.kibanaSavedObjectMeta", "type": "Object", "tags": [], - "label": "searchSource", + "label": "kibanaSavedObjectMeta", "description": [], "signature": [ - "{ create: () => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; history: ", - "SearchRequest", - "[]; setOverwriteDataViewType: (overwriteType: string | false | undefined) => void; setField: (field: K, value: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceFields", - "text": "SearchSourceFields" - }, - "[K]) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; removeField: (field: K) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; setFields: (newFields: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceFields", - "text": "SearchSourceFields" - }, - ") => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; getId: () => string; getFields: () => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceFields", - "text": "SearchSourceFields" - }, - "; getField: (field: K, recurse?: boolean) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceFields", - "text": "SearchSourceFields" - }, - "[K]; getActiveIndexFilter: () => string[]; getOwnField: (field: K) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceFields", - "text": "SearchSourceFields" - }, - "[K]; createCopy: () => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; createChild: (options?: {}) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; setParent: (parent?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ISearchSource", - "text": "ISearchSource" - }, - " | undefined, options?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceOptions", - "text": "SearchSourceOptions" - }, - ") => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - "; getParent: () => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - " | undefined; fetch$: (options?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceSearchOptions", - "text": "SearchSourceSearchOptions" - }, - ") => ", - "Observable", - "<", + "{ searchSourceJSON: string; }" + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardContainerStateWithType", + "type": "Interface", + "tags": [], + "label": "DashboardContainerStateWithType", + "description": [ + "--------------------------------------------------------------------\nDashboard container types\n -----------------------------------------------------------------------\n\nTypes below this line are copied here because so many important types are tied up in public. These types should be\nmoved from public into common." + ], + "signature": [ + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardContainerStateWithType", + "text": "DashboardContainerStateWithType" + }, + " extends ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableStateWithType", + "text": "EmbeddableStateWithType" + } + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardContainerStateWithType.panels", + "type": "Object", + "tags": [], + "label": "panels", + "description": [], + "signature": [ + "{ [panelId: string]: ", { - "pluginId": "data", + "pluginId": "dashboard", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IKibanaSearchResponse", - "text": "IKibanaSearchResponse" + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" }, "<", - "SearchResponse", - ">>>; fetch: (options?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceSearchOptions", - "text": "SearchSourceSearchOptions" - }, - ") => Promise<", - "SearchResponse", - ">>; onRequestStart: (handler: (searchSource: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSource", - "text": "SearchSource" - }, - ", options?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceSearchOptions", - "text": "SearchSourceSearchOptions" - }, - " | undefined) => Promise) => void; getSearchRequestBody: () => any; destroy: () => void; getSerializedFields: (recurse?: boolean) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SerializedSearchSourceFields", - "text": "SerializedSearchSourceFields" - }, - "; serialize: () => { searchSourceJSON: string; references: ", - "SavedObjectReference", - "[]; }; toExpressionAst: ({ asDatatable }?: ExpressionAstOptions) => ", { - "pluginId": "expressions", + "pluginId": "embeddable", "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionAstExpression", - "text": "ExpressionAstExpression" + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" }, - "; parseActiveIndexPatternFromQueryString: (queryString: string) => string[]; }" + " & { [k: string]: unknown; }>; }" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.getQuery", - "type": "Function", + "id": "def-common.DashboardContainerStateWithType.controlGroupInput", + "type": "Object", "tags": [], - "label": "getQuery", + "label": "controlGroupInput", "description": [], "signature": [ - "() => ", - "Query" + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.PersistableControlGroupInput", + "text": "PersistableControlGroupInput" + }, + " | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardPanelMap", + "type": "Interface", + "tags": [], + "label": "DashboardPanelMap", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.getFilters", - "type": "Function", + "id": "def-common.DashboardPanelMap.Unnamed", + "type": "IndexSignature", "tags": [], - "label": "getFilters", + "label": "[key: string]: DashboardPanelState", "description": [], "signature": [ - "() => ", - "Filter", - "[]" + "[key: string]: ", + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" + }, + "<", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.SavedObjectEmbeddableInput", + "text": "SavedObjectEmbeddableInput" + }, + ">" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.DashboardPanelState", + "type": "Interface", + "tags": [], + "label": "DashboardPanelState", + "description": [ + "--------------------------------------------------------------------\nDashboard panel types\n -----------------------------------------------------------------------\n\nThe dashboard panel format expected by the embeddable container." + ], + "signature": [ + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardPanelState", + "text": "DashboardPanelState" }, + " extends ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.PanelState", + "text": "PanelState" + }, + "" + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.getFullEditPath", - "type": "Function", + "id": "def-common.DashboardPanelState.gridData", + "type": "Object", "tags": [], - "label": "getFullEditPath", + "label": "gridData", "description": [], "signature": [ - "(editMode?: boolean | undefined) => string" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { - "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.getFullEditPath.$1", - "type": "CompoundType", - "tags": [], - "label": "editMode", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.GridData", + "text": "GridData" } ], - "returnComment": [] + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.outcome", - "type": "CompoundType", + "id": "def-common.DashboardPanelState.panelRefName", + "type": "string", "tags": [], - "label": "outcome", + "label": "panelRefName", "description": [], "signature": [ - "\"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined" + "string | undefined" ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dashboard", + "id": "def-common.GridData", + "type": "Interface", + "tags": [], + "label": "GridData", + "description": [ + "\nGrid type for React Grid Layout" + ], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.aliasId", - "type": "string", + "id": "def-common.GridData.w", + "type": "number", "tags": [], - "label": "aliasId", + "label": "w", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.aliasPurpose", - "type": "CompoundType", + "id": "def-common.GridData.h", + "type": "number", "tags": [], - "label": "aliasPurpose", + "label": "h", "description": [], - "signature": [ - "\"savedObjectConversion\" | \"savedObjectImport\" | undefined" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardSavedObject.controlGroupInput", - "type": "Object", + "id": "def-common.GridData.x", + "type": "number", "tags": [], - "label": "controlGroupInput", + "label": "x", "description": [], - "signature": [ - "Omit<", - { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.RawControlGroupAttributes", - "text": "RawControlGroupAttributes" - }, - ", \"id\"> | undefined" - ], - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - } - ], - "enums": [], - "misc": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DASHBOARD_CONTAINER_TYPE", - "type": "string", - "tags": [], - "label": "DASHBOARD_CONTAINER_TYPE", - "description": [], - "signature": [ - "\"dashboard\"" - ], - "path": "src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardAppLocator", - "type": "Type", - "tags": [], - "label": "DashboardAppLocator", - "description": [], - "signature": [ - { - "pluginId": "share", - "scope": "common", - "docId": "kibSharePluginApi", - "section": "def-common.LocatorPublic", - "text": "LocatorPublic" - }, - "<", - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardAppLocatorParams", - "text": "DashboardAppLocatorParams" - }, - ">" - ], - "path": "src/plugins/dashboard/public/locator.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardAppLocatorParams", - "type": "Type", - "tags": [], - "label": "DashboardAppLocatorParams", - "description": [ - "\nWe use `type` instead of `interface` to avoid having to extend this type with\n`SerializableRecord`. See https://github.com/microsoft/TypeScript/issues/15300." - ], - "signature": [ - "{ dashboardId?: string | undefined; timeRange?: ", - "TimeRange", - " | undefined; refreshInterval?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.RefreshInterval", - "text": "RefreshInterval" - }, - " | undefined; filters?: ", - "Filter", - "[] | undefined; query?: ", - "Query", - " | undefined; useHash?: boolean | undefined; preserveSavedFilters?: boolean | undefined; viewMode?: ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.ViewMode", - "text": "ViewMode" }, - " | undefined; searchSessionId?: string | undefined; panels?: ", { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel730ToLatest", - "text": "SavedDashboardPanel730ToLatest" + "parentPluginId": "dashboard", + "id": "def-common.GridData.y", + "type": "number", + "tags": [], + "label": "y", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false }, - "[] | undefined; savedQuery?: string | undefined; tags?: string[] | undefined; options?: ", - "DashboardOptions", - " | undefined; controlGroupInput?: ", { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.SerializableControlGroupInput", - "text": "SerializableControlGroupInput" - }, - " | undefined; }" + "parentPluginId": "dashboard", + "id": "def-common.GridData.i", + "type": "string", + "tags": [], + "label": "i", + "description": [], + "path": "src/plugins/dashboard/common/types.ts", + "deprecated": false, + "trackAdoption": false + } ], - "path": "src/plugins/dashboard/public/locator.ts", - "deprecated": false, - "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "dashboard", - "id": "def-public.SavedDashboardPanel", - "type": "Type", + "id": "def-common.SavedDashboardPanel", + "type": "Interface", "tags": [], "label": "SavedDashboardPanel", "description": [ - "\nThis should always represent the latest dashboard panel shape, after all possible migrations." - ], - "signature": [ - "Pick<", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.RawSavedDashboardPanel730ToLatest", - "text": "RawSavedDashboardPanel730ToLatest" - }, - ", \"type\" | \"title\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\" | \"panelRefName\"> & { readonly id?: string | undefined; readonly type: string; }" + "\nA saved dashboard panel parsed directly from the Dashboard Attributes panels JSON" ], "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants", - "type": "Object", - "tags": [], - "label": "DashboardConstants", - "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false, "children": [ { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.LANDING_PAGE_PATH", - "type": "string", - "tags": [], - "label": "LANDING_PAGE_PATH", - "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.CREATE_NEW_DASHBOARD_URL", - "type": "string", + "id": "def-common.SavedDashboardPanel.embeddableConfig", + "type": "Object", "tags": [], - "label": "CREATE_NEW_DASHBOARD_URL", + "label": "embeddableConfig", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "signature": [ + "{ [key: string]: ", + "Serializable", + "; }" + ], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.VIEW_DASHBOARD_URL", + "id": "def-common.SavedDashboardPanel.id", "type": "string", "tags": [], - "label": "VIEW_DASHBOARD_URL", + "label": "id", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.PRINT_DASHBOARD_URL", + "id": "def-common.SavedDashboardPanel.type", "type": "string", "tags": [], - "label": "PRINT_DASHBOARD_URL", + "label": "type", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.ADD_EMBEDDABLE_ID", + "id": "def-common.SavedDashboardPanel.panelRefName", "type": "string", "tags": [], - "label": "ADD_EMBEDDABLE_ID", + "label": "panelRefName", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.ADD_EMBEDDABLE_TYPE", - "type": "string", + "id": "def-common.SavedDashboardPanel.gridData", + "type": "Object", "tags": [], - "label": "ADD_EMBEDDABLE_TYPE", + "label": "gridData", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "signature": [ + { + "pluginId": "dashboard", + "scope": "common", + "docId": "kibDashboardPluginApi", + "section": "def-common.GridData", + "text": "GridData" + } + ], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.DASHBOARDS_ID", + "id": "def-common.SavedDashboardPanel.panelIndex", "type": "string", "tags": [], - "label": "DASHBOARDS_ID", + "label": "panelIndex", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.DASHBOARD_ID", + "id": "def-common.SavedDashboardPanel.version", "type": "string", "tags": [], - "label": "DASHBOARD_ID", + "label": "version", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.SEARCH_SESSION_ID", + "id": "def-common.SavedDashboardPanel.title", "type": "string", "tags": [], - "label": "SEARCH_SESSION_ID", - "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.CHANGE_CHECK_DEBOUNCE", - "type": "number", - "tags": [], - "label": "CHANGE_CHECK_DEBOUNCE", - "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardConstants.CHANGE_APPLY_DEBOUNCE", - "type": "number", - "tags": [], - "label": "CHANGE_APPLY_DEBOUNCE", + "label": "title", "description": [], - "path": "src/plugins/dashboard/public/dashboard_constants.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/dashboard/common/types.ts", "deprecated": false, "trackAdoption": false } @@ -2398,674 +2083,8 @@ "initialIsOpen": false } ], - "setup": { - "parentPluginId": "dashboard", - "id": "def-public.DashboardSetup", - "type": "Interface", - "tags": [], - "label": "DashboardSetup", - "description": [], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardSetup.locator", - "type": "Object", - "tags": [], - "label": "locator", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardAppLocator", - "text": "DashboardAppLocator" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "lifecycle": "setup", - "initialIsOpen": true - }, - "start": { - "parentPluginId": "dashboard", - "id": "def-public.DashboardStart", - "type": "Interface", - "tags": [], - "label": "DashboardStart", - "description": [], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardStart.getSavedDashboardLoader", - "type": "Function", - "tags": [], - "label": "getSavedDashboardLoader", - "description": [], - "signature": [ - "() => ", - "SavedObjectLoader" - ], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardStart.getDashboardContainerByValueRenderer", - "type": "Function", - "tags": [], - "label": "getDashboardContainerByValueRenderer", - "description": [], - "signature": [ - "() => React.FC" - ], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardStart.locator", - "type": "Object", - "tags": [], - "label": "locator", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardAppLocator", - "text": "DashboardAppLocator" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-public.DashboardStart.dashboardFeatureFlagConfig", - "type": "Object", - "tags": [], - "label": "dashboardFeatureFlagConfig", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "public", - "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardFeatureFlagConfig", - "text": "DashboardFeatureFlagConfig" - } - ], - "path": "src/plugins/dashboard/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "server": { - "classes": [], - "functions": [ - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables", - "type": "Function", - "tags": [], - "label": "findByValueEmbeddables", - "description": [], - "signature": [ - "(savedObjectClient: Pick<", - "ISavedObjectsRepository", - ", \"find\">, embeddableType: string) => Promise<{ [key: string]: ", - "Serializable", - "; }[]>" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables.$1", - "type": "Object", - "tags": [], - "label": "savedObjectClient", - "description": [], - "signature": [ - "Pick<", - "ISavedObjectsRepository", - ", \"find\">" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables.$2", - "type": "string", - "tags": [], - "label": "embeddableType", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], - "interfaces": [], "enums": [], "misc": [], - "objects": [], - "setup": { - "parentPluginId": "dashboard", - "id": "def-server.DashboardPluginSetup", - "type": "Interface", - "tags": [], - "label": "DashboardPluginSetup", - "description": [], - "path": "src/plugins/dashboard/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "lifecycle": "setup", - "initialIsOpen": true - }, - "start": { - "parentPluginId": "dashboard", - "id": "def-server.DashboardPluginStart", - "type": "Interface", - "tags": [], - "label": "DashboardPluginStart", - "description": [], - "path": "src/plugins/dashboard/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "common": { - "classes": [], - "functions": [ - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730", - "type": "Function", - "tags": [], - "label": "migratePanelsTo730", - "description": [], - "signature": [ - "(panels: (", - "RawSavedDashboardPanelTo60", - " | ", - "RawSavedDashboardPanel610", - " | ", - "RawSavedDashboardPanel620", - " | ", - "RawSavedDashboardPanel640To720", - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanelTo60", - "text": "SavedDashboardPanelTo60" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel610", - "text": "SavedDashboardPanel610" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel620", - "text": "SavedDashboardPanel620" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel630", - "text": "SavedDashboardPanel630" - }, - ")[], version: string, useMargins: boolean, uiState: { [key: string]: ", - "SerializableRecord", - "; } | undefined) => ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.RawSavedDashboardPanel730ToLatest", - "text": "RawSavedDashboardPanel730ToLatest" - }, - "[]" - ], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730.$1", - "type": "Array", - "tags": [], - "label": "panels", - "description": [], - "signature": [ - "(", - "RawSavedDashboardPanelTo60", - " | ", - "RawSavedDashboardPanel610", - " | ", - "RawSavedDashboardPanel620", - " | ", - "RawSavedDashboardPanel640To720", - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanelTo60", - "text": "SavedDashboardPanelTo60" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel610", - "text": "SavedDashboardPanel610" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel620", - "text": "SavedDashboardPanel620" - }, - " | ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel630", - "text": "SavedDashboardPanel630" - }, - ")[]" - ], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730.$2", - "type": "string", - "tags": [], - "label": "version", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730.$3", - "type": "boolean", - "tags": [], - "label": "useMargins", - "description": [], - "signature": [ - "boolean" - ], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730.$4", - "type": "Object", - "tags": [], - "label": "uiState", - "description": [], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.migratePanelsTo730.$4.Unnamed", - "type": "IndexSignature", - "tags": [], - "label": "[key: string]: SerializableRecord", - "description": [], - "signature": [ - "[key: string]: ", - "SerializableRecord" - ], - "path": "src/plugins/dashboard/common/migrate_to_730_panels.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], - "interfaces": [ - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardContainerStateWithType", - "type": "Interface", - "tags": [], - "label": "DashboardContainerStateWithType", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.DashboardContainerStateWithType", - "text": "DashboardContainerStateWithType" - }, - " extends ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableStateWithType", - "text": "EmbeddableStateWithType" - } - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardContainerStateWithType.panels", - "type": "Object", - "tags": [], - "label": "panels", - "description": [], - "signature": [ - "{ [panelId: string]: ", - "DashboardPanelState", - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - " & { [k: string]: unknown; }>; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardContainerStateWithType.controlGroupInput", - "type": "Object", - "tags": [], - "label": "controlGroupInput", - "description": [], - "signature": [ - { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.PersistableControlGroupInput", - "text": "PersistableControlGroupInput" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ], - "enums": [], - "misc": [ - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardDoc700To720", - "type": "Type", - "tags": [], - "label": "DashboardDoc700To720", - "description": [], - "signature": [ - "Doc" - ], - "path": "src/plugins/dashboard/common/bwc/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardDoc730ToLatest", - "type": "Type", - "tags": [], - "label": "DashboardDoc730ToLatest", - "description": [], - "signature": [ - "Doc" - ], - "path": "src/plugins/dashboard/common/bwc/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardDocPre700", - "type": "Type", - "tags": [], - "label": "DashboardDocPre700", - "description": [], - "signature": [ - "DocPre700" - ], - "path": "src/plugins/dashboard/common/bwc/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.GridData", - "type": "Type", - "tags": [], - "label": "GridData", - "description": [], - "signature": [ - "{ w: number; h: number; x: number; y: number; i: string; }" - ], - "path": "src/plugins/dashboard/common/embeddable/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.RawSavedDashboardPanel730ToLatest", - "type": "Type", - "tags": [], - "label": "RawSavedDashboardPanel730ToLatest", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanel640To720", - ", \"title\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\"> & { readonly type?: string | undefined; readonly name?: string | undefined; panelIndex: string; panelRefName?: string | undefined; }" - ], - "path": "src/plugins/dashboard/common/bwc/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel610", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanel610", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanel610", - ", \"columns\" | \"title\" | \"sort\" | \"panelIndex\" | \"gridData\" | \"version\"> & { readonly id: string; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel620", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanel620", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanel620", - ", \"columns\" | \"title\" | \"sort\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\"> & { readonly id: string; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel630", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanel630", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanel620", - ", \"columns\" | \"title\" | \"sort\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\"> & { readonly id: string; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel640To720", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanel640To720", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanel640To720", - ", \"title\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\"> & { readonly id: string; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel730ToLatest", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanel730ToLatest", - "description": [], - "signature": [ - "Pick<", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.RawSavedDashboardPanel730ToLatest", - "text": "RawSavedDashboardPanel730ToLatest" - }, - ", \"type\" | \"title\" | \"panelIndex\" | \"gridData\" | \"version\" | \"embeddableConfig\" | \"panelRefName\"> & { readonly id?: string | undefined; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanelTo60", - "type": "Type", - "tags": [], - "label": "SavedDashboardPanelTo60", - "description": [], - "signature": [ - "Pick<", - "RawSavedDashboardPanelTo60", - ", \"columns\" | \"title\" | \"sort\" | \"size_x\" | \"size_y\" | \"row\" | \"col\" | \"panelIndex\"> & { readonly id: string; readonly type: string; }" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], "objects": [ { "parentPluginId": "dashboard", diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 609a9e0925333..7a36e763bfeac 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 144 | 0 | 139 | 10 | +| 120 | 0 | 113 | 3 | ## Client @@ -37,9 +37,6 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese ### Functions -### Classes - - ### Interfaces @@ -68,6 +65,3 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese ### Interfaces -### Consts, variables and types - - diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index ae6e7c69b3a88..06821933db670 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index b8c91c02caa2d..e5c75be5fa094 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.devdocs.json b/api_docs/data_query.devdocs.json index 23849f6641368..051270f939554 100644 --- a/api_docs/data_query.devdocs.json +++ b/api_docs/data_query.devdocs.json @@ -1855,14 +1855,6 @@ "plugin": "discover", "path": "src/plugins/discover/public/application/main/services/discover_state.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts" - }, { "plugin": "maps", "path": "x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts" diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index b81d02840aba1..fdd7cb4298da4 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 415500b499045..3be2133617864 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -2794,9 +2794,9 @@ "label": "options", "description": [], "signature": [ - "{ filter?: any; search?: string | undefined; aggs?: Record | undefined; fields?: string[] | undefined; searchAfter?: string[] | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", + "> | undefined; searchAfter?: string[] | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", "SortOrder", " | undefined; searchFields?: string[] | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", "SavedObjectsFindOptionsReference", @@ -3475,13 +3475,7 @@ "label": "DataRequestHandlerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { search: Promise<", { "pluginId": "data", diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 12e4806b7ea2b..619cec4220ea7 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 1384a135bfbe8..f0d1bf1f9d458 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 4b95039f7981c..9800b25ff8baf 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 3608114bd15af..d2f23607c286c 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index c6389cc090a71..3c9d9dc38bae9 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 69bcfa932c46f..86bbfd4e83cfa 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index b658b5b795600..547c12c8570c2 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -24,9 +24,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | alerting, discover, securitySolution | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | | | actions, alerting | - | -| | savedObjects, embeddable, fleet, visualizations, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, dashboard, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | -| | savedObjects, embeddable, fleet, visualizations, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, dashboard, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | -| | discover, dashboard, maps, monitoring | - | +| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | discover, maps, monitoring | - | | | unifiedSearch, discover, maps, infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | @@ -34,7 +34,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | encryptedSavedObjects, actions, data, cloud, ml, logstash, securitySolution | - | | | dashboard, dataVisualizer, stackAlerts, expressionPartitionVis | - | -| | dashboard | - | | | dataViews, maps | - | | | dataViews, maps | - | | | maps | - | @@ -43,6 +42,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement | - | +| | dashboard | - | | | observability, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | | | dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | @@ -75,10 +75,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | apm, security, securitySolution | 8.8.0 | | | visualizations, dashboard, lens, maps, ml, securitySolution, security, @kbn/core-application-browser-internal, @kbn/core-application-browser-mocks | 8.8.0 | | | securitySolution, @kbn/core-application-browser-internal | 8.8.0 | -| | savedObjectsTaggingOss, dashboard | 8.8.0 | -| | dashboard | 8.8.0 | | | maps, dashboard, @kbn/core-saved-objects-migration-server-internal | 8.8.0 | | | monitoring, kibanaUsageCollection, @kbn/core-apps-browser-internal, @kbn/core-metrics-server-internal, @kbn/core-status-server-internal, @kbn/core-usage-data-server-internal | 8.8.0 | +| | savedObjectsTaggingOss, dashboard | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | @@ -131,6 +130,7 @@ Safe to remove. | | expressions | | | expressions | | | kibanaReact | +| | savedObjects | | | savedObjects | | | licensing | | | licensing | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index dabf6a7945b9a..3943c847158f1 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -211,16 +211,14 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [sync_dashboard_filter_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts#:~:text=syncQueryStateWithUrl), [sync_dashboard_filter_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts#:~:text=syncQueryStateWithUrl) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/types.ts#:~:text=fieldFormats), [data_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/data_service.ts#:~:text=fieldFormats) | - | | | [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton), [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton), [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton) | - | | | [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | -| | [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObject), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObject), [dashboard_tagging.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts#:~:text=SavedObject), [dashboard_tagging.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts#:~:text=SavedObject) | 8.8.0 | -| | [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObjectClass) | 8.8.0 | +| | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject) | 8.8.0 | | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/types.ts#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | -| | [dashboard_migrations.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts#:~:text=SavedObjectAttributes), [dashboard_migrations.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [saved_dashboard_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/saved_dashboard_references.ts#:~:text=SavedObjectAttributes), [saved_dashboard_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/saved_dashboard_references.ts#:~:text=SavedObjectAttributes)+ 8 more | - | -| | [dashboard_migrations.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts#:~:text=SavedObjectAttributes), [dashboard_migrations.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [saved_dashboard_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/saved_dashboard_references.ts#:~:text=SavedObjectAttributes), [saved_dashboard_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/saved_dashboard_references.ts#:~:text=SavedObjectAttributes)+ 8 more | - | -| | [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations_730.ts#:~:text=warning), [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations_730.ts#:~:text=warning) | 8.8.0 | +| | [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectAttributes), [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectAttributes), [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes)+ 11 more | - | +| | [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectAttributes), [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectAttributes), [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes)+ 11 more | - | +| | [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts#:~:text=warning), [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts#:~:text=warning) | 8.8.0 | @@ -525,7 +523,7 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [pack_queries_status_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx#:~:text=indexPatternId), [pack_queries_status_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx#:~:text=indexPatternId), [use_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/common/hooks/use_discover_link.tsx#:~:text=indexPatternId) | - | +| | [pack_queries_status_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx#:~:text=indexPatternId), [view_results_in_discover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx#:~:text=indexPatternId), [use_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/common/hooks/use_discover_link.tsx#:~:text=indexPatternId) | - | | | [empty_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/components/empty_state.tsx#:~:text=KibanaPageTemplate), [empty_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/components/empty_state.tsx#:~:text=KibanaPageTemplate) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 643d002885ef8..da0e278bffa6c 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -89,10 +89,9 @@ so TS and code-reference navigation might not highlight them. | | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| | dashboard | | [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal), [saved_object_save_modal_dashboard.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx#:~:text=SavedObjectSaveModal), [saved_object_save_modal_dashboard.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | -| dashboard | | [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [saved_object_loader.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/saved_object_loader.ts#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObject), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObject), [dashboard_tagging.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts#:~:text=SavedObject), [dashboard_tagging.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts#:~:text=SavedObject) | 8.8.0 | -| dashboard | | [saved_dashboard.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=SavedObjectClass) | 8.8.0 | +| dashboard | | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject) | 8.8.0 | | dashboard | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/types.ts#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | -| dashboard | | [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations_730.ts#:~:text=warning), [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations_730.ts#:~:text=warning) | 8.8.0 | +| dashboard | | [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts#:~:text=warning), [migrations_730.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/saved_objects/migrations/migrate_to_730/migrations_730.ts#:~:text=warning) | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index ddeb12beb9a7a..b724ab15e695e 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 695319775e984..8c4a6308dd17b 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -346,7 +346,7 @@ }, { "plugin": "osquery", - "path": "x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx" + "path": "x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx" }, { "plugin": "osquery", @@ -1101,7 +1101,7 @@ "label": "sharingSavedObjectProps", "description": [], "signature": [ - "{ outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" + "{ outcome?: \"conflict\" | \"exactMatch\" | \"aliasMatch\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" ], "path": "src/plugins/saved_search/public/services/saved_searches/types.ts", "deprecated": false, diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 4a2a4abc7d5ef..39b2814d4136c 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 104fc1d5cde65..e9974fbb2bf45 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 3203b4c957545..8bbe0a063796d 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index e5e64a70e8012..c53b6a4aba7e9 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 6d488adbda835..6cc990a42eba7 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 2d3bbbfe7ad85..8f80e15d3886b 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index e6eb098f3c9cd..6a4ca6fcb7735 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 54a7269ef5325..159a2f7167b0a 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index f821ced3a76e0..8361059c91079 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1312,7 +1312,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" + "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1332,7 +1332,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1347,7 +1347,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" + "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index dff00025d47a5..b9be569d3a3e9 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index ba5e5e742f4c5..6d9a8d244589a 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 331af14bbd784..b38b786ca55b9 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 36ea696e53359..9d856b882a318 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 97052c63f7476..84225e80ad009 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 95950d42e4473..e6902356f091e 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index ee6b2d7e353f4..60a4d843aad34 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index c1edcc43088dd..85c6421d292d1 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 672e12f360d34..4d4a23fe3a6ff 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 7fbd70637ed36..ff4af965c963e 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 09f5c55834635..2d3ede34cf142 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 8414051020d06..114bb08462720 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 42b6c0963d951..6a87768ff01e5 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 340a2de8e8e21..bf0c8f9c49aea 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.devdocs.json b/api_docs/expressions.devdocs.json index cf0d00e1dd478..bae5e63702f4f 100644 --- a/api_docs/expressions.devdocs.json +++ b/api_docs/expressions.devdocs.json @@ -11507,7 +11507,7 @@ "\nThis type represents the `type` of any `DatatableColumn` in a `Datatable`.\nits duplicated from KBN_FIELD_TYPES" ], "signature": [ - "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"conflict\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"histogram\"" + "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"histogram\"" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, @@ -21037,7 +21037,7 @@ "\nThis type represents the `type` of any `DatatableColumn` in a `Datatable`.\nits duplicated from KBN_FIELD_TYPES" ], "signature": [ - "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"conflict\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"histogram\"" + "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"histogram\"" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, @@ -29210,7 +29210,7 @@ "label": "type", "description": [], "signature": [ - "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"conflict\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"histogram\"" + "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"histogram\"" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, @@ -35226,7 +35226,7 @@ "\nThis type represents the `type` of any `DatatableColumn` in a `Datatable`.\nits duplicated from KBN_FIELD_TYPES" ], "signature": [ - "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"conflict\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"histogram\"" + "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"null\" | \"ip\" | \"nested\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"histogram\"" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 74c3fd828f44f..cb5617b237f7a 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 7b4880c40f8d6..1c1d57aa91eb1 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 8c4ebd13fe23d..dc89d286ddfe5 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index c0963ced99a2c..f29f32174d128 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 96ec1e67bf2bf..3cc10c4a1612a 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index d068e061e494b..3059a8f9c9f84 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -7170,13 +7170,7 @@ "text": "NewPackagePolicy" }, ", context: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ", request: ", "KibanaRequest", ") => Promise
    ) => Promise<", @@ -7611,13 +7593,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - } + "RequestHandlerContext" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -7709,13 +7685,7 @@ "text": "PackagePolicy" }, ", context: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ", request: ", "KibanaRequest", ") => Promise<", @@ -7761,13 +7731,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - } + "RequestHandlerContext" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -7808,13 +7772,7 @@ "text": "UpdatePackagePolicy" }, ", context: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", ", request: ", "KibanaRequest", ") => Promise<", @@ -7860,13 +7818,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - } + "RequestHandlerContext" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -15292,7 +15244,7 @@ "label": "PackageSpecCategory", "description": [], "signature": [ - "\"custom\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\"" + "\"custom\" | \"aws\" | \"azure\" | \"cloud\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"infrastructure\" | \"kubernetes\" | \"languages\" | \"message_queue\" | \"monitoring\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"security\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"web\"" ], "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 6f966dbb5a316..7c5beab0d5c9d 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 63433541ece2b..6803fa9d095e6 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index c28c8b2fef872..062ffb1effb3c 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 8c285e037a355..8f772016026cb 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 2d1523f302cb2..29de31f6cf689 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 46b6303673ab9..3dac89fa09d7f 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index d86809062de00..315978011a0b4 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 0a75d4c550c42..1eec7a67db10b 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 1b2ce0d5a84c2..ba3797cab67d7 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 3ca0d95b596d9..c6dcd6bde7831 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 7e469da7a40b8..78c88adbe04a1 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 9e1d8daed9b7c..8e0003a6394ae 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 294c0b5ebb04b..b936fa218da1b 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index e991378eefb61..480d1389d1d53 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 61ec0f495851f..67d5f65e938ae 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index af69ccaa66458..2c75bd5664b65 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index c80fb067196d2..af39931531e69 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index c8fd4ef979406..5515fe767880f 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 102c627e9ef5e..d857977462103 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 88ae4fc21aca0..ec16bbb3798aa 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 951b6896fa528..cd86d11a33dc4 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index a679b1a252dad..24e76f781315f 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index c330c66a3b511..a38a76d26da3c 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 4cf5662b020cc..96f35a5ca85a3 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 4396f3ffe02e1..efc5f97cca9fe 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 1857d82ee013a..7030b63abaa14 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.devdocs.json b/api_docs/kbn_ci_stats_reporter.devdocs.json index f831ac502a939..18389c93d07ca 100644 --- a/api_docs/kbn_ci_stats_reporter.devdocs.json +++ b/api_docs/kbn_ci_stats_reporter.devdocs.json @@ -654,7 +654,7 @@ "\nOverall result of this test group" ], "signature": [ - "\"fail\" | \"pass\" | \"skip\"" + "\"skip\" | \"fail\" | \"pass\"" ], "path": "packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts", "deprecated": false, @@ -755,7 +755,7 @@ "\n\"fail\", \"pass\" or \"skip\", the result of the tests" ], "signature": [ - "\"fail\" | \"pass\" | \"skip\"" + "\"skip\" | \"fail\" | \"pass\"" ], "path": "packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts", "deprecated": false, @@ -1041,7 +1041,7 @@ "label": "CiStatsTestResult", "description": [], "signature": [ - "\"fail\" | \"pass\" | \"skip\"" + "\"skip\" | \"fail\" | \"pass\"" ], "path": "packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts", "deprecated": false, diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index f3779a0488fb4..464ef1f629346 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index e2d60d46020af..7aac22dfeead6 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index e0475bc85ecd2..edd9cb125e751 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 32514d096d20f..b82dc689dba2f 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index b93c70c8a0eb5..cebca9163e797 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 78d758c0f2fe3..bd788d6e1868d 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 812ca543dea50..ac56135aa2927 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index c29081c8b626d..eefeafb88d5d6 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 739c7e7af5969..36b99e4215594 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 2c0999666ff1f..abf361ac547be 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index f58a16ef732ad..ec45043e4726a 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index ced023377219a..04e49781988c4 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index cbea4bff1bf86..126ab218962cd 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index b1580098a3652..71e7fae504f5f 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index f704df4a7f345..55beb31993bb2 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 4767d8fa50538..c777505da45db 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 7c6ce13416170..10828fe0de714 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 17402d105d5b5..978ac4faed760 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 48cb4145874c7..16fd9445d124c 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 28228f4c74761..cdaf427d6802b 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 09afb9dd896c7..5bd2a717b5b84 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index bf845366f8e62..76f931b4d756c 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index bf1d2e6b25458..77aa19a6800c9 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index a2eb49783d4be..e6bfe1ecd7db2 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index aa1d950326fc9..e9475861b697e 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 3d8690f8d6f7d..d6dc431b07fdc 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 78563e898c03a..fdf7cc31d41f9 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 4b44cbfe07571..73d5385236586 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index f8052fad772a5..a1f512749d437 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 79da95fae9e67..42574e3029ea4 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index ad16ad6aea2e6..6bb0bb64c5ba2 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 7fe5dba92aa5c..938d8d6b38aff 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 2547deec251f6..9d33c64bd7f4a 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index d96ff821c4365..7f254dda6eee2 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 74dee4cd3fdcc..527a5f2be91ee 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index da6a21361ea25..7b470aeff3555 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 617a402d35fcc..34e0e5fb4d1fe 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 1217a7b8fa20d..97c4d2e6d4b44 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 0ccaad98b392b..0347b3f309a86 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 14eccd7c10c34..5cccdf33b5038 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index f5f017918ee4a..e2c4d921a70b3 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 477917148b690..1d786ddcd3496 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 399790680e068..25079df745e3c 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 318e2f19b36d7..a5712d10acd36 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index b4e402b1d85a7..2b2107d520826 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 1263eb43812bf..c9aa728e95fb2 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index efd3c4dce7d8e..630ee26b50242 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 4b8b92e29d47e..62ed3d4b8afa0 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 12f2b1b89aaa5..1c33ab738b0ea 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 22703ff1d56c5..8d4402a7a2cda 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 58d7084730b09..5e923792bc936 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 9f241f79f3092..f2336d9fdfb05 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 2d3ce9691dbc3..61e72d43c392a 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index db3e364e96ff7..1c31d922fc680 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index cbeb87f2524be..00772d47658c5 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index e838dfc832fd6..7a8edbe3e3b30 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 6bdcb4cd9e37a..6dd6b8aebce65 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index afb5974de9a1d..8014dbb2ceb6b 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 8e28ebbe2ff12..ed565e7b8f3b3 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 463b56f364292..632f5546a7210 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index bf4cf64d7a58a..92184cbab2784 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 4047f8a6f4847..52f62aad5c930 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.devdocs.json b/api_docs/kbn_core_http_request_handler_context_server.devdocs.json new file mode 100644 index 0000000000000..ebf6567c0a8b4 --- /dev/null +++ b/api_docs/kbn_core_http_request_handler_context_server.devdocs.json @@ -0,0 +1,283 @@ +{ + "id": "@kbn/core-http-request-handler-context-server", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CoreRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "CoreRequestHandlerContext", + "description": [ + "\nThe `core` context provided to route handler.\n\nProvides the following clients and services:\n - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client\n which uses the credentials of the incoming request\n - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing\n all the registered types.\n - {@link IScopedClusterClient | elasticsearch.client} - Elasticsearch\n data client which uses the credentials of the incoming request\n - {@link IUiSettingsClient | uiSettings.client} - uiSettings client\n which uses the credentials of the incoming request" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CoreRequestHandlerContext.savedObjects", + "type": "Object", + "tags": [], + "label": "savedObjects", + "description": [], + "signature": [ + "SavedObjectsRequestHandlerContext" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CoreRequestHandlerContext.elasticsearch", + "type": "Object", + "tags": [], + "label": "elasticsearch", + "description": [], + "signature": [ + "ElasticsearchRequestHandlerContext" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CoreRequestHandlerContext.uiSettings", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [], + "signature": [ + "UiSettingsRequestHandlerContext" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CoreRequestHandlerContext.deprecations", + "type": "Object", + "tags": [], + "label": "deprecations", + "description": [], + "signature": [ + "DeprecationsRequestHandlerContext" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootCoreRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "PrebootCoreRequestHandlerContext", + "description": [], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootCoreRequestHandlerContext.uiSettings", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.PrebootUiSettingsRequestHandlerContext", + "text": "PrebootUiSettingsRequestHandlerContext" + } + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "PrebootRequestHandlerContext", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.PrebootRequestHandlerContext", + "text": "PrebootRequestHandlerContext" + }, + " extends ", + "RequestHandlerContextBase" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootRequestHandlerContext.core", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "Promise<", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.PrebootCoreRequestHandlerContext", + "text": "PrebootCoreRequestHandlerContext" + }, + ">" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootUiSettingsRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "PrebootUiSettingsRequestHandlerContext", + "description": [], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.PrebootUiSettingsRequestHandlerContext.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + "IUiSettingsClient" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/preboot_request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.RequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "RequestHandlerContext", + "description": [ + "\nBase context passed to a route handler, containing the `core` context part.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " extends ", + "RequestHandlerContextBase" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.RequestHandlerContext.core", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "Promise<", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.CoreRequestHandlerContext", + "text": "CoreRequestHandlerContext" + }, + ">" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/core-http-request-handler-context-server", + "id": "def-server.CustomRequestHandlerContext", + "type": "Type", + "tags": [], + "label": "CustomRequestHandlerContext", + "description": [ + "\nMixin allowing plugins to define their own request handler contexts.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "server", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-server.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " & { [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; }" + ], + "path": "packages/core/http/core-http-request-handler-context-server/src/request_handler_context.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx new file mode 100644 index 0000000000000..0e41263673dda --- /dev/null +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreHttpRequestHandlerContextServerPluginApi +slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server +title: "@kbn/core-http-request-handler-context-server" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-http-request-handler-context-server plugin +date: 2022-09-29 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] +--- +import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 11 | 0 | + +## Server + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 650426f493570..0fdb1bfe43185 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 0df0a232ed7e3..b758dea2dd269 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 404f13d3ca12a..bb3c7c1bd3505 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 524b236008cfb..220d5282e1fc4 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 94a0469ff1071..ed120c45d977d 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index aac9e17df421b..9cb11bc93a1fe 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index e83f6e8553d82..1bb71320adbcf 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index a89d07338f781..edbbec7b5aac1 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 3575cc0d18a98..408a8656056a7 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index ce24eb49860c5..549c463617a54 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 92e9d0e07cfe5..11a4ba2c541e6 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 17e4abfa2a1aa..579e9087903cf 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index d8c9f151e2f4f..c88718d0e930c 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 574ff5014691b..6be7ee2210094 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 62c8d4f2358e5..863f69b315180 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 8deee157e3d42..add898c16abe6 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index cf1126fe73e2a..a488e40d01dd0 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index bc4d09292edbf..87082d1e33197 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 485e124d97e42..7a746c6278054 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 4b17dbaac8c84..a413d283cb4c8 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 36c1e8c0f9a42..3ad9c040ab8ff 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index a1a8ce47aed54..4e9e0445e3f75 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 6b4a55f82676d..0cfd64309de97 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index d1494db236370..40f97497e1cff 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index ad35d06977cc0..3e4d25261b363 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 5b5b8a9421411..9ec6da0be76fe 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index bf95da355c9e9..e1be058f66949 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 3a07f7a676064..2f8dd3d2d3cfc 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 130bddee31d14..99c39b3ae3bff 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index fef5e6c2f9a85..b1abfcae9d1d7 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 724251e5e502f..6af7ea7a401a6 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 3837e623f29c4..4e53620f210c2 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 0f7f597d06d38..3cdf63fa196bf 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 42846c64f33d6..ed9822ce6524b 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index a52c3697e57dd..0e35863807e95 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 238252f104b3c..e8513df53c908 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index d5b97eb1a0cbb..a33965c6f0245 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 20a753b66ea99..499f42fd91b2c 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index c074d0f80cb88..cedfc5029275d 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index b12a698b21606..bc6024dea788e 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -76,7 +76,7 @@ "\nThe outcome for a successful `resolve` call is one of the following values:\n\n * `'exactMatch'` -- One document exactly matched the given ID.\n * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different\n than the given ID.\n * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the\n `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID." ], "signature": [ - "\"exactMatch\" | \"aliasMatch\" | \"conflict\"" + "\"conflict\" | \"exactMatch\" | \"aliasMatch\"" ], "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts", "deprecated": false, @@ -1974,9 +1974,9 @@ "label": "SavedObjectsFindOptions", "description": [], "signature": [ - "{ type: string | string[]; filter?: any; search?: string | undefined; aggs?: Record | undefined; fields?: string[] | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; searchFields?: string[] | undefined; hasReference?: ", + "> | undefined; page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; searchFields?: string[] | undefined; hasReference?: ", "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 949af62592ecb..de5b9aa205cab 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index 0e040b72d2d3d..1bff25681bdbf 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -5949,7 +5949,7 @@ "\nThe outcome for a successful `resolve` call is one of the following values:\n\n * `'exactMatch'` -- One document exactly matched the given ID.\n * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different\n than the given ID.\n * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the\n `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID." ], "signature": [ - "\"exactMatch\" | \"aliasMatch\" | \"conflict\"" + "\"conflict\" | \"exactMatch\" | \"aliasMatch\"" ], "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts", "deprecated": false, @@ -6427,9 +6427,9 @@ "label": "SavedObjectsCreatePointInTimeFinderOptions", "description": [], "signature": [ - "{ type: string | string[]; filter?: any; search?: string | undefined; aggs?: Record | undefined; fields?: string[] | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", + "> | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: ", "SortOrder", " | undefined; searchFields?: string[] | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", { diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 6fa09c36e31f1..7628f073dc948 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 7c588cdc511e2..a91351cf9f732 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 447e323f6f6c3..fa562b3b2acf5 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 9931b55fa4f21..acc5f724b1bc8 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index deb24928a107a..114ad87c44b3c 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 3bfa422bdff98..6731e21119771 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 1a306a18a3eb1..276569792284a 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index a34e632cf249f..9f0c9ea82ba6c 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 59d4d1841c7fa..bc3ea822a9424 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index f0610e31ec803..b5f3e61df18bf 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index cd9ba3c71908b..99e95bfdab286 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index f2dd4437d43e1..5edf8cbfddef4 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 88c329e9ae344..5ed2a4976d1cf 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index df7eaa590f6db..8f12d48e0f7a2 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 5534b4652be59..6de1b336be402 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index ccf532e33ba38..4fb52fecf8710 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 5b78e21ba58ce..71fdb228446e2 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index f4f9cc899056b..49314722d46af 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 86d04a0b4ff7f..266265f2ee05f 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 52e756da1a554..8d222376f5bf9 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 432a7d95007ec..f8cae5c4a5506 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 8ccca542c4b7d..0dacdfd5af97c 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index ab86530abfbbd..99b4036abb6bd 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 9de9031c75db8..549cdfe3a58e1 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 9c59e5a04b8e4..c429de87e13d9 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 02d479962f929..0888bc8875a22 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 71eada8e5603f..254373b7a14d3 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index b8b744e259c54..61ceae147d70f 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index a89cdf92e9ac2..9b9365bc3081a 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 290774ac1f9eb..baf48b87a0eb3 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 004e652235478..1aba4f31960ad 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 84190ae50348c..b2fe92199314c 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 2f362f1ede3ff..91c104b8b9de3 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index e214233f02eb7..31ba42e2554fc 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index b8ac7249cb2af..a25b0cfc501c8 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 4f5cca38779c3..101431a9c73db 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index bc10003b1203b..710a3c62d5446 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index d95c8ad7295ed..a8c5ebefe66c9 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 06ce6dd999351..b7a8b4187db82 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 4bcac88980eb1..77c10c205e812 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 42846a27d0d57..f79bd78824a01 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 5db4302dd4cd2..156f2ad1e34d0 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 968955efd32a8..c40a114fb200d 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index e047906625c28..6c817ebf71e78 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index c7d4d0fe3d260..055b95affc247 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 30e9b543d9d8d..f6a0a8c29e495 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 8590060b07965..fa63acb9d852c 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 1e69af86cfda4..9f608a801f2c0 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 695ed989dfe99..1498ddb5d2636 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index b329278d82729..537db266a40f5 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 9a2cfbe08a28b..923c644404d7b 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 2e0b9742678dc..23893848967a1 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 0373e71bbf94c..a29e11c7f3f5c 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index f0a54f4a6740d..c3673495d2d9a 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index a2bfedd08171e..29968a934531a 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 2a9f62b4f3f36..535192fd22884 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index b1b28470de32e..270341543e978 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index b675d4d8f4da0..75d249e64bf71 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index b7abe8b512959..d7346ce1b677d 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index e7a5d6fe0f7ec..b1ce2043e27f1 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index f716a25e4408f..63510d74b009a 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 25ba31dee6a69..ce7de735a1105 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index c36a372e85d74..a96adba4a09b2 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 6051ff9b96e16..d35da6c7ae880 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 0554879f31ad2..623552a19ab96 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index c005d1ea66f6e..46b40beb8bf35 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index b2cd34e94a506..e9b4b290f7d14 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 1b91d601c2246..3f19d6bc0c359 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 13444331d9fcc..da43bfec51c4d 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index d6db9caa5dd01..2770d7ccb6ac7 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 12ab660fc1764..83b9321c6a3f9 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index a0fbd8c5ef3c9..ae4854e96ac2a 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 6808dca6dbe35..dd5f5c5ace183 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 8da4778d8b81f..4a750b0b7dfad 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index e9cf6c1838c51..1702698262627 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 4ec4c4ef7c9eb..987ec45f387a2 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 757c4018832a9..ed7566b575b58 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 11961ef3d602b..7c45eeef92b57 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index a980b80bd7a38..859b6a067644d 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index b1051ed927c86..aedd0101a45aa 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 98d5196dc24f1..bfd4b4e6b5afb 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index a04f5630c37b4..3ca9f9e155f16 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index af33c58333e71..293fa51905454 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 2a373248eac93..aaf5d45ea057c 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index c30477647a1ce..2d6403bd41dfe 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -912,7 +912,7 @@ "label": "AlertConsumers", "description": [], "signature": [ - "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" + "\"infrastructure\" | \"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts", "deprecated": false, @@ -1077,7 +1077,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"tags\" | \"kibana\" | \"@timestamp\" | \"event.action\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert\" | \"kibana.alert.rule\"" + "\"tags\" | \"kibana\" | \"@timestamp\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert\" | \"kibana.alert.rule\"" ], "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", "deprecated": false, @@ -1107,7 +1107,7 @@ "label": "ValidFeatureId", "description": [], "signature": [ - "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" + "\"infrastructure\" | \"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 61d440950cbf3..4f65db7503fc7 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 4ca4605d2eb99..5a9078575773a 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 99858a653681a..dc5fcdbd1d0f1 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json new file mode 100644 index 0000000000000..97b711804af12 --- /dev/null +++ b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json @@ -0,0 +1,1209 @@ +{ + "id": "@kbn/securitysolution-exception-list-components", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.EmptyViewerState", + "type": "Function", + "tags": [], + "label": "EmptyViewerState", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/empty_viewer_state/empty_viewer_state.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.EmptyViewerState.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCard", + "type": "Function", + "tags": [], + "label": "ExceptionItemCard", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionItemProps", + "text": "ExceptionItemProps" + }, + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCard.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardComments", + "type": "Function", + "tags": [], + "label": "ExceptionItemCardComments", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionItemCardCommentsProps", + "text": "ExceptionItemCardCommentsProps" + }, + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardComments.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardConditions", + "type": "Function", + "tags": [], + "label": "ExceptionItemCardConditions", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + "CriteriaConditionsProps", + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/conditions/conditions.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardConditions.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeader", + "type": "Function", + "tags": [], + "label": "ExceptionItemCardHeader", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionItemCardHeaderProps", + "text": "ExceptionItemCardHeaderProps" + }, + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeader.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfo", + "type": "Function", + "tags": [], + "label": "ExceptionItemCardMetaInfo", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionItemCardMetaInfoProps", + "text": "ExceptionItemCardMetaInfoProps" + }, + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfo.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItems", + "type": "Function", + "tags": [], + "label": "ExceptionItems", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_items/exception_items.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItems.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.Pagination", + "type": "Function", + "tags": [], + "label": "Pagination", + "description": [], + "signature": [ + "React.NamedExoticComponent<", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.PaginationProps", + "text": "PaginationProps" + }, + ">" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/pagination/pagination.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.Pagination.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.SearchBar", + "type": "Function", + "tags": [], + "label": "SearchBar", + "description": [], + "signature": [ + "React.NamedExoticComponent" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/search_bar/search_bar.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.SearchBar.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ValueWithSpaceWarning", + "type": "Function", + "tags": [], + "label": "ValueWithSpaceWarning", + "description": [], + "signature": [ + "({ value, tooltipIconType, tooltipIconText, }: React.PropsWithChildren) => JSX.Element | null" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ValueWithSpaceWarning.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n value,\n tooltipIconType = 'iInCircle',\n tooltipIconText,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/value_with_space_warning.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardCommentsProps", + "type": "Interface", + "tags": [], + "label": "ExceptionItemCardCommentsProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardCommentsProps.comments", + "type": "Array", + "tags": [], + "label": "comments", + "description": [], + "signature": [ + "EuiCommentProps", + "[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/comments.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeaderProps", + "type": "Interface", + "tags": [], + "label": "ExceptionItemCardHeaderProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeaderProps.item", + "type": "Object", + "tags": [], + "label": "item", + "description": [], + "signature": [ + "{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeaderProps.actions", + "type": "Array", + "tags": [], + "label": "actions", + "description": [], + "signature": [ + "{ key: string; icon: string; label: string | boolean; onClick: () => void; }[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeaderProps.disableActions", + "type": "CompoundType", + "tags": [], + "label": "disableActions", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardHeaderProps.dataTestSubj", + "type": "string", + "tags": [], + "label": "dataTestSubj", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/header/header.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps", + "type": "Interface", + "tags": [], + "label": "ExceptionItemCardMetaInfoProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps.item", + "type": "Object", + "tags": [], + "label": "item", + "description": [], + "signature": [ + "{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps.references", + "type": "Array", + "tags": [], + "label": "references", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.RuleReference", + "text": "RuleReference" + }, + "[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps.dataTestSubj", + "type": "string", + "tags": [], + "label": "dataTestSubj", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps.formattedDateComponent", + "type": "CompoundType", + "tags": [], + "label": "formattedDateComponent", + "description": [], + "signature": [ + "\"symbol\" | \"object\" | React.ComponentType | \"body\" | \"path\" | \"circle\" | \"filter\" | \"data\" | \"line\" | \"area\" | \"time\" | \"label\" | \"legend\" | \"image\" | \"link\" | \"menu\" | \"stop\" | \"base\" | \"text\" | \"title\" | \"s\" | \"small\" | \"svg\" | \"meta\" | \"script\" | \"summary\" | \"source\" | \"desc\" | \"q\" | \"pattern\" | \"mask\" | \"input\" | \"slot\" | \"style\" | \"head\" | \"section\" | \"big\" | \"sub\" | \"sup\" | \"animate\" | \"progress\" | \"view\" | \"output\" | \"var\" | \"map\" | \"html\" | \"a\" | \"img\" | \"audio\" | \"form\" | \"main\" | \"abbr\" | \"address\" | \"article\" | \"aside\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"select\" | \"span\" | \"strong\" | \"table\" | \"template\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"video\" | \"wbr\" | \"webview\" | \"animateMotion\" | \"animateTransform\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\"" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemCardMetaInfoProps.securityLinkAnchorComponent", + "type": "CompoundType", + "tags": [], + "label": "securityLinkAnchorComponent", + "description": [], + "signature": [ + "\"symbol\" | \"object\" | React.ComponentType | \"body\" | \"path\" | \"circle\" | \"filter\" | \"data\" | \"line\" | \"area\" | \"time\" | \"label\" | \"legend\" | \"image\" | \"link\" | \"menu\" | \"stop\" | \"base\" | \"text\" | \"title\" | \"s\" | \"small\" | \"svg\" | \"meta\" | \"script\" | \"summary\" | \"source\" | \"desc\" | \"q\" | \"pattern\" | \"mask\" | \"input\" | \"slot\" | \"style\" | \"head\" | \"section\" | \"big\" | \"sub\" | \"sup\" | \"animate\" | \"progress\" | \"view\" | \"output\" | \"var\" | \"map\" | \"html\" | \"a\" | \"img\" | \"audio\" | \"form\" | \"main\" | \"abbr\" | \"address\" | \"article\" | \"aside\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"select\" | \"span\" | \"strong\" | \"table\" | \"template\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"video\" | \"wbr\" | \"webview\" | \"animateMotion\" | \"animateTransform\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\"" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps", + "type": "Interface", + "tags": [], + "label": "ExceptionItemProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.dataTestSubj", + "type": "string", + "tags": [], + "label": "dataTestSubj", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.disableActions", + "type": "CompoundType", + "tags": [], + "label": "disableActions", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.exceptionItem", + "type": "Object", + "tags": [], + "label": "exceptionItem", + "description": [], + "signature": [ + "{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.listType", + "type": "Enum", + "tags": [], + "label": "listType", + "description": [], + "signature": [ + "ExceptionListTypeEnum" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.ruleReferences", + "type": "Array", + "tags": [], + "label": "ruleReferences", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.editActionLabel", + "type": "string", + "tags": [], + "label": "editActionLabel", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.deleteActionLabel", + "type": "string", + "tags": [], + "label": "deleteActionLabel", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.securityLinkAnchorComponent", + "type": "CompoundType", + "tags": [], + "label": "securityLinkAnchorComponent", + "description": [], + "signature": [ + "\"symbol\" | \"object\" | React.ComponentType | \"body\" | \"path\" | \"circle\" | \"filter\" | \"data\" | \"line\" | \"area\" | \"time\" | \"label\" | \"legend\" | \"image\" | \"link\" | \"menu\" | \"stop\" | \"base\" | \"text\" | \"title\" | \"s\" | \"small\" | \"svg\" | \"meta\" | \"script\" | \"summary\" | \"source\" | \"desc\" | \"q\" | \"pattern\" | \"mask\" | \"input\" | \"slot\" | \"style\" | \"head\" | \"section\" | \"big\" | \"sub\" | \"sup\" | \"animate\" | \"progress\" | \"view\" | \"output\" | \"var\" | \"map\" | \"html\" | \"a\" | \"img\" | \"audio\" | \"form\" | \"main\" | \"abbr\" | \"address\" | \"article\" | \"aside\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"select\" | \"span\" | \"strong\" | \"table\" | \"template\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"video\" | \"wbr\" | \"webview\" | \"animateMotion\" | \"animateTransform\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\"" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.formattedDateComponent", + "type": "CompoundType", + "tags": [], + "label": "formattedDateComponent", + "description": [], + "signature": [ + "\"symbol\" | \"object\" | React.ComponentType | \"body\" | \"path\" | \"circle\" | \"filter\" | \"data\" | \"line\" | \"area\" | \"time\" | \"label\" | \"legend\" | \"image\" | \"link\" | \"menu\" | \"stop\" | \"base\" | \"text\" | \"title\" | \"s\" | \"small\" | \"svg\" | \"meta\" | \"script\" | \"summary\" | \"source\" | \"desc\" | \"q\" | \"pattern\" | \"mask\" | \"input\" | \"slot\" | \"style\" | \"head\" | \"section\" | \"big\" | \"sub\" | \"sup\" | \"animate\" | \"progress\" | \"view\" | \"output\" | \"var\" | \"map\" | \"html\" | \"a\" | \"img\" | \"audio\" | \"form\" | \"main\" | \"abbr\" | \"address\" | \"article\" | \"aside\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"select\" | \"span\" | \"strong\" | \"table\" | \"template\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"video\" | \"wbr\" | \"webview\" | \"animateMotion\" | \"animateTransform\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\"" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.getFormattedComments", + "type": "Function", + "tags": [], + "label": "getFormattedComments", + "description": [], + "signature": [ + "(comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]) => ", + "EuiCommentProps", + "[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.getFormattedComments.$1", + "type": "Array", + "tags": [], + "label": "comments", + "description": [], + "signature": [ + "({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.onDeleteException", + "type": "Function", + "tags": [], + "label": "onDeleteException", + "description": [], + "signature": [ + "(arg: ", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionListItemIdentifiers", + "text": "ExceptionListItemIdentifiers" + }, + ") => void" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.onDeleteException.$1", + "type": "Object", + "tags": [], + "label": "arg", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.ExceptionListItemIdentifiers", + "text": "ExceptionListItemIdentifiers" + } + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.onEditException", + "type": "Function", + "tags": [], + "label": "onEditException", + "description": [], + "signature": [ + "(item: { _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }) => void" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionItemProps.onEditException.$1", + "type": "Object", + "tags": [], + "label": "item", + "description": [], + "signature": [ + "{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListItemIdentifiers", + "type": "Interface", + "tags": [], + "label": "ExceptionListItemIdentifiers", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListItemIdentifiers.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListItemIdentifiers.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListItemIdentifiers.namespaceType", + "type": "CompoundType", + "tags": [], + "label": "namespaceType", + "description": [], + "signature": [ + "\"single\" | \"agnostic\"" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListSummaryProps", + "type": "Interface", + "tags": [], + "label": "ExceptionListSummaryProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListSummaryProps.pagination", + "type": "Object", + "tags": [], + "label": "pagination", + "description": [], + "signature": [ + "Pagination" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ExceptionListSummaryProps.lastUpdated", + "type": "CompoundType", + "tags": [], + "label": "lastUpdated", + "description": [], + "signature": [ + "string | number | null" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.GetExceptionItemProps", + "type": "Interface", + "tags": [], + "label": "GetExceptionItemProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.GetExceptionItemProps.pagination", + "type": "Object", + "tags": [], + "label": "pagination", + "description": [], + "signature": [ + "Pagination", + " | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.GetExceptionItemProps.search", + "type": "string", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.GetExceptionItemProps.filters", + "type": "string", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps", + "type": "Interface", + "tags": [], + "label": "PaginationProps", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps.dataTestSubj", + "type": "string", + "tags": [], + "label": "dataTestSubj", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps.ariaLabel", + "type": "string", + "tags": [], + "label": "ariaLabel", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps.pagination", + "type": "Object", + "tags": [], + "label": "pagination", + "description": [], + "signature": [ + "Pagination" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps.onPaginationChange", + "type": "Function", + "tags": [], + "label": "onPaginationChange", + "description": [], + "signature": [ + "(arg: ", + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.GetExceptionItemProps", + "text": "GetExceptionItemProps" + }, + ") => void" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.PaginationProps.onPaginationChange.$1", + "type": "Object", + "tags": [], + "label": "arg", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-exception-list-components", + "scope": "common", + "docId": "kibKbnSecuritysolutionExceptionListComponentsPluginApi", + "section": "def-common.GetExceptionItemProps", + "text": "GetExceptionItemProps" + } + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReference", + "type": "Interface", + "tags": [], + "label": "RuleReference", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReference.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReference.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReference.ruleId", + "type": "string", + "tags": [], + "label": "ruleId", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReference.exceptionLists", + "type": "Array", + "tags": [], + "label": "exceptionLists", + "description": [], + "signature": [ + "{ _version: string | undefined; created_at: string; created_by: string; description: string; id: string; immutable: boolean; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; updated_at: string; updated_by: string; version: number; }[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReferences", + "type": "Interface", + "tags": [], + "label": "RuleReferences", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.RuleReferences.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: any[]", + "description": [], + "signature": [ + "[key: string]: any[]" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ListTypeText", + "type": "Enum", + "tags": [], + "label": "ListTypeText", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ViewerStatus", + "type": "Enum", + "tags": [], + "label": "ViewerStatus", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.ViewerFlyoutName", + "type": "Type", + "tags": [], + "label": "ViewerFlyoutName", + "description": [], + "signature": [ + "\"addException\" | \"editException\" | null" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx new file mode 100644 index 0000000000000..1bcc5017dfc5e --- /dev/null +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -0,0 +1,39 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSecuritysolutionExceptionListComponentsPluginApi +slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components +title: "@kbn/securitysolution-exception-list-components" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/securitysolution-exception-list-components plugin +date: 2022-09-29 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] +--- +import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 76 | 0 | 67 | 1 | + +## Common + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index c74046c4b1fa8..a22707f2957be 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 5f200ca33cd52..cdc1b3ea54ed7 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 75beb67b94d03..185f8a6c2a54b 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 2525d36a83930..ee826f79d080f 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 1729eb1e31923..79363d1648a7d 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index e7ea35711a930..bad03cd4ead92 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 061d996305ac9..d2e797f19a3e8 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 19a2ed45a48ba..aadd62030a1b8 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 247718cc1d2a7..37f5836577b4f 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 53f33ec3d564b..7aa2954ee0c08 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 0be9d3e0886f9..99fe2b585b0b2 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index b3d353cc6987e..e132797fea852 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index aefc65e7066a3..48d0d4756ef6c 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 25cf89691a720..5c184c9a583cc 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index aaf7c6592481a..249f6a00cf7e6 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index b12d28f86530e..e5e0f236974fb 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 4fafd24671035..b78abda31ac59 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 401b8763ce31c..dda07aa08205d 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 25b21295a4ebd..92fa45970a95f 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 5867910e33d25..67fd1537b9531 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 757324760d8eb..dba90c297e1be 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 19d3fcf3f0acc..eb63fe71719da 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index f75c2846a0475..312cc6dc442de 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 89d4d761d558c..fc5ddb138a558 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 2056f2314bce9..00f4e0b05251d 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 495d91eac2545..22e5de7cb344c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 7767a12066d24..de0d8b5ff101e 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index e0d65073b737e..b1597719e26ac 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 8c798dd34a9a4..254ce88f6ed03 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 243de44838e10..4b7ce948af067 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 38664c4b62fdf..4bebd9fa27f9e 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 3f237d268f073..a3b2ddd97a17e 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 3ff43ae1b6263..0a1d772cc031c 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 48e497a99004b..70d3cf84c5ef6 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 15f626088ad23..61fe760de5035 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 2e0d73cc43623..02064c4eacc66 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index e9accf72f5a02..86606778f8dfd 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 6309733dc1bab..180ae33f7885a 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index c770dd8671956..7c2f7cc09c77b 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 4b531126964e6..ef8d669ed27aa 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 8073cadd73d80..0e8f9505b35f7 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index c05c4d734c490..c2a9a20196954 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 660e36ef4374e..9662e0b7958fe 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 93828baf05442..f279425a11288 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index fc2ed806bd5a7..70dd586f3b9f7 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index fe8a35b282802..2d4b1f7a3e301 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index b072b928a5fee..f0c472ec736d5 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 2cc7dbb077e4d..421399623af64 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 4c380a6aada55..8c06707332c23 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 4658f66ec9f02..a03912522beaa 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 5c785be7541a2..7d6e18575e25c 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 058bbf15a8534..3d387dbca9d59 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index ffad848d71c2b..80555e578ee94 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index b7a7e0d49898e..0396c24bc40eb 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 9c4fac9fc2223..de93a8fe050a9 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index a08a1f8c35b21..fe8d933d19a34 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 484f22d4d3fad..e9e1e8d327dfd 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 86360f220dc24..96ce8b5f78b19 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index db2dd293c3e4b..2a28c3743374d 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index a8b6bbd605637..7ab80f87e401c 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index e8b72f7cee2d1..21d349683d8e3 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index fc9a2e294a610..b7d40f28432aa 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 75d2cd54a2d8b..0cb5e40b3b4cb 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.devdocs.json b/api_docs/license_api_guard.devdocs.json index a554c2c4b7895..03acec45780d4 100644 --- a/api_docs/license_api_guard.devdocs.json +++ b/api_docs/license_api_guard.devdocs.json @@ -94,13 +94,7 @@ "description": [], "signature": [ "(handler: ", { "pluginId": "core", diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 42f4d0805fc3c..63d15a7c40650 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index ca3e9e1a23c12..88e17bcee6feb 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 67cd87e28a5ce..c7000d51761c4 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index c0fba6cbabbb7..9deb279fb73b6 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 7a332ae0f68be..2cec82774807b 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index c821d5118393a..2667e8f19bae6 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 30e6fc5864b60..558352f89505a 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 83e81edff74a4..b1abe52cefe17 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 8b27dbc3d7849..b9332eb4163e6 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index d0c0e4b930125..23c398899ccbf 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index c8d4e9b1fbf7c..bfa907ab09267 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 79f080105b7b9..4336085d004ce 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index d4d3483ef247c..fd03a2f285ab5 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -7613,13 +7613,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { licensing: Promise<", { "pluginId": "licensing", @@ -7637,13 +7631,7 @@ "text": "AlertingApiRequestHandlerContext" }, ">; core: Promise<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreRequestHandlerContext", - "text": "CoreRequestHandlerContext" - }, + "CoreRequestHandlerContext", ">; }" ], "path": "x-pack/plugins/observability/server/routes/types.ts", @@ -7944,7 +7932,7 @@ "label": "ObservabilityConfig", "description": [], "signature": [ - "{ readonly unsafe: Readonly<{} & { slo: Readonly<{} & { enabled: boolean; }>; alertDetails: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; }" + "{ readonly unsafe: Readonly<{} & { slo: Readonly<{} & { enabled: boolean; }>; alertDetails: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { enabled: boolean; index: string; }>; }" ], "path": "x-pack/plugins/observability/server/index.ts", "deprecated": false, @@ -9798,13 +9786,7 @@ "description": [], "signature": [ "{ getScopedAnnotationsClient: (requestContext: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { licensing: Promise<", { "pluginId": "licensing", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 4c2ca8b17a13f..db3f0906e6142 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 942900853abdb..c569e301708df 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index bcddc85faf5c2..568f38705df0a 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
    public API | Number of teams | |--------------|----------|------------------------| -| 474 | 395 | 38 | +| 477 | 397 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 31860 | 179 | 21432 | 1007 | +| 31953 | 179 | 21502 | 1003 | ## Plugin Directory @@ -30,7 +30,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 214 | 0 | 209 | 19 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 370 | 0 | 361 | 22 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 379 | 0 | 370 | 24 | | | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 52 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 80 | 1 | 71 | 2 | @@ -42,10 +42,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 212 | 0 | 204 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2684 | 0 | 35 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2688 | 0 | 30 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 103 | 0 | 84 | 1 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 144 | 0 | 139 | 10 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 104 | 0 | 85 | 1 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 120 | 0 | 113 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3213 | 33 | 2509 | 23 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 15 | 0 | 7 | 0 | @@ -152,7 +152,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 346 | 33 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 513 | 1 | 486 | 48 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 512 | 1 | 485 | 48 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 132 | 0 | 91 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 61 | 0 | 59 | 2 | @@ -175,7 +175,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 679 | 12 | 649 | 18 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 693 | 12 | 663 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -261,6 +261,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 16 | 0 | 16 | 0 | | | Kibana Core | - | 4 | 0 | 0 | 0 | | | Kibana Core | - | 10 | 1 | 10 | 0 | +| | Kibana Core | - | 14 | 0 | 11 | 0 | | | Kibana Core | - | 25 | 5 | 25 | 1 | | | Kibana Core | - | 7 | 0 | 7 | 1 | | | Kibana Core | - | 392 | 1 | 154 | 0 | @@ -388,6 +389,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 74 | 0 | 71 | 0 | | | [Owner missing] | Security Solution auto complete | 56 | 1 | 41 | 1 | | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | +| | [Owner missing] | - | 76 | 0 | 67 | 1 | | | [Owner missing] | Security Solution utilities for React hooks | 15 | 0 | 7 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 145 | 0 | 127 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 505 | 1 | 492 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 191e4e563cb34..099351a2d0f2e 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index fb048ac82442b..2ef17ce2192fa 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index c0b505ed6394d..8a248f78f1fa8 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 4088e39f97108..9589edc812df2 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 4f8cf9c8e8dbf..1964dc994cd2e 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 424057fd0db70..089f973bc0bf0 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -1917,7 +1917,7 @@ "\nID of the Kibana feature associated with the index.\nUsed by alerts-as-data RBAC.\n\nNote from @dhurley14\nThe purpose of the `feature` param is to force the user to update\nthe data structure which contains the mapping of consumers to alerts\nas data indices. The idea is it is typed such that it forces the\nuser to go to the code and modify it. At least until a better system\nis put in place or we move the alerts as data client out of rule registry.\n" ], "signature": [ - "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" + "\"infrastructure\" | \"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\"" ], "path": "x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts", "deprecated": false, @@ -4270,7 +4270,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" + "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 15c672f3f6fee..d4539ef64efa0 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 001d245e9adf3..8112f8684b552 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.devdocs.json b/api_docs/saved_objects.devdocs.json index 6d40e242e3cd5..71d53634b7540 100644 --- a/api_docs/saved_objects.devdocs.json +++ b/api_docs/saved_objects.devdocs.json @@ -1557,18 +1557,6 @@ "plugin": "savedObjectsTaggingOss", "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/saved_object_loader.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/saved_object_loader.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/saved_object_loader.ts" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/application/actions/clone_panel_action.tsx" @@ -1580,22 +1568,6 @@ { "plugin": "dashboard", "path": "src/plugins/dashboard/public/application/actions/clone_panel_action.tsx" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/lib/dashboard_tagging.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/lib/dashboard_tagging.ts" } ], "children": [ @@ -3121,12 +3093,7 @@ "deprecated": true, "removeBy": "8.8.0", "trackAdoption": false, - "references": [ - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" - } - ] + "references": [] }, { "parentPluginId": "savedObjects", diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 51bc9b36316c4..613f942ef9065 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 3a6b9b0a9e738..23cf7d31ca48e 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 8fb7925eb7808..c9cc4aa61ea7c 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 9a0469a70f642..1450399aefd83 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 8abc78f1fdc7e..1ed5aa9d511ea 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.devdocs.json b/api_docs/saved_search.devdocs.json index df52fe959fea2..8e6b2e5503d3c 100644 --- a/api_docs/saved_search.devdocs.json +++ b/api_docs/saved_search.devdocs.json @@ -661,7 +661,7 @@ "label": "sharingSavedObjectProps", "description": [], "signature": [ - "{ outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" + "{ outcome?: \"conflict\" | \"exactMatch\" | \"aliasMatch\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" ], "path": "src/plugins/saved_search/public/services/saved_searches/types.ts", "deprecated": false, diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index a39c3adbc8200..6c1900781f66a 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.devdocs.json b/api_docs/screenshot_mode.devdocs.json index c6be83e928479..ab834dfc733bc 100644 --- a/api_docs/screenshot_mode.devdocs.json +++ b/api_docs/screenshot_mode.devdocs.json @@ -139,13 +139,7 @@ "label": "ScreenshotModeRequestHandlerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, + "RequestHandlerContext", " & { screenshotMode: Promise<{ isScreenshot: boolean; }>; }" ], "path": "src/plugins/screenshot_mode/server/types.ts", diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 6eaf962f670fa..883f8e26e2cc0 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 8cb0692d0c854..df0feab2c445c 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index e6f2900462223..814d0173f69cf 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 8ad8f2f5fe218..c3ab461b2eb22 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -768,13 +768,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreRequestHandlerContext", - "text": "CoreRequestHandlerContext" - } + "CoreRequestHandlerContext" ], "path": "x-pack/plugins/security_solution/server/types.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index c59c106fce254..a86c48a48dfce 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index ad4253bd0d7e5..66978ad664e04 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index e6f9de45dbb58..0086ad02ab5ac 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 8a45ec4089ad2..7bce31d6e1832 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 94f8821728343..decf22f6da092 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index cb3df93effb09..e68ecfcdfb999 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index a71b61831fb69..5363b913fbe0c 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index b84502a5d8c55..7377ef34d529c 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index dfdbd20746f57..1a95f8384f249 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index edb998b57d6f5..355a38255c9c3 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index c23dd99eb95e3..e607fc3598bd1 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 5e8ae624dfe82..f538309dd4628 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 80ac38a8ed990..bcebf8f8b9342 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 5875aa47c1309..4b5ec5ce28970 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -3192,7 +3192,7 @@ "IFieldSubType", " | undefined; type?: string | undefined; })[]; id: string; title: string; filters?: ", "Filter", - "[] | undefined; dataViewId: string | null; sort: ", + "[] | undefined; dataViewId: string | null; savedObjectId: string | null; sort: ", "SortColumnTimeline", "[]; version: string | null; filterManager?: ", { @@ -3218,7 +3218,7 @@ }, "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", "IFieldSubType", - " | undefined; type?: string | undefined; })[]; savedObjectId: string | null; isLoading: boolean; dataProviders: ", + " | undefined; type?: string | undefined; })[]; isLoading: boolean; dataProviders: ", { "pluginId": "timelines", "scope": "common", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index ecb0a115ea511..7a96d016a99c9 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 70b733e2c869e..7d3c611c084ba 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 316d5941b9d23..d6351e414ae46 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -3139,7 +3139,7 @@ "description": [], "signature": [ "BasicFields", - " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; } & { [x: string]: unknown[]; }" + " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; } & { [x: string]: unknown[]; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, @@ -3585,20 +3585,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.AlertStatus.actionSubgroup", - "type": "string", - "tags": [], - "label": "actionSubgroup", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/alerting/common/alert_summary.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "triggersActionsUi", "id": "def-public.AlertStatus.activeStartDate", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index cfa8f8965332e..e40b4b0c4d440 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 513 | 1 | 486 | 48 | +| 512 | 1 | 485 | 48 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 25fd9d32aac51..9a49946469889 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index f85563ce18a73..e1fd951d5e2a2 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 2aae0fbddf27b..254b1228a31fd 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 261ef25b80e9b..5015d2debdc41 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 1e75d7da1deb9..5a40018e6081e 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index d2cdaf4d89c84..f57d70892e70d 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 5f6baf6d86c5f..f40f15636c943 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 470423b8266a7..d462e4d19616c 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index ff57731114146..0b2993331260b 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 1da13d1fcc094..cc20cda9db105 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index d30f1b6a560aa..609c06b1a92cf 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index f8d894bdaa7de..3f8c15b314a0e 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index deb819de77df3..5aa2bd8b01f5a 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 042fb74b10af4..2219fc96f2e31 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 2458fbb4d9dee..2608ea12c24f9 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 606b4dcf05e16..e535d8ca970e3 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index e56cd5d8f509d..c6bd5fba0b068 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 9ac63dfe30ccc..8caa2a67dab8d 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index adc8f3022cb1a..95b3390b94cea 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -3042,7 +3042,7 @@ "label": "sharingSavedObjectProps", "description": [], "signature": [ - "{ outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" + "{ outcome?: \"conflict\" | \"exactMatch\" | \"aliasMatch\" | undefined; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; errorJSON?: string | undefined; } | undefined" ], "path": "src/plugins/visualizations/public/types.ts", "deprecated": false, @@ -9284,6 +9284,203 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration", + "type": "Interface", + "tags": [], + "label": "MetricVisConfiguration", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"data\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.metricAccessor", + "type": "string", + "tags": [], + "label": "metricAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.secondaryMetricAccessor", + "type": "string", + "tags": [], + "label": "secondaryMetricAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.maxAccessor", + "type": "string", + "tags": [], + "label": "maxAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.breakdownByAccessor", + "type": "string", + "tags": [], + "label": "breakdownByAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.collapseFn", + "type": "string", + "tags": [], + "label": "collapseFn", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.subtitle", + "type": "string", + "tags": [], + "label": "subtitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.secondaryPrefix", + "type": "string", + "tags": [], + "label": "secondaryPrefix", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.progressDirection", + "type": "CompoundType", + "tags": [], + "label": "progressDirection", + "description": [], + "signature": [ + "LayoutDirection", + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.palette", + "type": "Object", + "tags": [], + "label": "palette", + "description": [], + "signature": [ + "PaletteOutput", + "<", + "CustomPaletteParams", + "> | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MetricVisConfiguration.maxCols", + "type": "number", + "tags": [], + "label": "maxCols", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.MovingAverageParams", @@ -11891,6 +12088,14 @@ "docId": "kibVisualizationsPluginApi", "section": "def-common.TableVisConfiguration", "text": "TableVisConfiguration" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MetricVisConfiguration", + "text": "MetricVisConfiguration" } ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index e5907aaf8a8a5..6e29fc3d03f29 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-09-28 +date: 2022-09-29 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 679 | 12 | 649 | 18 | +| 693 | 12 | 663 | 18 | ## Client From ea046acd24a6ec30789c90b6a5c5c1b1148f65ee Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 29 Sep 2022 09:32:55 +0300 Subject: [PATCH 155/172] [Agg based pie][Lens]Navigate to lens Agg based pie (#140879) * Added support for converting Agg-based pie to Lens. * Fixed problem with removed index pattern. * Made some changes for uniformity. * Fixed label of go back to. * Fixed legend display if uiState was changed. * Fixed configuration. * Removed as string[]. * Fixed code according to the nit. * Allowed 3 segments and restricted converting of split_row/split_column. * Fixed tests. * Refactored functional tests. * Added functional tests for pie. Co-authored-by: Yaroslav Kuznietsov Co-authored-by: Joe Reuter --- .../pie_vis_function.test.ts.snap | 2 + .../expression_functions/pie_vis_function.ts | 1 + .../common/types/expression_renderers.ts | 1 + .../partition_vis_renderer.tsx | 13 +- src/plugins/vis_types/pie/kibana.json | 4 +- .../configurations/index.test.ts | 104 ++++++++++ .../convert_to_lens/configurations/index.ts | 79 +++++++ .../pie/public/convert_to_lens/index.test.ts | 77 +++++++ .../pie/public/convert_to_lens/index.ts | 79 +++++++ .../pie/public/convert_to_lens/types.ts | 20 ++ src/plugins/vis_types/pie/public/plugin.ts | 13 +- .../pie/public/sample_vis.test.mocks.ts | 3 + src/plugins/vis_types/pie/public/services.ts | 13 ++ .../vis_types/pie/public/vis_type/pie.ts | 7 + .../common/convert_to_lens/constants.ts | 64 ++++++ .../convert_to_lens/types/configurations.ts | 80 +++++--- .../public/convert_to_lens/schemas.ts | 26 ++- .../components/visualize_top_nav.tsx | 10 +- .../workspace_panel/chart_switch.tsx | 6 +- .../indexpattern_suggestions.test.tsx | 6 +- .../indexpattern_suggestions.ts | 1 + .../operations/layer_helpers.ts | 30 ++- .../visualize_agg_based_vis_actions.ts | 2 +- x-pack/plugins/lens/public/types.ts | 8 +- .../datatable/visualization.tsx | 10 +- .../partition/suggestions.test.ts | 24 ++- .../visualizations/partition/suggestions.ts | 13 +- .../partition/visualization.tsx | 43 ++++ .../visualizations/xy/xy_suggestions.ts | 7 +- .../test/functional/apps/lens/group3/index.ts | 2 +- .../group3/open_in_lens/agg_based/index.ts | 14 ++ .../lens/group3/open_in_lens/agg_based/pie.ts | 68 +++++++ .../apps/lens/group3/open_in_lens/index.ts | 15 ++ .../group3/open_in_lens/tsvb/dashboard.ts | 101 +++++++++ .../lens/group3/open_in_lens/tsvb/index.ts | 16 ++ .../lens/group3/open_in_lens/tsvb/metric.ts | 44 ++++ .../group3/open_in_lens/tsvb/timeseries.ts | 87 ++++++++ .../apps/lens/group3/tsvb_open_in_lens.ts | 192 ------------------ 38 files changed, 1010 insertions(+), 275 deletions(-) create mode 100644 src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts create mode 100644 src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts create mode 100644 src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts create mode 100644 src/plugins/vis_types/pie/public/convert_to_lens/index.ts create mode 100644 src/plugins/vis_types/pie/public/convert_to_lens/types.ts create mode 100644 src/plugins/vis_types/pie/public/services.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/index.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts delete mode 100644 x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap index 0f64f4c0a4779..3fd9966e7524e 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap @@ -26,6 +26,7 @@ Object { "as": "partitionVis", "type": "render", "value": Object { + "canNavigateToLens": false, "params": Object { "listenOnChange": true, }, @@ -160,6 +161,7 @@ Object { "as": "partitionVis", "type": "render", "value": Object { + "canNavigateToLens": false, "params": Object { "listenOnChange": true, }, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts index 5b69fbc6194fd..119d45f579ebf 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts @@ -188,6 +188,7 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({ visConfig, syncColors: handlers?.isSyncColorsEnabled?.() ?? false, visType: args.isDonut ? ChartTypes.DONUT : ChartTypes.PIE, + canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens), params: { listenOnChange: true, }, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts index 2f436de90e138..6a8fd2935ba54 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts @@ -108,6 +108,7 @@ export interface RenderValue { visType: ChartTypes; visConfig: PartitionVisParams; syncColors: boolean; + canNavigateToLens?: boolean; } export enum LabelPositions { diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx index 546b70d98c6eb..4b6fe45e4df92 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx @@ -49,7 +49,11 @@ export const getPartitionVisRenderer: ( displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, - render: async (domNode, { visConfig, visData, visType, syncColors }, handlers) => { + render: async ( + domNode, + { visConfig, visData, visType, syncColors, canNavigateToLens }, + handlers + ) => { const { core, plugins } = getStartDeps(); handlers.onDestroy(() => { @@ -62,9 +66,12 @@ export const getPartitionVisRenderer: ( const visualizationType = extractVisualizationType(executionContext); if (containerType && visualizationType) { - plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [ + const events = [ `render_${visualizationType}_${visType}`, - ]); + canNavigateToLens ? `render_${visualizationType}_${visType}_convertable` : undefined, + ].filter((event): event is string => Boolean(event)); + + plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events); } handlers.done(); }; diff --git a/src/plugins/vis_types/pie/kibana.json b/src/plugins/vis_types/pie/kibana.json index abed576cc6732..4c5ee6b50579e 100644 --- a/src/plugins/vis_types/pie/kibana.json +++ b/src/plugins/vis_types/pie/kibana.json @@ -3,8 +3,8 @@ "version": "kibana", "ui": true, "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis"], - "requiredBundles": ["visDefaultEditor"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis", "dataViews"], + "requiredBundles": ["visDefaultEditor", "kibanaUtils"], "extraPublicDirs": ["common/index"], "owner": { "name": "Vis Editors", diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..0a10a5bd7c0c0 --- /dev/null +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getConfiguration } from '.'; +import { samplePieVis } from '../../sample_vis.test.mocks'; + +describe('getConfiguration', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should return correct configuration', () => { + samplePieVis.uiState.get.mockReturnValueOnce(undefined); + expect( + getConfiguration('test1', samplePieVis as any, { + metrics: ['metric-1'], + buckets: ['bucket-1'], + }) + ).toEqual({ + layers: [ + { + categoryDisplay: undefined, + emptySizeRatio: undefined, + layerId: 'test1', + layerType: 'data', + legendDisplay: 'show', + legendMaxLines: 1, + legendPosition: 'right', + legendSize: 'large', + metric: 'metric-1', + nestedLegend: true, + numberDisplay: 'percent', + percentDecimals: 2, + primaryGroups: ['bucket-1'], + secondaryGroups: [], + showValuesInLegend: true, + truncateLegend: true, + }, + ], + shape: 'donut', + palette: undefined, + }); + }); + + test('should return legendDisplay = show if uiState contains truthy value', () => { + samplePieVis.uiState.get.mockReturnValueOnce(true); + expect( + getConfiguration( + 'test1', + { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'hide' } } as any, + { + metrics: ['metric-1'], + buckets: ['bucket-1'], + } + ) + ).toEqual({ + layers: [expect.objectContaining({ legendDisplay: 'show' })], + shape: 'donut', + palette: undefined, + }); + }); + + test('should return legendDisplay = hide if uiState contains falsy value', () => { + samplePieVis.uiState.get.mockReturnValueOnce(false); + expect( + getConfiguration( + 'test1', + { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'show' } } as any, + { + metrics: ['metric-1'], + buckets: ['bucket-1'], + } + ) + ).toEqual({ + layers: [expect.objectContaining({ legendDisplay: 'hide' })], + shape: 'donut', + palette: undefined, + }); + }); + + test('should return value of legendDisplay if uiState contains undefined value', () => { + samplePieVis.uiState.get.mockReturnValueOnce(undefined); + const legendDisplay = 'show'; + expect( + getConfiguration( + 'test1', + { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay } } as any, + { + metrics: ['metric-1'], + buckets: ['bucket-1'], + } + ) + ).toEqual({ + layers: [expect.objectContaining({ legendDisplay })], + shape: 'donut', + palette: undefined, + }); + }); +}); diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..9a3420581c1fd --- /dev/null +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LegendDisplay, PartitionVisParams } from '@kbn/expression-partition-vis-plugin/common'; +import { + CategoryDisplayTypes, + NumberDisplayTypes, + PartitionVisConfiguration, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Vis } from '@kbn/visualizations-plugin/public'; + +const getLayers = ( + layerId: string, + vis: Vis, + metrics: string[], + buckets: string[] +): PartitionVisConfiguration['layers'] => { + const legendOpen = vis.uiState.get('vis.legendOpen'); + const legendDisplayFromUiState = + legendOpen !== undefined ? (legendOpen ? LegendDisplay.SHOW : LegendDisplay.HIDE) : undefined; + + const showValuesInLegend = + vis.params.labels.values ?? + vis.params.showValuesInLegend ?? + vis.type.visConfig.defaults.showValuesInLegend; + + return [ + { + layerId, + layerType: 'data' as const, + primaryGroups: buckets, + secondaryGroups: [], + metric: metrics[0], + numberDisplay: + showValuesInLegend === false + ? NumberDisplayTypes.HIDDEN + : vis.params.labels.valuesFormat ?? vis.type.visConfig.defaults.labels.valuesFormat, + categoryDisplay: vis.params.labels.show + ? vis.params.labels.position ?? vis.type.visConfig.defaults.labels.position + : CategoryDisplayTypes.HIDE, + legendDisplay: + legendDisplayFromUiState ?? + vis.params.legendDisplay ?? + vis.type.visConfig.defaults.legendDisplay, + legendPosition: vis.params.legendPosition ?? vis.type.visConfig.defaults.legendPosition, + showValuesInLegend, + nestedLegend: vis.params.nestedLegend ?? vis.type.visConfig.defaults.nestedLegend, + percentDecimals: + vis.params.labels.percentDecimals ?? vis.type.visConfig.defaults.labels.percentDecimals, + emptySizeRatio: vis.params.emptySizeRatio ?? vis.type.visConfig.defaults.emptySizeRatio, + legendMaxLines: vis.params.maxLegendLines ?? vis.type.visConfig.defaults.maxLegendLines, + legendSize: vis.params.legendSize ?? vis.type.visConfig.defaults.legendSize, + truncateLegend: vis.params.truncateLegend ?? vis.type.visConfig.defaults.truncateLegend, + }, + ]; +}; + +export const getConfiguration = ( + layerId: string, + vis: Vis, + { + metrics, + buckets, + }: { + metrics: string[]; + buckets: string[]; + } +): PartitionVisConfiguration => { + return { + shape: vis.params.isDonut ? 'donut' : 'pie', + layers: getLayers(layerId, vis, metrics, buckets), + palette: vis.params.palette, + }; +}; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..c1e39d741f84d --- /dev/null +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { convertToLens } from '.'; +import { samplePieVis } from '../sample_vis.test.mocks'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + }), + getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +describe('convertToLens', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(samplePieVis as any, {} as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if more than three split slice levels', async () => { + mockGetColumnsFromVis.mockReturnValue({ + buckets: ['1', '2', '3', '4'], + }); + const result = await convertToLens(samplePieVis as any, {} as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if no one split slices', async () => { + mockGetColumnsFromVis.mockReturnValue({ + buckets: [], + }); + const result = await convertToLens(samplePieVis as any, {} as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should state for valid vis', async () => { + mockGetColumnsFromVis.mockReturnValue({ + buckets: ['2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }); + const result = await convertToLens(samplePieVis as any, {} as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsPie'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '2' }, { columnId: '1' }], + }) + ); + }); +}); diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..5b1973507c7df --- /dev/null +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getConfiguration } from './configurations'; +import { ConvertPieToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const { getColumnsFromVis } = await convertToLensModule; + const result = getColumnsFromVis(vis, timefilter, dataView, { + buckets: [], + splits: ['segment'], + unsupported: ['split_row', 'split_column'], + }); + + if (result === null) { + return null; + } + + // doesn't support more than three split slice levels + // doesn't support pie without at least one split slice + if (result.buckets.length > 3 || !result.buckets.length) { + return null; + } + + const layerId = uuid(); + + const indexPatternId = dataView.id!; + return { + type: 'lnsPie', + layers: [ + { + indexPatternId, + layerId, + columns: result.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration: getConfiguration(layerId, vis, result), + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/types.ts b/src/plugins/vis_types/pie/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..b190a4c891304 --- /dev/null +++ b/src/plugins/vis_types/pie/public/convert_to_lens/types.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { PartitionVisParams } from '@kbn/expression-partition-vis-plugin/common'; +import { + NavigateToLensContext, + PartitionVisConfiguration, +} from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; + +export type ConvertPieToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/pie/public/plugin.ts b/src/plugins/vis_types/pie/public/plugin.ts index 480cf0c49db63..b4a8c0e3a2a69 100644 --- a/src/plugins/vis_types/pie/public/plugin.ts +++ b/src/plugins/vis_types/pie/public/plugin.ts @@ -6,13 +6,15 @@ * Side Public License, v 1. */ -import { CoreSetup, DocLinksStart, ThemeServiceStart } from '@kbn/core/public'; +import { CoreSetup, CoreStart, DocLinksStart, ThemeServiceStart } from '@kbn/core/public'; import { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LEGACY_PIE_CHARTS_LIBRARY } from '../common'; import { pieVisType } from './vis_type'; +import { setDataViewsStart } from './services'; /** @internal */ export interface VisTypePieSetupDependencies { @@ -21,6 +23,11 @@ export interface VisTypePieSetupDependencies { usageCollection: UsageCollectionSetup; } +/** @internal */ +export interface VisTypePieStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + /** @internal */ export interface VisTypePiePluginStartDependencies { data: DataPublicPluginStart; @@ -53,5 +60,7 @@ export class VisTypePiePlugin { return {}; } - start() {} + start(core: CoreStart, { dataViews }: VisTypePieStartDependencies) { + setDataViewsStart(dataViews); + } } diff --git a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts index 3525b7b6fbc05..035432de9ad23 100644 --- a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts @@ -9,6 +9,8 @@ import { LegendDisplay } from '@kbn/expression-partition-vis-plugin/common'; import { LegendSize } from '@kbn/visualizations-plugin/common'; +const mockUiStateGet = jest.fn().mockReturnValue(() => false); + export const samplePieVis = { type: { name: 'pie', @@ -1353,5 +1355,6 @@ export const samplePieVis = { vis: { legendOpen: false, }, + get: mockUiStateGet, }, }; diff --git a/src/plugins/vis_types/pie/public/services.ts b/src/plugins/vis_types/pie/public/services.ts new file mode 100644 index 0000000000000..736ad70d49419 --- /dev/null +++ b/src/plugins/vis_types/pie/public/services.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/pie/public/vis_type/pie.ts b/src/plugins/vis_types/pie/public/vis_type/pie.ts index 6d48507cf47a3..8d4b7b6828e39 100644 --- a/src/plugins/vis_types/pie/public/vis_type/pie.ts +++ b/src/plugins/vis_types/pie/public/vis_type/pie.ts @@ -21,6 +21,7 @@ import { DEFAULT_PERCENT_DECIMALS } from '../../common'; import { PieTypeProps } from '../types'; import { toExpressionAst } from '../to_ast'; import { getPieOptions } from '../editor/components'; +import { convertToLens } from '../convert_to_lens'; export const getPieVisTypeDefinition = ({ showElasticChartsOptions = false, @@ -123,4 +124,10 @@ export const getPieVisTypeDefinition = ({ }, hierarchicalData: true, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }); diff --git a/src/plugins/visualizations/common/convert_to_lens/constants.ts b/src/plugins/visualizations/common/convert_to_lens/constants.ts index fa92b881033c7..12ed815bc7247 100644 --- a/src/plugins/visualizations/common/convert_to_lens/constants.ts +++ b/src/plugins/visualizations/common/convert_to_lens/constants.ts @@ -36,6 +36,70 @@ export const OperationsWithReferences = { export const Operations = { ...OperationsWithSourceField, ...OperationsWithReferences } as const; +export const PartitionChartTypes = { + PIE: 'pie', + DONUT: 'donut', + TREEMAP: 'treemap', + MOSAIC: 'mosaic', + WAFFLE: 'waffle', +} as const; + +export const CategoryDisplayTypes = { + DEFAULT: 'default', + INSIDE: 'inside', + HIDE: 'hide', +} as const; + +export const NumberDisplayTypes = { + HIDDEN: 'hidden', + PERCENT: 'percent', + VALUE: 'value', +} as const; + +export const LegendDisplayTypes = { + DEFAULT: 'default', + SHOW: 'show', + HIDE: 'hide', +} as const; + +export const LayerTypes = { + DATA: 'data', + REFERENCELINE: 'referenceLine', + ANNOTATIONS: 'annotations', +} as const; + +export const XYCurveTypes = { + LINEAR: 'LINEAR', + CURVE_MONOTONE_X: 'CURVE_MONOTONE_X', + CURVE_STEP_AFTER: 'CURVE_STEP_AFTER', +} as const; + +export const YAxisModes = { + AUTO: 'auto', + LEFT: 'left', + RIGHT: 'right', + BOTTOM: 'bottom', +} as const; + +export const SeriesTypes = { + BAR: 'bar', + LINE: 'line', + AREA: 'area', + BAR_STACKED: 'bar_stacked', + AREA_STACKED: 'area_stacked', + BAR_HORIZONTAL: 'bar_horizontal', + BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked', + BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked', + AREA_PERCENTAGE_STACKED: 'area_percentage_stacked', + BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked', +} as const; + +export const FillTypes = { + NONE: 'none', + ABOVE: 'above', + BELOW: 'below', +} as const; + export const RANGE_MODES = { Range: 'range', Histogram: 'histogram', diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index fc1e440db3506..a6a771d07e7a6 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -11,43 +11,27 @@ import { $Values } from '@kbn/utility-types'; import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { KibanaQueryOutput } from '@kbn/data-plugin/common'; import { LegendSize } from '../../constants'; - -export const XYCurveTypes = { - LINEAR: 'LINEAR', - CURVE_MONOTONE_X: 'CURVE_MONOTONE_X', - CURVE_STEP_AFTER: 'CURVE_STEP_AFTER', -} as const; - -export const YAxisModes = { - AUTO: 'auto', - LEFT: 'left', - RIGHT: 'right', - BOTTOM: 'bottom', -} as const; - -export const SeriesTypes = { - BAR: 'bar', - LINE: 'line', - AREA: 'area', - BAR_STACKED: 'bar_stacked', - AREA_STACKED: 'area_stacked', - BAR_HORIZONTAL: 'bar_horizontal', - BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked', - BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked', - AREA_PERCENTAGE_STACKED: 'area_percentage_stacked', - BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked', -} as const; - -export const FillTypes = { - NONE: 'none', - ABOVE: 'above', - BELOW: 'below', -} as const; +import { + CategoryDisplayTypes, + PartitionChartTypes, + NumberDisplayTypes, + LegendDisplayTypes, + FillTypes, + SeriesTypes, + YAxisModes, + XYCurveTypes, + LayerTypes, +} from '../constants'; export type FillType = $Values; export type SeriesType = $Values; export type YAxisMode = $Values; export type XYCurveType = $Values; +export type PartitionChartType = $Values; +export type CategoryDisplayType = $Values; +export type NumberDisplayType = $Values; +export type LegendDisplayType = $Values; +export type LayerType = $Values; export interface AxisExtentConfig { mode: 'full' | 'custom' | 'dataBounds'; @@ -217,4 +201,34 @@ export interface MetricVisConfiguration { maxCols?: number; } -export type Configuration = XYConfiguration | TableVisConfiguration | MetricVisConfiguration; +export interface PartitionLayerState { + layerId: string; + layerType: LayerType; + primaryGroups: string[]; + secondaryGroups?: string[]; + metric?: string; + collapseFns?: Record; + numberDisplay: NumberDisplayType; + categoryDisplay: CategoryDisplayType; + legendDisplay: LegendDisplayType; + legendPosition?: Position; + showValuesInLegend?: boolean; + nestedLegend?: boolean; + percentDecimals?: number; + emptySizeRatio?: number; + legendMaxLines?: number; + legendSize?: LegendSize; + truncateLegend?: boolean; +} + +export interface PartitionVisConfiguration { + shape: PartitionChartType; + layers: PartitionLayerState[]; + palette?: PaletteOutput; +} + +export type Configuration = + | XYConfiguration + | TableVisConfiguration + | PartitionVisConfiguration + | MetricVisConfiguration; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 372e434ca8868..e9b467074b6f1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -24,14 +24,26 @@ import { sortColumns, } from './utils'; +const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array) => { + const usedUnsupportedSchemas = unsupported.filter( + (schema) => visSchemas[schema] && visSchemas[schema]?.length + ); + return !usedUnsupportedSchemas.length; +}; + export const getColumnsFromVis = ( vis: Vis, timefilter: TimefilterContract, dataView: DataView, - { splits, buckets }: { splits: Array; buckets: Array } = { - splits: [], - buckets: [], - }, + { + splits = [], + buckets = [], + unsupported = [], + }: { + splits?: Array; + buckets?: Array; + unsupported?: Array; + } = {}, config?: { dropEmptyRowsInDateHistogram?: boolean; } @@ -41,7 +53,7 @@ export const getColumnsFromVis = ( timeRange: timefilter.getAbsoluteTime(), }); - if (!isValidVis(visSchemas)) { + if (!isValidVis(visSchemas) || !areVisSchemasValid(visSchemas, unsupported)) { return null; } @@ -111,8 +123,8 @@ export const getColumnsFromVis = ( const columnsWithoutReferenced = getColumnsWithoutReferenced(columns); return { - metrics: getColumnIds(metrics), - buckets: getColumnIds([...bucketColumns, ...splitBucketColumns, ...customBucketColumns]), + metrics: getColumnIds(columnsWithoutReferenced.filter((с) => !с.isBucketed)), + buckets: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)), bucketCollapseFn: getBucketCollapseFn(visSchemas.metric, customBucketColumns), columnsWithoutReferenced, columns, diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index 500a5e5b34d41..055b326b8f19f 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -96,6 +96,7 @@ const TopNav = ({ [doReload] ); + const uiStateJSON = useMemo(() => vis.uiState.toJSON(), [vis.uiState]); useEffect(() => { const asyncGetTriggerContext = async () => { if (vis.type.navigateToLens) { @@ -107,7 +108,14 @@ const TopNav = ({ } }; asyncGetTriggerContext(); - }, [services.data.query.timefilter.timefilter, vis, vis.type, vis.params, vis.data.indexPattern]); + }, [ + services.data.query.timefilter.timefilter, + vis, + vis.type, + vis.params, + uiStateJSON?.vis, + vis.data.indexPattern, + ]); const displayEditInLensItem = Boolean(vis.type.navigateToLens && editInLensConfig); const config = useMemo(() => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 2e81f9443ff15..dc09baf01fb2a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -534,12 +534,8 @@ function getTopSuggestion( ); }); - // We prefer unchanged or reduced suggestions when switching - // charts since that allows you to switch from A to B and back - // to A with the greatest chance of preserving your original state. return ( - suggestions.find((s) => s.changeType === 'unchanged') || - suggestions.find((s) => s.changeType === 'reduced') || + suggestions.find((s) => s.changeType === 'unchanged' || s.changeType === 'reduced') || suggestions[0] ); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 860c272d9ee19..382c11bda545a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1774,7 +1774,7 @@ describe('IndexPattern Data Source suggestions', () => { state: expect.objectContaining({ layers: { test: expect.objectContaining({ - columnOrder: ['column-id-3', 'column-id-2', 'column-id-1'], + columnOrder: ['column-id-2', 'column-id-3', 'column-id-1'], columns: { 'column-id-1': expect.objectContaining({ operationType: 'count', @@ -1804,10 +1804,10 @@ describe('IndexPattern Data Source suggestions', () => { isMultiRow: true, columns: [ expect.objectContaining({ - columnId: 'column-id-3', + columnId: 'column-id-2', }), expect.objectContaining({ - columnId: 'column-id-2', + columnId: 'column-id-3', }), expect.objectContaining({ columnId: 'column-id-1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index fed70d2970b1e..8503c369f4ec2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -341,6 +341,7 @@ function createNewLayerWithMetricAggregationFromVizEditor( newLayer = insertNewColumn({ ...column, layer: newLayer, + respectOrder: true, }); } }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 2d8b41ce866b4..b3f1b3bc7d5f3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -68,6 +68,7 @@ interface ColumnChange { columnParams?: Record; initialParams?: { params: Record }; // TODO: bind this to the op parameter references?: Array>; + respectOrder?: boolean; } interface ColumnCopy { @@ -362,6 +363,7 @@ export function insertNewColumn({ columnParams, initialParams, references, + respectOrder, }: ColumnChange): IndexPatternLayer { const operationDefinition = operationDefinitionMap[op]; @@ -394,7 +396,14 @@ export function insertNewColumn({ : operationDefinition.buildColumn({ ...baseOptions, layer }); return updateDefaultLabels( - addOperationFn(layer, buildColumnFn, columnId, visualizationGroups, targetGroup), + addOperationFn( + layer, + buildColumnFn, + columnId, + visualizationGroups, + targetGroup, + respectOrder + ), indexPattern ); } @@ -445,7 +454,14 @@ export function insertNewColumn({ ) : operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, referenceIds }); return updateDefaultLabels( - addOperationFn(tempLayer, buildColumnFn, columnId, visualizationGroups, targetGroup), + addOperationFn( + tempLayer, + buildColumnFn, + columnId, + visualizationGroups, + targetGroup, + respectOrder + ), indexPattern ); } @@ -468,7 +484,8 @@ export function insertNewColumn({ operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), columnId, visualizationGroups, - targetGroup + targetGroup, + respectOrder ), indexPattern ); @@ -508,7 +525,7 @@ export function insertNewColumn({ const isBucketed = Boolean(possibleOperation.isBucketed); const addOperationFn = isBucketed ? addBucket : addMetric; return updateDefaultLabels( - addOperationFn(layer, newColumn, columnId, visualizationGroups, targetGroup), + addOperationFn(layer, newColumn, columnId, visualizationGroups, targetGroup, respectOrder), indexPattern ); } @@ -1154,7 +1171,8 @@ function addBucket( column: BaseIndexPatternColumn, addedColumnId: string, visualizationGroups: VisualizationDimensionGroupConfig[], - targetGroup?: string + targetGroup?: string, + respectOrder?: boolean ): IndexPatternLayer { const [buckets, metrics] = partition( layer.columnOrder, @@ -1166,7 +1184,7 @@ function addBucket( ); let updatedColumnOrder: string[] = []; - if (oldDateHistogramIndex > -1 && column.operationType === 'terms') { + if (oldDateHistogramIndex > -1 && column.operationType === 'terms' && !respectOrder) { // Insert the new terms bucket above the first date histogram updatedColumnOrder = [ ...buckets.slice(0, oldDateHistogramIndex), diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts index 2d2b329a7ea67..a5300d550f2c7 100644 --- a/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_agg_based_vis_actions.ts @@ -35,7 +35,7 @@ export const visualizeAggBasedVisAction = (application: ApplicationStart) => type: ACTION_CONVERT_TO_LENS, payload, originatingApp: i18n.translate('xpack.lens.AggBasedLabel', { - defaultMessage: 'Aggregation based visualization', + defaultMessage: 'aggregation based visualization', }), }, }); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index af2dcc601c996..fc1dc5ed1661c 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -17,7 +17,7 @@ import type { IInterpreterRenderHandlers, Datatable, } from '@kbn/expressions-plugin/public'; -import type { NavigateToLensContext } from '@kbn/visualizations-plugin/common'; +import type { Configuration, NavigateToLensContext } from '@kbn/visualizations-plugin/common'; import { Adapters } from '@kbn/inspector-plugin/public'; import type { Query } from '@kbn/es-query'; import type { @@ -217,13 +217,13 @@ export interface InitializationOptions { isFullEditor?: boolean; } -export type VisualizeEditorContext = { +export type VisualizeEditorContext = { savedObjectId?: string; embeddableId?: string; vizEditorOriginatingAppUrl?: string; originatingApp?: string; isVisualizeAction: boolean; -} & NavigateToLensContext; +} & NavigateToLensContext; export interface GetDropPropsArgs { state: T; @@ -1129,7 +1129,7 @@ export interface Visualization { getSuggestionFromConvertToLensContext?: ( props: VisualizationStateFromContextChangeProps - ) => Suggestion; + ) => Suggestion | undefined; } // Use same technique as TriggerContext diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index 72bf5812b7157..4ef58c22969ec 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -161,11 +161,19 @@ export const getDatatableVisualization = ({ }, }); + const changeType = table.changeType; + const changeFactor = + changeType === 'reduced' || changeType === 'layers' + ? 0.3 + : changeType === 'unchanged' + ? 0.5 + : 1; + return [ { title, // table with >= 10 columns will have a score of 0.4, fewer columns reduce score - score: (Math.min(table.columns.length, 10) / 10) * 0.4, + score: (Math.min(table.columns.length, 10) / 10) * 0.4 * changeFactor, state: { ...(state || {}), layerId: table.layerId, diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts index eea673dc531d3..dc0ce54b5bc09 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts @@ -93,7 +93,7 @@ describe('suggestions', () => { ).toHaveLength(0); }); - it('should reject date operations', () => { + it('should hide date operations', () => { expect( suggestions({ table: { @@ -118,11 +118,17 @@ describe('suggestions', () => { }, state: undefined, keptLayerIds: ['first'], - }) - ).toHaveLength(0); + }).map((s) => [s.hide, s.score]) + ).toEqual([ + [true, 0], + [true, 0], + [true, 0], + [true, 0], + [true, 0], + ]); }); - it('should reject histogram operations', () => { + it('should hide histogram operations', () => { expect( suggestions({ table: { @@ -147,8 +153,14 @@ describe('suggestions', () => { }, state: undefined, keptLayerIds: ['first'], - }) - ).toHaveLength(0); + }).map((s) => [s.hide, s.score]) + ).toEqual([ + [true, 0], + [true, 0], + [true, 0], + [true, 0], + [true, 0], + ]); }); it('should not reject histogram operations in case of switching between partition charts', () => { diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts index 36e367ca3ad6f..9f2d0920983ad 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts @@ -29,15 +29,10 @@ function hasIntervalScale(columns: TableSuggestionColumn[]) { } function shouldReject({ table, keptLayerIds, state }: SuggestionRequest) { - // Histograms are not good for pi. But we should not reject them on switching between partition charts. - const shouldRejectIntervals = - state?.shape && isPartitionShape(state.shape) ? false : hasIntervalScale(table.columns); - return ( keptLayerIds.length > 1 || (keptLayerIds.length && table.layerId !== keptLayerIds[0]) || table.changeType === 'reorder' || - shouldRejectIntervals || table.columns.some((col) => col.operation.isStaticValue) ); } @@ -111,6 +106,10 @@ export function suggestions({ const results: Array> = []; + // Histograms are not good for pi. But we should not hide suggestion on switching between partition charts. + const shouldHideSuggestion = + state?.shape && isPartitionShape(state.shape) ? false : hasIntervalScale(table.columns); + if ( groups.length <= PartitionChartsMeta.pie.maxBuckets && !hasCustomSuggestionsExists(subVisualizationId) @@ -309,11 +308,11 @@ export function suggestions({ return [...results] .map((suggestion) => ({ ...suggestion, - score: suggestion.score + 0.05 * groups.length, + score: shouldHideSuggestion ? 0 : suggestion.score + 0.05 * groups.length, })) .sort((a, b) => b.score - a.score) .map((suggestion) => ({ ...suggestion, - hide: incompleteConfiguration || suggestion.hide, + hide: shouldHideSuggestion || incompleteConfiguration || suggestion.hide, })); } diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 6a58d46b34caa..a60578aa1f0ba 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -14,11 +14,14 @@ import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { EuiSpacer } from '@elastic/eui'; +import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/convert_to_lens'; import type { Visualization, OperationMetadata, AccessorConfig, VisualizationDimensionGroupConfig, + Suggestion, + VisualizeEditorContext, } from '../../types'; import { getSortedGroups, toExpression, toPreviewExpression } from './to_expression'; import { CategoryDisplay, layerTypes, LegendDisplay, NumberDisplay } from '../../../common'; @@ -27,6 +30,17 @@ import { PartitionChartsMeta } from './partition_charts_meta'; import { DimensionEditor, PieToolbar } from './toolbar'; import { checkTableForContainsSmallValues } from './render_helpers'; import { PieChartTypes, PieLayerState, PieVisualizationState } from '../../../common'; +import { IndexPatternLayer } from '../..'; + +interface DatatableDatasourceState { + [prop: string]: unknown; + layers: IndexPatternLayer[]; +} + +export interface PartitionSuggestion extends Suggestion { + datasourceState: DatatableDatasourceState; + visualizationState: PieVisualizationState; +} function newLayerState(layerId: string): PieLayerState { return { @@ -42,6 +56,12 @@ function newLayerState(layerId: string): PieLayerState { }; } +function isPartitionVisConfiguration( + context: VisualizeEditorContext +): context is VisualizeEditorContext { + return context.type === 'lnsPie'; +} + const bucketedOperations = (op: OperationMetadata) => op.isBucketed; const numberMetricOperations = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number' && !op.isStaticValue; @@ -422,6 +442,29 @@ export const getPieVisualization = ({ return warningMessages; }, + getSuggestionFromConvertToLensContext(props) { + const context = props.context; + if (!isPartitionVisConfiguration(context)) { + return; + } + if (!props.suggestions.length) { + return; + } + const suggestionByShape = (props.suggestions as PartitionSuggestion[]).find( + (suggestion) => suggestion.visualizationState.shape === context.configuration.shape + ); + if (!suggestionByShape) { + return; + } + return { + ...suggestionByShape, + visualizationState: { + ...suggestionByShape.visualizationState, + ...context.configuration, + }, + }; + }, + getErrorMessages(state) { const hasTooManyBucketDimensions = state.layers .map( diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts index 1184712ae033a..b63bfeb5fbd9b 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts @@ -615,7 +615,12 @@ function getScore( changeType: TableChangeType ) { // Unchanged table suggestions half the score because the underlying data doesn't change - const changeFactor = changeType === 'unchanged' ? 0.5 : 1; + const changeFactor = + changeType === 'reduced' || changeType === 'layers' + ? 0.3 + : changeType === 'unchanged' + ? 0.5 + : 1; // chart with multiple y values and split series will have a score of 1, single y value and no split series reduce score return (((yValues.length > 1 ? 2 : 1) + (splitBy ? 1 : 0)) / 3) * changeFactor; } diff --git a/x-pack/test/functional/apps/lens/group3/index.ts b/x-pack/test/functional/apps/lens/group3/index.ts index 44365d5333d16..627e9d560ca21 100644 --- a/x-pack/test/functional/apps/lens/group3/index.ts +++ b/x-pack/test/functional/apps/lens/group3/index.ts @@ -86,7 +86,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./error_handling')); loadTestFile(require.resolve('./lens_tagging')); loadTestFile(require.resolve('./lens_reporting')); - loadTestFile(require.resolve('./tsvb_open_in_lens')); + loadTestFile(require.resolve('./open_in_lens')); // keep these two last in the group in this order because they are messing with the default saved objects loadTestFile(require.resolve('./rollup')); loadTestFile(require.resolve('./no_data')); diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts new file mode 100644 index 0000000000000..b279f0d8a93cd --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/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 { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Agg based Vis to Lens', function () { + loadTestFile(require.resolve('./pie')); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts new file mode 100644 index 0000000000000..1640a6a14631d --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.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. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visEditor, lens, timePicker, header } = getPageObjects([ + 'visualize', + 'lens', + 'visEditor', + 'timePicker', + 'header', + ]); + + const testSubjects = getService('testSubjects'); + const pieChart = getService('pieChart'); + + describe('Pie', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickPieChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should hide the "Edit Visualization in Lens" menu item if no split slices were defined', async () => { + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(false); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + await visEditor.clickBucket('Split slices'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await header.waitUntilLoadingHasFinished(); + await visEditor.clickGo(isNewChartsLibraryEnabled); + + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(true); + }); + + it('should convert to Lens', async () => { + const expectedTableData = ['ios', 'osx', 'win 7', 'win 8', 'win xp']; + await visEditor.clickBucket('Split slices'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await header.waitUntilLoadingHasFinished(); + await visEditor.clickGo(isNewChartsLibraryEnabled); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('partitionVisChart'); + + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/index.ts new file mode 100644 index 0000000000000..b1d5a1cbb3c52 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/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. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Open in Lens', function () { + loadTestFile(require.resolve('./tsvb')); + loadTestFile(require.resolve('./agg_based')); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts new file mode 100644 index 0000000000000..bd74e109326bf --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts @@ -0,0 +1,101 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens, timeToVisualize, dashboard, canvas } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'lens', + 'timeToVisualize', + 'dashboard', + 'canvas', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const panelActions = getService('dashboardPanelActions'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + describe('Dashboard to TSVB to Lens', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + it('should convert a by value TSVB viz to a Lens viz', async () => { + await visualize.navigateToNewVisualization(); + await visualize.clickVisualBuilder(); + await visualBuilder.checkVisualBuilderIsPresent(); + await visualBuilder.resetPage(); + await testSubjects.click('visualizeSaveButton'); + + await timeToVisualize.saveFromModal('My TSVB to Lens viz 1', { + addToDashboard: 'new', + saveToLibrary: false, + }); + + await dashboard.waitForRenderComplete(); + const originalEmbeddableCount = await canvas.getEmbeddableCount(); + await panelActions.openContextMenu(); + await panelActions.clickEdit(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(await dimensions[1].getVisibleText()).to.be('Count of records'); + }); + + await lens.saveAndReturn(); + await retry.try(async () => { + const embeddableCount = await canvas.getEmbeddableCount(); + expect(embeddableCount).to.eql(originalEmbeddableCount); + }); + await panelActions.removePanel(); + }); + + it('should convert a by reference TSVB viz to a Lens viz', async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickVisType('metrics'); + await testSubjects.click('visualizesaveAndReturnButton'); + // save it to library + const originalPanel = await testSubjects.find('embeddablePanelHeading-'); + await panelActions.saveToLibrary('My TSVB to Lens viz 2', originalPanel); + + await dashboard.waitForRenderComplete(); + const originalEmbeddableCount = await canvas.getEmbeddableCount(); + await panelActions.openContextMenu(); + await panelActions.clickEdit(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('legacyMtrVis'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(await dimensions[1].getVisibleText()).to.be('Count of records'); + }); + + await lens.saveAndReturn(); + await retry.try(async () => { + const embeddableCount = await canvas.getEmbeddableCount(); + expect(embeddableCount).to.eql(originalEmbeddableCount); + }); + + const panel = await testSubjects.find(`embeddablePanelHeading-`); + const descendants = await testSubjects.findAllDescendant( + 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', + panel + ); + expect(descendants.length).to.equal(0); + + await panelActions.removePanel(); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts new file mode 100644 index 0000000000000..c786ed22c6a1a --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/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. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('TSVB to Lens', function () { + loadTestFile(require.resolve('./metric')); + loadTestFile(require.resolve('./timeseries')); + loadTestFile(require.resolve('./dashboard')); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts new file mode 100644 index 0000000000000..273473b67a31e --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens } = getPageObjects(['visualBuilder', 'visualize', 'lens']); + + const testSubjects = getService('testSubjects'); + + describe('Metric', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + beforeEach(async () => { + await visualize.navigateToNewVisualization(); + await visualize.clickVisualBuilder(); + await visualBuilder.checkVisualBuilderIsPresent(); + await visualBuilder.resetPage(); + await visualBuilder.clickMetric(); + await visualBuilder.clickDataTab('metric'); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(true); + }); + + it('should convert to Lens', async () => { + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('mtrVis'); + + const metricData = await lens.getMetricVisualizationData(); + expect(metricData[0].title).to.eql('Count of records'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts new file mode 100644 index 0000000000000..17e3a6b1bee8a --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts @@ -0,0 +1,87 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens, header } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'header', + 'lens', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const find = getService('find'); + const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); + + describe('Time Series', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => { + await visualize.navigateToNewVisualization(); + await visualize.clickVisualBuilder(); + await visualBuilder.checkVisualBuilderIsPresent(); + await visualBuilder.resetPage(); + const isMenuItemVisible = await find.existsByCssSelector( + '[data-test-subj="visualizeEditInLensButton"]' + ); + expect(isMenuItemVisible).to.be(true); + }); + + it('visualizes field to Lens and loads fields to the dimesion editor', async () => { + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[0].getVisibleText()).to.be('@timestamp'); + expect(await dimensions[1].getVisibleText()).to.be('Count of records'); + }); + }); + + it('navigates back to TSVB when the Back button is clicked', async () => { + const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); + goBackBtn.click(); + await visualBuilder.checkVisualBuilderIsPresent(); + await retry.try(async () => { + const actualCount = await visualBuilder.getRhythmChartLegendValue(); + expect(actualCount).to.be('56'); + }); + }); + + it('should preserve app filters in lens', async () => { + await filterBar.addFilter('extension', 'is', 'css'); + await header.waitUntilLoadingHasFinished(); + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + + expect(await filterBar.hasFilter('extension', 'css')).to.be(true); + }); + + it('should preserve query in lens', async () => { + const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); + goBackBtn.click(); + await visualBuilder.checkVisualBuilderIsPresent(); + await queryBar.setQuery('machine.os : ios'); + await queryBar.submitQuery(); + await header.waitUntilLoadingHasFinished(); + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + + expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts b/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts deleted file mode 100644 index 173ab1c4fdf04..0000000000000 --- a/x-pack/test/functional/apps/lens/group3/tsvb_open_in_lens.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { visualize, visualBuilder, header, lens, timeToVisualize, dashboard, canvas } = - getPageObjects([ - 'visualBuilder', - 'visualize', - 'header', - 'lens', - 'timeToVisualize', - 'dashboard', - 'canvas', - ]); - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const panelActions = getService('dashboardPanelActions'); - const retry = getService('retry'); - const filterBar = getService('filterBar'); - const queryBar = getService('queryBar'); - - describe('TSVB to Lens', function describeIndexTests() { - before(async () => { - await visualize.initTests(); - }); - - describe('Time Series', () => { - it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => { - await visualize.navigateToNewVisualization(); - await visualize.clickVisualBuilder(); - await visualBuilder.checkVisualBuilderIsPresent(); - await visualBuilder.resetPage(); - const isMenuItemVisible = await find.existsByCssSelector( - '[data-test-subj="visualizeEditInLensButton"]' - ); - expect(isMenuItemVisible).to.be(true); - }); - - it('visualizes field to Lens and loads fields to the dimesion editor', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('xyVisChart'); - await retry.try(async () => { - const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); - expect(dimensions).to.have.length(2); - expect(await dimensions[0].getVisibleText()).to.be('@timestamp'); - expect(await dimensions[1].getVisibleText()).to.be('Count of records'); - }); - }); - - it('navigates back to TSVB when the Back button is clicked', async () => { - const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); - goBackBtn.click(); - await visualBuilder.checkVisualBuilderIsPresent(); - await retry.try(async () => { - const actualCount = await visualBuilder.getRhythmChartLegendValue(); - expect(actualCount).to.be('56'); - }); - }); - - it('should preserve app filters in lens', async () => { - await filterBar.addFilter('extension', 'is', 'css'); - await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('xyVisChart'); - - expect(await filterBar.hasFilter('extension', 'css')).to.be(true); - }); - - it('should preserve query in lens', async () => { - const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); - goBackBtn.click(); - await visualBuilder.checkVisualBuilderIsPresent(); - await queryBar.setQuery('machine.os : ios'); - await queryBar.submitQuery(); - await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('xyVisChart'); - - expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); - }); - }); - - describe('Metric', () => { - beforeEach(async () => { - await visualize.navigateToNewVisualization(); - await visualize.clickVisualBuilder(); - await visualBuilder.checkVisualBuilderIsPresent(); - await visualBuilder.resetPage(); - await visualBuilder.clickMetric(); - await visualBuilder.clickDataTab('metric'); - }); - - it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); - }); - - it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('mtrVis'); - - const metricData = await lens.getMetricVisualizationData(); - expect(metricData[0].title).to.eql('Count of records'); - }); - }); - - describe('Dashboard to TSVB to Lens', () => { - it('should convert a by value TSVB viz to a Lens viz', async () => { - await visualize.navigateToNewVisualization(); - await visualize.clickVisualBuilder(); - await visualBuilder.checkVisualBuilderIsPresent(); - await visualBuilder.resetPage(); - await testSubjects.click('visualizeSaveButton'); - - await timeToVisualize.saveFromModal('My TSVB to Lens viz 1', { - addToDashboard: 'new', - saveToLibrary: false, - }); - - await dashboard.waitForRenderComplete(); - const originalEmbeddableCount = await canvas.getEmbeddableCount(); - await panelActions.openContextMenu(); - await panelActions.clickEdit(); - - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('xyVisChart'); - await retry.try(async () => { - const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); - expect(await dimensions[1].getVisibleText()).to.be('Count of records'); - }); - - await lens.saveAndReturn(); - await retry.try(async () => { - const embeddableCount = await canvas.getEmbeddableCount(); - expect(embeddableCount).to.eql(originalEmbeddableCount); - }); - await panelActions.removePanel(); - }); - - it('should convert a by reference TSVB viz to a Lens viz', async () => { - await dashboardAddPanel.clickEditorMenuButton(); - await dashboardAddPanel.clickVisType('metrics'); - await testSubjects.click('visualizesaveAndReturnButton'); - // save it to library - const originalPanel = await testSubjects.find('embeddablePanelHeading-'); - await panelActions.saveToLibrary('My TSVB to Lens viz 2', originalPanel); - - await dashboard.waitForRenderComplete(); - const originalEmbeddableCount = await canvas.getEmbeddableCount(); - await panelActions.openContextMenu(); - await panelActions.clickEdit(); - - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); - await lens.waitForVisualization('legacyMtrVis'); - await retry.try(async () => { - const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); - expect(await dimensions[1].getVisibleText()).to.be('Count of records'); - }); - - await lens.saveAndReturn(); - await retry.try(async () => { - const embeddableCount = await canvas.getEmbeddableCount(); - expect(embeddableCount).to.eql(originalEmbeddableCount); - }); - - const panel = await testSubjects.find(`embeddablePanelHeading-`); - const descendants = await testSubjects.findAllDescendant( - 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', - panel - ); - expect(descendants.length).to.equal(0); - - await panelActions.removePanel(); - }); - }); - }); -} From 6a0b2fd717e53c177fcc9fe3bad02c0f726e5d78 Mon Sep 17 00:00:00 2001 From: Michael Dokolin Date: Thu, 29 Sep 2022 08:38:58 +0200 Subject: [PATCH 156/172] [Expressions] Fix expressions chain invocation not to unsubscribe on error (#142105) --- .../common/execution/execution.test.ts | 57 +++++++ .../expressions/common/execution/execution.ts | 145 +++++++++--------- 2 files changed, 129 insertions(+), 73 deletions(-) diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index 75a95035bb89f..a5e03084a6977 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -488,6 +488,63 @@ describe('Execution', () => { expect(spy.fn).toHaveBeenCalledTimes(0); }); + + test('continues execution when error state is gone', async () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const a = 1; + const b = 2; + const c = 3; + const observable$ = cold('abc|', { a, b, c }); + const flakyFn = jest + .fn() + .mockImplementationOnce((value) => value) + .mockImplementationOnce(() => { + throw new Error('Some error.'); + }) + .mockImplementationOnce((value) => value); + const spyFn = jest.fn((value) => value); + + const executor = createUnitTestExecutor(); + executor.registerFunction({ + name: 'observable', + args: {}, + help: '', + fn: () => observable$, + }); + executor.registerFunction({ + name: 'flaky', + args: {}, + help: '', + fn: (value) => flakyFn(value), + }); + executor.registerFunction({ + name: 'spy', + args: {}, + help: '', + fn: (value) => spyFn(value), + }); + + const result = executor.run('observable | flaky | spy', null, {}); + + expectObservable(result).toBe('abc|', { + a: { partial: true, result: a }, + b: { + partial: true, + result: { + type: 'error', + error: expect.objectContaining({ message: '[flaky] > Some error.' }), + }, + }, + c: { partial: false, result: c }, + }); + + flush(); + + expect(spyFn).toHaveBeenCalledTimes(2); + expect(spyFn).toHaveBeenNthCalledWith(1, a); + expect(spyFn).toHaveBeenNthCalledWith(2, c); + }); + }); }); describe('state', () => { diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index b4ef83389ce28..68cebaa65569b 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -295,87 +295,86 @@ export class Execution< } invokeChain( - chainArr: ExpressionAstFunction[], + [head, ...tail]: ExpressionAstFunction[], input: unknown - ): Observable { + ): Observable { + if (!head) { + return of(input as ChainOutput); + } + return of(input).pipe( - ...(chainArr.map((link) => - switchMap((currentInput) => { - const { function: fnName, arguments: fnArgs } = link; - const fn = getByAlias( - this.state.get().functions, - fnName, - this.execution.params.namespace - ); + switchMap((currentInput) => { + const { function: fnName, arguments: fnArgs } = head; + const fn = getByAlias(this.state.get().functions, fnName, this.execution.params.namespace); + + if (!fn) { + throw createError({ + name: 'fn not found', + message: i18n.translate('expressions.execution.functionNotFound', { + defaultMessage: `Function {fnName} could not be found.`, + values: { + fnName, + }, + }), + }); + } - if (!fn) { - throw createError({ - name: 'fn not found', - message: i18n.translate('expressions.execution.functionNotFound', { - defaultMessage: `Function {fnName} could not be found.`, - values: { - fnName, - }, - }), - }); - } + if (fn.disabled) { + throw createError({ + name: 'fn is disabled', + message: i18n.translate('expressions.execution.functionDisabled', { + defaultMessage: `Function {fnName} is disabled.`, + values: { + fnName, + }, + }), + }); + } - if (fn.disabled) { - throw createError({ - name: 'fn is disabled', - message: i18n.translate('expressions.execution.functionDisabled', { - defaultMessage: `Function {fnName} is disabled.`, - values: { - fnName, - }, - }), - }); - } + if (fn.deprecated) { + this.logger?.warn(`Function '${fnName}' is deprecated`); + } - if (fn.deprecated) { - this.logger?.warn(`Function '${fnName}' is deprecated`); - } + if (this.execution.params.debug) { + head.debug = { + args: {}, + duration: 0, + fn: fn.name, + input: currentInput, + success: true, + }; + } - if (this.execution.params.debug) { - link.debug = { - args: {}, - duration: 0, - fn: fn.name, - input: currentInput, - success: true, - }; - } + const timeStart = this.execution.params.debug ? now() : 0; + + // `resolveArgs` returns an object because the arguments themselves might + // actually have `then` or `subscribe` methods which would be treated as a `Promise` + // or an `Observable` accordingly. + return this.resolveArgs(fn, currentInput, fnArgs).pipe( + tap((args) => this.execution.params.debug && Object.assign(head.debug, { args })), + switchMap((args) => this.invokeFunction(fn, currentInput, args)), + switchMap((output) => (getType(output) === 'error' ? throwError(output) : of(output))), + tap((output) => this.execution.params.debug && Object.assign(head.debug, { output })), + switchMap((output) => this.invokeChain(tail, output)), + catchError((rawError) => { + const error = createError(rawError); + error.error.message = `[${fnName}] > ${error.error.message}`; + + if (this.execution.params.debug) { + Object.assign(head.debug, { error, rawError, success: false }); + } - const timeStart = this.execution.params.debug ? now() : 0; - - // `resolveArgs` returns an object because the arguments themselves might - // actually have `then` or `subscribe` methods which would be treated as a `Promise` - // or an `Observable` accordingly. - return this.resolveArgs(fn, currentInput, fnArgs).pipe( - tap((args) => this.execution.params.debug && Object.assign(link.debug, { args })), - switchMap((args) => this.invokeFunction(fn, currentInput, args)), - switchMap((output) => (getType(output) === 'error' ? throwError(output) : of(output))), - tap((output) => this.execution.params.debug && Object.assign(link.debug, { output })), - catchError((rawError) => { - const error = createError(rawError); - error.error.message = `[${fnName}] > ${error.error.message}`; - - if (this.execution.params.debug) { - Object.assign(link.debug, { error, rawError, success: false }); - } - - return throwError(error); - }), - finalize(() => { - if (this.execution.params.debug) { - Object.assign(link.debug, { duration: now() - timeStart }); - } - }) - ); - }) - ) as Parameters['pipe']>), + return of(error); + }), + finalize(() => { + if (this.execution.params.debug) { + Object.assign(head.debug, { duration: now() - timeStart }); + } + }) + ); + }), catchError((error) => of(error)) - ) as Observable; + ); } invokeFunction( From e3bf5539a1a55e06b4076906c8c1420d2d00ae37 Mon Sep 17 00:00:00 2001 From: Giorgos Bamparopoulos Date: Thu, 29 Sep 2022 09:05:12 +0100 Subject: [PATCH 157/172] [APM] Add e2e tests for storage explorer (#141959) * Refactor APM user creation and add tests for storage explorer --- .../storage_explorer/storage_explorer.cy.ts | 192 ++++++++++++++++++ .../apm/ftr_e2e/cypress/support/commands.ts | 12 +- .../apm/ftr_e2e/cypress/support/types.d.ts | 1 + .../apm/ftr_e2e/cypress_test_runner.ts | 14 +- x-pack/plugins/apm/ftr_e2e/tsconfig.json | 1 + .../index_lifecycle_phase_select.tsx | 1 + .../storage_details_per_service.tsx | 11 +- .../create_apm_users/create_apm_users_cli.ts | 34 +++- .../create_apm_users}/authentication.ts | 80 +------- .../create_apm_users/create_apm_users.ts | 39 ++-- .../create_apm_users/helpers/call_kibana.ts | 2 +- .../helpers/create_custom_role.ts | 60 ++++++ .../helpers/create_or_update_user.ts | 0 .../create_apm_users/helpers/get_version.ts | 2 +- .../observability/e2e/synthetics_runner.ts | 16 +- .../e2e/journeys/data_view_permissions.ts | 2 +- .../common/bootstrap_apm_synthtrace.ts | 2 +- .../test/apm_api_integration/common/config.ts | 64 ++---- .../settings/agent_keys/agent_keys.spec.ts | 2 +- 19 files changed, 370 insertions(+), 165 deletions(-) create mode 100644 x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts rename x-pack/{test/apm_api_integration/common => plugins/apm/server/test_helpers/create_apm_users}/authentication.ts (61%) rename x-pack/plugins/apm/{scripts => server/test_helpers}/create_apm_users/create_apm_users.ts (69%) rename x-pack/plugins/apm/{scripts => server/test_helpers}/create_apm_users/helpers/call_kibana.ts (97%) create mode 100644 x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_custom_role.ts rename x-pack/plugins/apm/{scripts => server/test_helpers}/create_apm_users/helpers/create_or_update_user.ts (100%) rename x-pack/plugins/apm/{scripts => server/test_helpers}/create_apm_users/helpers/get_version.ts (96%) diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts new file mode 100644 index 0000000000000..e989ea5cf0faf --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -0,0 +1,192 @@ +/* + * 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 url from 'url'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; +import { checkA11y } from '../../../support/commands'; + +const timeRange = { + rangeFrom: '2021-10-10T00:00:00.000Z', + rangeTo: '2021-10-10T00:15:00.000Z', +}; + +const storageExplorerHref = url.format({ + pathname: '/app/apm/storage-explorer', + query: timeRange, +}); + +const mainApiRequestsToIntercept = [ + { + endpoint: '/internal/apm/storage_chart', + aliasName: 'storageChartRequest', + }, + { + endpoint: '/internal/apm/storage_explorer_summary_stats', + aliasName: 'summaryStatsRequest', + }, + { + endpoint: '/internal/apm/storage_explorer', + aliasName: 'storageExlorerRequest', + }, +]; + +const mainAliasNames = mainApiRequestsToIntercept.map( + ({ aliasName }) => `@${aliasName}` +); + +describe('Storage Explorer', () => { + before(() => { + const { rangeFrom, rangeTo } = timeRange; + synthtrace.index( + opbeans({ + from: new Date(rangeFrom).getTime(), + to: new Date(rangeTo).getTime(), + }) + ); + }); + + after(() => { + synthtrace.clean(); + }); + + describe('When navigating to storage explorer without the required permissions', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana(storageExplorerHref); + }); + + it('displays a prompt for permissions', () => { + cy.contains('You need permission'); + }); + }); + + describe('When navigating to storage explorer with the required permissions', () => { + beforeEach(() => { + cy.loginAsMonitorUser(); + cy.visitKibana(storageExplorerHref); + }); + + it('has no detectable a11y violations on load', () => { + cy.contains('h1', 'Storage explorer'); + // set skipFailures to true to not fail the test when there are accessibility failures + checkA11y({ skipFailures: true }); + }); + + it('has a list of summary stats', () => { + cy.contains('Total APM size'); + cy.contains('Daily data generation'); + cy.contains('Traces per minute'); + cy.contains('Number of services'); + }); + + it('renders the storage timeseries chart', () => { + cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); + }); + + it('has a list of services and environments', () => { + cy.contains('opbeans-node'); + cy.contains('opbeans-java'); + cy.contains('opbeans-rum'); + cy.get('td:contains(production)').should('have.length', 3); + }); + + it('when clicking on a service it loads the service overview for that service', () => { + cy.contains('opbeans-node').click({ force: true }); + cy.url().should('include', '/apm/services/opbeans-node/overview'); + cy.contains('h1', 'opbeans-node'); + }); + }); + + describe('Calls APIs', () => { + beforeEach(() => { + mainApiRequestsToIntercept.forEach(({ endpoint, aliasName }) => { + cy.intercept({ pathname: endpoint }).as(aliasName); + }); + + cy.loginAsMonitorUser(); + cy.visitKibana(storageExplorerHref); + }); + + it('with the correct environment when changing the environment', () => { + cy.wait(mainAliasNames); + + cy.get('[data-test-subj="environmentFilter"]').type('production'); + + cy.contains('button', 'production').click({ force: true }); + + cy.expectAPIsToHaveBeenCalledWith({ + apisIntercepted: mainAliasNames, + value: 'environment=production', + }); + }); + + it('when clicking the refresh button', () => { + cy.wait(mainAliasNames); + cy.contains('Refresh').click(); + cy.wait(mainAliasNames); + }); + + it('when selecting a different time range and clicking the update button', () => { + cy.wait(mainAliasNames); + + cy.selectAbsoluteTimeRange( + moment(timeRange.rangeFrom).subtract(5, 'm').toISOString(), + moment(timeRange.rangeTo).subtract(5, 'm').toISOString() + ); + cy.contains('Update').click(); + cy.wait(mainAliasNames); + + cy.contains('Refresh').click(); + cy.wait(mainAliasNames); + }); + + it('with the correct lifecycle phase when changing the lifecycle phase', () => { + cy.wait(mainAliasNames); + + cy.get('[data-test-subj="storageExplorerLifecyclePhaseSelect"]').click(); + cy.contains('button', 'Warm').click(); + + cy.expectAPIsToHaveBeenCalledWith({ + apisIntercepted: mainAliasNames, + value: 'indexLifecyclePhase=warm', + }); + }); + }); + + describe('Storage details per service', () => { + beforeEach(() => { + const apiRequestsToIntercept = [ + ...mainApiRequestsToIntercept, + { + endpoint: '/internal/apm/services/opbeans-node/storage_details', + aliasName: 'storageDetailsRequest', + }, + ]; + + apiRequestsToIntercept.forEach(({ endpoint, aliasName }) => { + cy.intercept({ pathname: endpoint }).as(aliasName); + }); + + cy.loginAsMonitorUser(); + cy.visitKibana(storageExplorerHref); + }); + + it('shows storage details', () => { + cy.wait(mainAliasNames); + cy.contains('opbeans-node'); + + cy.get('[data-test-subj="storageDetailsButton_opbeans-node"]').click(); + cy.get('[data-test-subj="loadingSpinner"]').should('be.visible'); + cy.wait('@storageDetailsRequest'); + + cy.contains('Service storage details'); + cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); + cy.get('[data-test-subj="serviceStorageDetailsTable"]'); + }); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 692926d3049ca..7830e791c3655 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -9,13 +9,21 @@ import { Interception } from 'cypress/types/net-stubbing'; import 'cypress-axe'; import moment from 'moment'; import { AXE_CONFIG, AXE_OPTIONS } from '@kbn/axe-config'; +import { ApmUsername } from '../../../server/test_helpers/create_apm_users/authentication'; Cypress.Commands.add('loginAsViewerUser', () => { - return cy.loginAs({ username: 'viewer', password: 'changeme' }); + return cy.loginAs({ username: ApmUsername.viewerUser, password: 'changeme' }); }); Cypress.Commands.add('loginAsEditorUser', () => { - return cy.loginAs({ username: 'editor', password: 'changeme' }); + return cy.loginAs({ username: ApmUsername.editorUser, password: 'changeme' }); +}); + +Cypress.Commands.add('loginAsMonitorUser', () => { + return cy.loginAs({ + username: ApmUsername.apmMonitorIndices, + password: 'changeme', + }); }); Cypress.Commands.add( diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts index 27720210b668a..2235847e584a4 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts @@ -9,6 +9,7 @@ declare namespace Cypress { interface Chainable { loginAsViewerUser(): Cypress.Chainable>; loginAsEditorUser(): Cypress.Chainable>; + loginAsMonitorUser(): Cypress.Chainable>; loginAs(params: { username: string; password: string; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts index 8af466b872629..eba57b28ec0b9 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts @@ -11,7 +11,7 @@ import { esTestConfig } from '@kbn/test'; import { apm, createLogger, LogLevel } from '@kbn/apm-synthtrace'; import path from 'path'; import { FtrProviderContext } from './ftr_provider_context'; -import { createApmUsers } from '../scripts/create_apm_users/create_apm_users'; +import { createApmUsers } from '../server/test_helpers/create_apm_users/create_apm_users'; export async function cypressTestRunner({ getService }: FtrProviderContext) { const config = getService('config'); @@ -26,12 +26,6 @@ export async function cypressTestRunner({ getService }: FtrProviderContext) { const username = config.get('servers.elasticsearch.username'); const password = config.get('servers.elasticsearch.password'); - // Creates APM users - await createApmUsers({ - elasticsearch: { username, password }, - kibana: { hostname: kibanaUrl }, - }); - const esNode = Url.format({ protocol: config.get('servers.elasticsearch.protocol'), port: config.get('servers.elasticsearch.port'), @@ -39,6 +33,12 @@ export async function cypressTestRunner({ getService }: FtrProviderContext) { auth: `${username}:${password}`, }); + // Creates APM users + await createApmUsers({ + elasticsearch: { node: esNode, username, password }, + kibana: { hostname: kibanaUrl }, + }); + const esRequestTimeout = config.get('timeouts.esRequestTimeout'); const kibanaClient = new apm.ApmSynthtraceKibanaClient( createLogger(LogLevel.info) diff --git a/x-pack/plugins/apm/ftr_e2e/tsconfig.json b/x-pack/plugins/apm/ftr_e2e/tsconfig.json index 84a66afe4588d..730971a284ed5 100644 --- a/x-pack/plugins/apm/ftr_e2e/tsconfig.json +++ b/x-pack/plugins/apm/ftr_e2e/tsconfig.json @@ -18,5 +18,6 @@ "references": [ { "path": "../../../test/tsconfig.json" }, { "path": "../../../../test/tsconfig.json" }, + { "path": "../tsconfig.json" }, ] } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/index_lifecycle_phase_select.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/index_lifecycle_phase_select.tsx index 124579e0562e2..10554b4ff8278 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/index_lifecycle_phase_select.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/index_lifecycle_phase_select.tsx @@ -130,6 +130,7 @@ export function IndexLifecyclePhaseSelect() { }} hasDividers style={{ minWidth: 200 }} + data-test-subj="storageExplorerLifecyclePhaseSelect" /> ); } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx index 3d9cd14c8e958..0a101d3357684 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx @@ -186,7 +186,10 @@ export function StorageDetailsPerService({ - + - + {processorEventStats.map( ({ processorEventLabel, docs, size }) => ( <> diff --git a/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users_cli.ts b/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users_cli.ts index 194639128b4cf..24cda99fd669b 100644 --- a/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users_cli.ts +++ b/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users_cli.ts @@ -8,13 +8,17 @@ /* eslint-disable no-console */ import { argv } from 'yargs'; -import { AbortError, isAxiosError } from './helpers/call_kibana'; -import { createApmUsers } from './create_apm_users'; -import { getKibanaVersion } from './helpers/get_version'; +import { + AbortError, + isAxiosError, +} from '../../server/test_helpers/create_apm_users/helpers/call_kibana'; +import { createApmUsers } from '../../server/test_helpers/create_apm_users/create_apm_users'; +import { getKibanaVersion } from '../../server/test_helpers/create_apm_users/helpers/get_version'; async function init() { const esUserName = (argv.username as string) || 'elastic'; const esPassword = argv.password as string | undefined; + const esUrl = argv.esUrl as string | undefined; const kibanaBaseUrl = argv.kibanaUrl as string | undefined; if (!esPassword) { @@ -24,6 +28,20 @@ async function init() { process.exit(); } + if (!esUrl) { + console.error( + 'Please specify the url for elasticsearch: `--es-url http://localhost:9200` ' + ); + process.exit(); + } + + if (!esUrl.startsWith('https://') && !esUrl.startsWith('http://')) { + console.error( + 'Elasticsearch url must be prefixed with http(s):// `--es-url http://localhost:9200`' + ); + process.exit(); + } + if (!kibanaBaseUrl) { console.error( 'Please specify the url for Kibana: `--kibana-url http://localhost:5601` ' @@ -42,7 +60,11 @@ async function init() { } const kibana = { hostname: kibanaBaseUrl }; - const elasticsearch = { username: esUserName, password: esPassword }; + const elasticsearch = { + node: esUrl, + username: esUserName, + password: esPassword, + }; console.log({ kibana, elasticsearch }); @@ -50,9 +72,7 @@ async function init() { console.log(`Connected to Kibana ${version}`); const users = await createApmUsers({ elasticsearch, kibana }); - const credentials = users - .map((u) => ` - ${u.username} / ${esPassword}`) - .join('\n'); + const credentials = users.map((u) => ` - ${u} / ${esPassword}`).join('\n'); console.log( `\nYou can now login to ${kibana.hostname} with:\n${credentials}` diff --git a/x-pack/test/apm_api_integration/common/authentication.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts similarity index 61% rename from x-pack/test/apm_api_integration/common/authentication.ts rename to x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts index 93bae236d5dc5..f3d15ee6db21e 100644 --- a/x-pack/test/apm_api_integration/common/authentication.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts @@ -5,15 +5,7 @@ * 2.0. */ -import { Client } from '@elastic/elasticsearch'; -import { PrivilegeType } from '@kbn/apm-plugin/common/privilege_type'; -import { ToolingLog } from '@kbn/tooling-log'; -import { omit } from 'lodash'; -import { KbnClientRequesterError } from '@kbn/test'; -import { AxiosError } from 'axios'; -import { SecurityServiceProvider } from '../../../../test/common/services/security'; - -type SecurityService = Awaited>; +import { PrivilegeType } from '../../../common/privilege_type'; export enum ApmUsername { noAccessUser = 'no_access_user', @@ -34,7 +26,7 @@ export enum ApmCustomRolename { apmMonitorIndices = 'apm_monitor_indices', } -const customRoles = { +export const customRoles = { [ApmCustomRolename.apmReadUserWithoutMlAccess]: { elasticsearch: { cluster: [], @@ -97,7 +89,7 @@ const customRoles = { }, }; -const users: Record< +export const users: Record< ApmUsername, { builtInRoleNames?: string[]; customRoleNames?: ApmCustomRolename[] } > = { @@ -132,70 +124,4 @@ const users: Record< }, }; -function logErrorResponse(logger: ToolingLog, e: Error) { - if (e instanceof KbnClientRequesterError) { - logger.error(`KbnClientRequesterError: ${JSON.stringify(e.axiosError?.response?.data)}`); - } else if (e instanceof AxiosError) { - logger.error(`AxiosError: ${JSON.stringify(e.response?.data)}`); - } else { - logger.error(`Unknown error: ${e.constructor.name}`); - } -} - -export async function createApmUser({ - username, - security, - es, - logger, -}: { - username: ApmUsername; - security: SecurityService; - es: Client; - logger: ToolingLog; -}) { - const user = users[username]; - - if (!user) { - throw new Error(`No configuration found for ${username}`); - } - - const { builtInRoleNames = [], customRoleNames = [] } = user; - - try { - // create custom roles - await Promise.all( - customRoleNames.map(async (roleName) => createCustomRole({ roleName, security, es })) - ); - - // create user - await security.user.create(username, { - full_name: username, - password: APM_TEST_PASSWORD, - roles: [...builtInRoleNames, ...customRoleNames], - }); - } catch (e) { - logErrorResponse(logger, e); - throw e; - } -} - -async function createCustomRole({ - roleName, - security, - es, -}: { - roleName: ApmCustomRolename; - security: SecurityService; - es: Client; -}) { - const role = customRoles[roleName]; - - // Add application privileges with es client as they are not supported by - // security.user.create. They are preserved when updating the role below - if ('applications' in role) { - await es.security.putRole({ name: roleName, body: role }); - } - await security.role.create(roleName, omit(role, 'applications')); -} - export const APM_TEST_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/create_apm_users.ts similarity index 69% rename from x-pack/plugins/apm/scripts/create_apm_users/create_apm_users.ts rename to x-pack/plugins/apm/server/test_helpers/create_apm_users/create_apm_users.ts index 7532392c9a8b4..8e9e39373752b 100644 --- a/x-pack/plugins/apm/scripts/create_apm_users/create_apm_users.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/create_apm_users.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { asyncForEach } from '@kbn/std'; import { AbortError, callKibana } from './helpers/call_kibana'; import { createOrUpdateUser } from './helpers/create_or_update_user'; - +import { ApmUsername, users } from './authentication'; +import { createCustomRole } from './helpers/create_custom_role'; export interface Elasticsearch { + node: string; username: string; password: string; } @@ -28,6 +31,7 @@ export async function createApmUsers({ elasticsearch, kibana, }); + if (!isCredentialsValid) { throw new AbortError('Invalid username/password'); } @@ -36,24 +40,33 @@ export async function createApmUsers({ elasticsearch, kibana, }); + if (!isSecurityEnabled) { throw new AbortError('Security must be enabled!'); } - // user definitions - const users = [ - { username: 'viewer', roles: ['viewer'] }, - { username: 'editor', roles: ['editor'] }, - ]; + const apmUsers = Object.values(ApmUsername); + await asyncForEach(apmUsers, async (username) => { + const user = users[username]; + const { builtInRoleNames = [], customRoleNames = [] } = user; + + // create custom roles + await Promise.all( + customRoleNames.map(async (roleName) => + createCustomRole({ elasticsearch, kibana, roleName }) + ) + ); - // create users - await Promise.all( - users.map(async (user) => - createOrUpdateUser({ elasticsearch, kibana, user }) - ) - ); + // create user + const roles = builtInRoleNames.concat(customRoleNames); + await createOrUpdateUser({ + elasticsearch, + kibana, + user: { username, roles }, + }); + }); - return users; + return apmUsers; } async function getIsSecurityEnabled({ diff --git a/x-pack/plugins/apm/scripts/create_apm_users/helpers/call_kibana.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/call_kibana.ts similarity index 97% rename from x-pack/plugins/apm/scripts/create_apm_users/helpers/call_kibana.ts rename to x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/call_kibana.ts index 1e6bd2e02c416..72312645a644e 100644 --- a/x-pack/plugins/apm/scripts/create_apm_users/helpers/call_kibana.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/call_kibana.ts @@ -13,7 +13,7 @@ export async function callKibana({ kibana, options, }: { - elasticsearch: Elasticsearch; + elasticsearch: Omit; kibana: Kibana; options: AxiosRequestConfig; }): Promise { diff --git a/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_custom_role.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_custom_role.ts new file mode 100644 index 0000000000000..ac906fcbfb5e2 --- /dev/null +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_custom_role.ts @@ -0,0 +1,60 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { omit } from 'lodash'; +import { Elasticsearch, Kibana } from '../create_apm_users'; +import { callKibana } from './call_kibana'; +import { customRoles, ApmCustomRolename } from '../authentication'; + +export async function createCustomRole({ + elasticsearch, + kibana, + roleName, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + roleName: ApmCustomRolename; +}) { + const role = customRoles[roleName]; + + // Add application privileges with es client as they are not supported by + // the security API. They are preserved when updating the role below + if ('applications' in role) { + const esClient = getEsClient(elasticsearch); + await esClient.security.putRole({ name: roleName, body: role }); + } + + await callKibana({ + elasticsearch, + kibana, + options: { + method: 'PUT', + url: `/api/security/role/${roleName}`, + data: { + ...omit(role, 'applications'), + }, + }, + }); +} + +export function getEsClient(elasticsearch: Elasticsearch) { + const { node, username, password } = elasticsearch; + const client = new Client({ + node, + tls: { + rejectUnauthorized: false, + }, + requestTimeout: 120000, + auth: { + username, + password, + }, + }); + + return client; +} diff --git a/x-pack/plugins/apm/scripts/create_apm_users/helpers/create_or_update_user.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_or_update_user.ts similarity index 100% rename from x-pack/plugins/apm/scripts/create_apm_users/helpers/create_or_update_user.ts rename to x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/create_or_update_user.ts diff --git a/x-pack/plugins/apm/scripts/create_apm_users/helpers/get_version.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/get_version.ts similarity index 96% rename from x-pack/plugins/apm/scripts/create_apm_users/helpers/get_version.ts rename to x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/get_version.ts index a809efe7402ea..a00747ae78582 100644 --- a/x-pack/plugins/apm/scripts/create_apm_users/helpers/get_version.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/helpers/get_version.ts @@ -13,7 +13,7 @@ export async function getKibanaVersion({ elasticsearch, kibana, }: { - elasticsearch: Elasticsearch; + elasticsearch: Omit; kibana: Kibana; }) { try { diff --git a/x-pack/plugins/observability/e2e/synthetics_runner.ts b/x-pack/plugins/observability/e2e/synthetics_runner.ts index 25c236c6da585..a24771c091c09 100644 --- a/x-pack/plugins/observability/e2e/synthetics_runner.ts +++ b/x-pack/plugins/observability/e2e/synthetics_runner.ts @@ -10,7 +10,7 @@ import Url from 'url'; import { run as syntheticsRun } from '@elastic/synthetics'; import { PromiseType } from 'utility-types'; -import { createApmUsers } from '@kbn/apm-plugin/scripts/create_apm_users/create_apm_users'; +import { createApmUsers } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/create_apm_users'; import { esArchiverUnload } from './tasks/es_archiver'; @@ -23,6 +23,7 @@ export interface ArgParams { export class SyntheticsRunner { public getService: any; public kibanaUrl: string; + private elasticsearchUrl: string; public testFilesLoaded: boolean = false; @@ -31,6 +32,7 @@ export class SyntheticsRunner { constructor(getService: any, params: ArgParams) { this.getService = getService; this.kibanaUrl = this.getKibanaUrl(); + this.elasticsearchUrl = this.getElasticsearchUrl(); this.params = params; } @@ -40,7 +42,7 @@ export class SyntheticsRunner { async createTestUsers() { await createApmUsers({ - elasticsearch: { username: 'elastic', password: 'changeme' }, + elasticsearch: { node: this.elasticsearchUrl, username: 'elastic', password: 'changeme' }, kibana: { hostname: this.kibanaUrl }, }); } @@ -79,6 +81,16 @@ export class SyntheticsRunner { }); } + getElasticsearchUrl() { + const config = this.getService('config'); + + return Url.format({ + protocol: config.get('servers.elasticsearch.protocol'), + hostname: config.get('servers.elasticsearch.hostname'), + port: config.get('servers.elasticsearch.port'), + }); + } + async run() { if (!this.testFilesLoaded) { throw new Error('Test files not loaded'); diff --git a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts index aa29b92b7ea25..e5714234de690 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts @@ -6,7 +6,7 @@ */ import { journey, step, expect, before } from '@elastic/synthetics'; -import { callKibana } from '@kbn/apm-plugin/scripts/create_apm_users/helpers/call_kibana'; +import { callKibana } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/helpers/call_kibana'; import { byTestId, waitForLoadingToFinish } from '@kbn/observability-plugin/e2e/utils'; import { loginPageProvider } from '../page_objects/login'; diff --git a/x-pack/test/apm_api_integration/common/bootstrap_apm_synthtrace.ts b/x-pack/test/apm_api_integration/common/bootstrap_apm_synthtrace.ts index 96d0cbf3f8e7b..0a790d261ef49 100644 --- a/x-pack/test/apm_api_integration/common/bootstrap_apm_synthtrace.ts +++ b/x-pack/test/apm_api_integration/common/bootstrap_apm_synthtrace.ts @@ -7,7 +7,7 @@ import { apm, createLogger, LogLevel } from '@kbn/apm-synthtrace'; import { esTestConfig } from '@kbn/test'; -import { APM_TEST_PASSWORD } from './authentication'; +import { APM_TEST_PASSWORD } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; import { InheritedFtrProviderContext } from './ftr_provider_context'; export async function bootstrapApmSynthtrace( diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index e01c5fbf40541..431b445d3a1a0 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -8,11 +8,12 @@ import { FtrConfigProviderContext } from '@kbn/test'; import supertest from 'supertest'; import { format, UrlObject } from 'url'; -import { Client } from '@elastic/elasticsearch'; -import { ToolingLog } from '@kbn/tooling-log'; -import { SecurityServiceProvider } from '../../../../test/common/services/security'; +import { + ApmUsername, + APM_TEST_PASSWORD, +} from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; +import { createApmUsers } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/create_apm_users'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; -import { createApmUser, APM_TEST_PASSWORD, ApmUsername } from './authentication'; import { APMFtrConfigName } from '../configs'; import { createApmApiClient } from './apm_api_supertest'; import { RegistryProvider } from './registry'; @@ -25,17 +26,8 @@ export interface ApmFtrConfig { kibanaConfig?: Record; } -type SecurityService = Awaited>; - function getLegacySupertestClient(kibanaServer: UrlObject, username: ApmUsername) { return async (context: InheritedFtrProviderContext) => { - const security = context.getService('security'); - const es = context.getService('es'); - const logger = context.getService('log'); - await security.init(); - - await createApmUser({ security, username, es, logger }); - const url = format({ ...kibanaServer, auth: `${username}:${APM_TEST_PASSWORD}`, @@ -47,19 +39,11 @@ function getLegacySupertestClient(kibanaServer: UrlObject, username: ApmUsername async function getApmApiClient({ kibanaServer, - security, username, - es, - logger, }: { kibanaServer: UrlObject; - security: SecurityService; username: ApmUsername; - es: Client; - logger: ToolingLog; }) { - await createApmUser({ security, username, es, logger }); - const url = format({ ...kibanaServer, auth: `${username}:${APM_TEST_PASSWORD}`, @@ -81,6 +65,8 @@ export function createTestConfig(config: ApmFtrConfig) { const services = xPackAPITestsConfig.get('services') as InheritedServices; const servers = xPackAPITestsConfig.get('servers'); const kibanaServer = servers.kibana as UrlObject; + const kibanaServerUrl = format(kibanaServer); + const esServer = servers.elasticsearch as UrlObject; return { testFiles: [require.resolve('../tests')], @@ -91,72 +77,50 @@ export function createTestConfig(config: ApmFtrConfig) { apmFtrConfig: () => config, registry: RegistryProvider, synthtraceEsClient: (context: InheritedFtrProviderContext) => { - const kibanaServerUrl = format(kibanaServer); return bootstrapApmSynthtrace(context, kibanaServerUrl); }, apmApiClient: async (context: InheritedFtrProviderContext) => { - const security = context.getService('security'); - const es = context.getService('es'); - const logger = context.getService('log'); + const { username, password } = servers.kibana; + const esUrl = format(esServer); - await security.init(); + // Creates APM users + await createApmUsers({ + elasticsearch: { node: esUrl, username, password }, + kibana: { hostname: kibanaServerUrl }, + }); return { noAccessUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.noAccessUser, - es, - logger, }), readUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.viewerUser, - es, - logger, }), writeUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.editorUser, - es, - logger, }), annotationWriterUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.apmAnnotationsWriteUser, - es, - logger, }), noMlAccessUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.apmReadUserWithoutMlAccess, - es, - logger, }), manageOwnAgentKeysUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.apmManageOwnAgentKeys, - es, - logger, }), createAndAllAgentKeysUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.apmManageOwnAndCreateAgentKeys, - es, - logger, }), monitorIndicesUser: await getApmApiClient({ kibanaServer, - security, username: ApmUsername.apmMonitorIndices, - es, - logger, }), }; }, diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts b/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts index c0d6500fd298c..19459d2a4ec16 100644 --- a/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { first } from 'lodash'; import { PrivilegeType } from '@kbn/apm-plugin/common/privilege_type'; +import { ApmUsername } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { ApmApiError, ApmApiSupertest } from '../../../common/apm_api_supertest'; -import { ApmUsername } from '../../../common/authentication'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); From 2b4f5d9a640212a85675554c87be36e74e508b83 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+dimaanj@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:19:58 +0300 Subject: [PATCH 158/172] [Graph] Align Inspect requests experience with common approach (#141222) * [Graph] hide inspect button when there is no relevant request content * [Graph] fix functional * [Graph] add common inspector * [Discover] fix checks * [Discover] apply suggestion --- x-pack/plugins/graph/kibana.json | 3 +- x-pack/plugins/graph/public/application.tsx | 2 + .../graph/public/apps/workspace_route.tsx | 14 ++- .../graph/public/components/inspect_panel.tsx | 98 ------------------- .../workspace_layout.test.tsx | 9 ++ .../workspace_layout/workspace_layout.tsx | 16 ++- .../workspace_top_nav_menu.tsx | 22 ++++- .../graph/public/helpers/use_graph_loader.ts | 86 ++++++++++++---- .../graph/public/helpers/use_inspector.ts | 38 +++++++ x-pack/plugins/graph/public/plugin.ts | 3 + .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - x-pack/test/accessibility/apps/graph.ts | 6 -- 14 files changed, 156 insertions(+), 150 deletions(-) delete mode 100644 x-pack/plugins/graph/public/components/inspect_panel.tsx create mode 100644 x-pack/plugins/graph/public/helpers/use_inspector.ts diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index 641e595893c0a..6db4ca194d7d1 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -9,7 +9,8 @@ "data", "navigation", "savedObjects", - "unifiedSearch" + "unifiedSearch", + "inspector" ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index 1976b12621f4f..bbb14a96ac5eb 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -27,6 +27,7 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { FormattedRelative } from '@kbn/i18n-react'; +import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public'; import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'; import './index.scss'; @@ -70,6 +71,7 @@ export interface GraphDependencies { uiSettings: IUiSettingsClient; history: ScopedHistory; spaces?: SpacesApi; + inspect: InspectorPublicPluginStart; } export type GraphServices = Omit; diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index 051c0fa66ab89..9b1fde2e7cb25 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -43,6 +43,7 @@ export const WorkspaceRoute = ({ setHeaderActionMenu, spaces, indexPatterns: getIndexPatternProvider, + inspect, }, }: WorkspaceRouteProps) => { /** @@ -64,11 +65,6 @@ export const WorkspaceRoute = ({ [getIndexPatternProvider.get] ); - const { loading, callNodeProxy, callSearchNodeProxy, handleSearchQueryError } = useGraphLoader({ - toastNotifications, - coreStart, - }); - const services = useMemo( () => ({ appName: 'graph', @@ -80,6 +76,12 @@ export const WorkspaceRoute = ({ [coreStart, data, storage, unifiedSearch] ); + const { loading, requestAdapter, callNodeProxy, callSearchNodeProxy, handleSearchQueryError } = + useGraphLoader({ + toastNotifications, + coreStart, + }); + const [store] = useState(() => createGraphStore({ basePath: getBasePath(), @@ -150,6 +152,8 @@ export const WorkspaceRoute = ({ overlays={overlays} savedWorkspace={savedWorkspace} indexPatternProvider={indexPatternProvider} + inspect={inspect} + requestAdapter={requestAdapter} /> diff --git a/x-pack/plugins/graph/public/components/inspect_panel.tsx b/x-pack/plugins/graph/public/components/inspect_panel.tsx deleted file mode 100644 index e6197bac16ba7..0000000000000 --- a/x-pack/plugins/graph/public/components/inspect_panel.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo, useState } from 'react'; -import { EuiTab, EuiTabs, EuiText } from '@elastic/eui'; -import { monaco, XJsonLang } from '@kbn/monaco'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { CodeEditor } from '@kbn/kibana-react-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; - -interface InspectPanelProps { - showInspect: boolean; - indexPattern?: DataView; - lastRequest?: string; - lastResponse?: string; -} - -const CODE_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = { - automaticLayout: true, - fontSize: 12, - lineNumbers: 'on', - minimap: { - enabled: false, - }, - overviewRulerBorder: false, - readOnly: true, - scrollbar: { - alwaysConsumeMouseWheel: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', -}; - -const dummyCallback = () => {}; - -export const InspectPanel = ({ - showInspect, - lastRequest, - lastResponse, - indexPattern, -}: InspectPanelProps) => { - const [selectedTabId, setSelectedTabId] = useState('request'); - - const onRequestClick = () => setSelectedTabId('request'); - const onResponseClick = () => setSelectedTabId('response'); - - const editorContent = useMemo( - () => (selectedTabId === 'request' ? lastRequest : lastResponse), - [lastRequest, lastResponse, selectedTabId] - ); - - if (showInspect) { - return ( -
    -
    -
    - -
    - -
    - - http://host:port/{indexPattern?.id}/_graph/explore - - - - - - - - - - -
    -
    -
    - ); - } - - return null; -}; diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx index cb43b5ec5d688..72cd35997b876 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx @@ -10,6 +10,7 @@ import { shallow } from 'enzyme'; import { WorkspaceLayoutComponent } from '.'; import { coreMock } from '@kbn/core/public/mocks'; import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; +import { Start as InspectorStart, RequestAdapter } from '@kbn/inspector-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { GraphSavePolicy, @@ -51,6 +52,14 @@ describe('workspace_layout', () => { aliasTargetId: '', } as SharingSavedObjectProps, spaces: spacesPluginMock.createStartContract(), + inspect: { open: jest.fn() } as unknown as InspectorStart, + requestAdapter: { + start: () => ({ + stats: jest.fn(), + json: jest.fn(), + }), + reset: jest.fn(), + } as unknown as RequestAdapter, workspace: {} as unknown as Workspace, }; it('should display conflict notification if outcome is conflict', () => { diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx index 3eede479bd801..5de95636a61dd 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx @@ -11,6 +11,7 @@ import { EuiSpacer } from '@elastic/eui'; import { connect } from 'react-redux'; import { useLocation } from 'react-router-dom'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { SearchBar } from '../search_bar'; import { GraphState, @@ -20,7 +21,6 @@ import { import { FieldManager } from '../field_manager'; import { ControlType, IndexPatternProvider, TermIntersect, WorkspaceNode } from '../../types'; import { WorkspaceTopNavMenu } from './workspace_top_nav_menu'; -import { InspectPanel } from '../inspect_panel'; import { GuidancePanel } from '../guidance_panel'; import { GraphTitle } from '../graph_title'; import { GraphWorkspaceSavedObject, Workspace } from '../../types'; @@ -49,6 +49,7 @@ type WorkspaceLayoutProps = Pick< | 'canEditDrillDownUrls' | 'overlays' | 'spaces' + | 'inspect' > & { renderCounter: number; workspace?: Workspace; @@ -56,6 +57,7 @@ type WorkspaceLayoutProps = Pick< savedWorkspace: GraphWorkspaceSavedObject; indexPatternProvider: IndexPatternProvider; sharingSavedObjectProps?: SharingSavedObjectProps; + requestAdapter: RequestAdapter; }; interface WorkspaceLayoutStateProps { @@ -80,9 +82,10 @@ export const WorkspaceLayoutComponent = ({ setHeaderActionMenu, sharingSavedObjectProps, spaces, + inspect, + requestAdapter, }: WorkspaceLayoutProps & WorkspaceLayoutStateProps) => { const [currentIndexPattern, setCurrentIndexPattern] = useState(); - const [showInspect, setShowInspect] = useState(false); const [pickerOpen, setPickerOpen] = useState(false); const [mergeCandidates, setMergeCandidates] = useState([]); const [control, setControl] = useState('none'); @@ -188,20 +191,15 @@ export const WorkspaceLayoutComponent = ({ graphSavePolicy={graphSavePolicy} navigation={navigation} capabilities={capabilities} + inspect={inspect} + requestAdapter={requestAdapter} coreStart={coreStart} canEditDrillDownUrls={canEditDrillDownUrls} - setShowInspect={setShowInspect} confirmWipeWorkspace={confirmWipeWorkspace} setHeaderActionMenu={setHeaderActionMenu} isInitialized={isInitialized} /> - {isInitialized && }
    >; confirmWipeWorkspace: ( onConfirm: () => void, text?: string, @@ -30,9 +31,11 @@ interface WorkspaceTopNavMenuProps { graphSavePolicy: GraphSavePolicy; navigation: NavigationStart; capabilities: Capabilities; + inspect: InspectorPublicPluginStart; coreStart: CoreStart; canEditDrillDownUrls: boolean; isInitialized: boolean; + requestAdapter: RequestAdapter; } export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { @@ -40,6 +43,12 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { const location = useLocation(); const history = useHistory(); const allSavingDisabled = props.graphSavePolicy === 'none'; + const isInspectDisabled = !props.workspace?.lastRequest || !props.workspace.lastRequest; + + const { onOpenInspector } = useInspector({ + inspect: props.inspect, + requestAdapter: props.requestAdapter, + }); // ===== Menubar configuration ========= const { TopNavMenu } = props.navigation.ui; @@ -107,7 +116,7 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { topNavMenu.push({ key: 'inspect', disableButton() { - return props.workspace === null; + return isInspectDisabled; }, label: i18n.translate('xpack.graph.topNavMenu.inspectLabel', { defaultMessage: 'Inspect', @@ -116,7 +125,14 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { defaultMessage: 'Inspect', }), run: () => { - props.setShowInspect((prevShowInspect) => !prevShowInspect); + onOpenInspector(); + }, + tooltip: () => { + if (isInspectDisabled) { + return i18n.translate('xpack.graph.topNavMenu.inspectButton.disabledTooltip', { + defaultMessage: 'Perform a search or expand a node to enable Inspect', + }); + } }, testId: 'graphInspectButton', }); diff --git a/x-pack/plugins/graph/public/helpers/use_graph_loader.ts b/x-pack/plugins/graph/public/helpers/use_graph_loader.ts index 5b4f3bbbf1e47..0d50039ab9797 100644 --- a/x-pack/plugins/graph/public/helpers/use_graph_loader.ts +++ b/x-pack/plugins/graph/public/helpers/use_graph_loader.ts @@ -5,10 +5,12 @@ * 2.0. */ -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import type { CoreStart, ToastsStart } from '@kbn/core/public'; import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { RequestAdapter } from '@kbn/inspector-plugin/public'; import type { ExploreRequest, GraphExploreCallback, @@ -24,6 +26,7 @@ interface UseGraphLoaderProps { export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoaderProps) => { const [loading, setLoading] = useState(false); + const requestAdapter = useMemo(() => new RequestAdapter(), []); const handleHttpError = useCallback( (error: IHttpFetchError) => { @@ -52,21 +55,56 @@ export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoader [toastNotifications] ); + const getRequestInspector = useCallback( + (indexName: string) => { + const inspectRequest = requestAdapter.start( + i18n.translate('xpack.graph.inspectAdapter.graphExploreRequest.name', { + defaultMessage: 'Data', + }), + { + description: i18n.translate( + 'xpack.graph.inspectAdapter.graphExploreRequest.description', + { + defaultMessage: 'This request queries Elasticsearch to fetch the data for the Graph.', + } + ), + } + ); + inspectRequest.stats({ + indexPattern: { + label: i18n.translate( + 'xpack.graph.inspectAdapter.graphExploreRequest.dataView.description.label', + { defaultMessage: 'Data view' } + ), + value: indexName, + description: i18n.translate( + 'xpack.graph.inspectAdapter.graphExploreRequest.dataView.description', + { defaultMessage: 'The data view that connected to the Elasticsearch indices.' } + ), + }, + }); + return inspectRequest; + }, + [requestAdapter] + ); + // Replacement function for graphClientWorkspace's comms so // that it works with Kibana. const callNodeProxy = useCallback( (indexName: string, query: ExploreRequest, responseHandler: GraphExploreCallback) => { - const request = { - body: JSON.stringify({ - index: indexName, - query, - }), - }; + const dsl = { index: indexName, query }; + const request = { body: JSON.stringify(dsl) }; setLoading(true); + + requestAdapter.reset(); + const inspectRequest = getRequestInspector(indexName); + inspectRequest.json(dsl); + return coreStart.http - .post<{ resp: { timed_out: unknown } }>('../api/graph/graphExplore', request) + .post<{ resp: estypes.GraphExploreResponse }>('../api/graph/graphExplore', request) .then(function (data) { const response = data.resp; + if (response?.timed_out) { toastNotifications.addWarning( i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { @@ -74,38 +112,48 @@ export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoader }) ); } + inspectRequest.stats({}).ok({ json: response }); responseHandler(response); }) - .catch(handleHttpError) + .catch((e) => { + inspectRequest.error({ json: e }); + handleHttpError(e); + }) .finally(() => setLoading(false)); }, - [coreStart.http, handleHttpError, toastNotifications] + [coreStart.http, getRequestInspector, handleHttpError, requestAdapter, toastNotifications] ); // Helper function for the graphClientWorkspace to perform a query const callSearchNodeProxy = useCallback( (indexName: string, query: SearchRequest, responseHandler: GraphSearchCallback) => { - const request = { - body: JSON.stringify({ - index: indexName, - body: query, - }), - }; + const dsl = { index: indexName, body: query }; + const request = { body: JSON.stringify(dsl) }; setLoading(true); + + requestAdapter.reset(); + const inspectRequest = getRequestInspector(indexName); + inspectRequest.json(dsl); + coreStart.http - .post<{ resp: unknown }>('../api/graph/searchProxy', request) + .post<{ resp: estypes.GraphExploreResponse }>('../api/graph/searchProxy', request) .then(function (data) { const response = data.resp; + inspectRequest.stats({}).ok({ json: response }); responseHandler(response); }) - .catch(handleHttpError) + .catch((e) => { + inspectRequest.error({ json: e }); + handleHttpError(e); + }) .finally(() => setLoading(false)); }, - [coreStart.http, handleHttpError] + [coreStart.http, getRequestInspector, handleHttpError, requestAdapter] ); return { loading, + requestAdapter, callNodeProxy, callSearchNodeProxy, handleSearchQueryError, diff --git a/x-pack/plugins/graph/public/helpers/use_inspector.ts b/x-pack/plugins/graph/public/helpers/use_inspector.ts new file mode 100644 index 0000000000000..d6f237e625c3d --- /dev/null +++ b/x-pack/plugins/graph/public/helpers/use_inspector.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 { useCallback, useEffect, useState } from 'react'; +import { + Start as InspectorPublicPluginStart, + InspectorSession, + RequestAdapter, +} from '@kbn/inspector-plugin/public'; + +export const useInspector = ({ + inspect, + requestAdapter, +}: { + inspect: InspectorPublicPluginStart; + requestAdapter: RequestAdapter; +}) => { + const [inspectorSession, setInspectorSession] = useState(); + + useEffect(() => { + return () => { + if (inspectorSession) { + inspectorSession.close(); + } + }; + }, [inspectorSession]); + + const onOpenInspector = useCallback(() => { + const session = inspect.open({ requests: requestAdapter }, {}); + setInspectorSession(session); + }, [inspect, requestAdapter]); + + return { onOpenInspector }; +}; diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index d4f8471426cc5..96dac017eeabb 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -19,6 +19,7 @@ import { DEFAULT_APP_CATEGORIES, } from '@kbn/core/public'; +import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -40,6 +41,7 @@ export interface GraphPluginStartDependencies { data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; savedObjects: SavedObjectsStart; + inspector: InspectorPublicPluginStart; home?: HomePublicPluginStart; spaces?: SpacesApi; } @@ -110,6 +112,7 @@ export class GraphPlugin savedObjects: pluginsStart.savedObjects, uiSettings: core.uiSettings, spaces: pluginsStart.spaces, + inspect: pluginsStart.inspector, }); }, }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e4db41419cb9b..cdaa18f14c6e5 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13917,9 +13917,6 @@ "xpack.graph.icon.tachometer": "Tachymètre", "xpack.graph.icon.user": "Utilisateur", "xpack.graph.icon.users": "Utilisateurs", - "xpack.graph.inspect.requestTabTitle": "Requête", - "xpack.graph.inspect.responseTabTitle": "Réponse", - "xpack.graph.inspect.title": "Inspecter", "xpack.graph.leaveWorkspace.confirmButtonLabel": "Quitter", "xpack.graph.leaveWorkspace.confirmText": "Si vous quittez maintenant, vous perdez les modifications non enregistrées.", "xpack.graph.leaveWorkspace.modalTitle": "Modifications non enregistrées", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 557f4014f1e4c..a7d49509c7756 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13903,9 +13903,6 @@ "xpack.graph.icon.tachometer": "タコメーター", "xpack.graph.icon.user": "ユーザー", "xpack.graph.icon.users": "ユーザー", - "xpack.graph.inspect.requestTabTitle": "リクエスト", - "xpack.graph.inspect.responseTabTitle": "応答", - "xpack.graph.inspect.title": "検査", "xpack.graph.leaveWorkspace.confirmButtonLabel": "それでも移動", "xpack.graph.leaveWorkspace.confirmText": "今移動すると、保存されていない変更が失われます。", "xpack.graph.leaveWorkspace.modalTitle": "保存されていない変更", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7fb05ec21f71d..25bd585b13431 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13923,9 +13923,6 @@ "xpack.graph.icon.tachometer": "转速表", "xpack.graph.icon.user": "用户", "xpack.graph.icon.users": "用户", - "xpack.graph.inspect.requestTabTitle": "请求", - "xpack.graph.inspect.responseTabTitle": "响应", - "xpack.graph.inspect.title": "检查", "xpack.graph.leaveWorkspace.confirmButtonLabel": "离开", "xpack.graph.leaveWorkspace.confirmText": "如果现在离开,将丢失未保存的更改。", "xpack.graph.leaveWorkspace.modalTitle": "未保存的更改", diff --git a/x-pack/test/accessibility/apps/graph.ts b/x-pack/test/accessibility/apps/graph.ts index d13ed5a58f871..85a77f4816273 100644 --- a/x-pack/test/accessibility/apps/graph.ts +++ b/x-pack/test/accessibility/apps/graph.ts @@ -63,12 +63,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('saveCancelButton'); }); - it('Graph inspect panel', async function () { - await testSubjects.click('graphInspectButton'); - await a11y.testAppSnapshot(); - await testSubjects.click('graphInspectButton'); - }); - it('Graph settings - advanced settings tab', async function () { await testSubjects.click('graphSettingsButton'); await a11y.testAppSnapshot(); From a3414f6e2ff705699c3b09497fecc4d9ae049e37 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Thu, 29 Sep 2022 04:39:22 -0400 Subject: [PATCH 159/172] [Fleet] Sample README implementation for Language Clients (#141935) * wip * Adjust language clients APIs * Comment out sample card * Fix checks * Fix translations * Add breadcrumbs and expand sample page Co-authored-by: criamico Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/language_integrations.ts | 14 ++ .../fleet_integration/overview_component.tsx | 24 --- .../sample/sample_client_readme.tsx | 200 ++++++++++++++++++ .../custom_integrations/public/mocks.tsx | 2 +- .../custom_integrations/public/plugin.tsx | 15 +- .../custom_integrations/public/types.ts | 2 +- .../fleet/.storybook/context/index.tsx | 2 +- .../detail/custom_languages_overview.tsx | 8 +- 8 files changed, 223 insertions(+), 44 deletions(-) delete mode 100644 src/plugins/custom_integrations/public/components/fleet_integration/overview_component.tsx create mode 100644 src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx diff --git a/src/plugins/custom_integrations/common/language_integrations.ts b/src/plugins/custom_integrations/common/language_integrations.ts index c9821281f2854..9ba914c02fd0d 100644 --- a/src/plugins/custom_integrations/common/language_integrations.ts +++ b/src/plugins/custom_integrations/common/language_integrations.ts @@ -145,4 +145,18 @@ export const languageIntegrations: LanguageIntegration[] = [ integrationsAppUrl: `/app/integrations/language_clients/java/overview`, exportLanguageUiComponent: false, }, + // Uncomment to show the sample language client card + README UI + // { + // id: 'sample', + // title: i18n.translate('customIntegrations.languageclients.SampleTitle', { + // defaultMessage: 'Sample Language Client', + // }), + // icon: 'es.svg', + // description: i18n.translate('customIntegrations.languageclients.SampleDescription', { + // defaultMessage: 'Sample language client', + // }), + // docUrlTemplate: '', + // integrationsAppUrl: `/app/integrations/language_clients/sample/overview`, + // exportLanguageUiComponent: true, + // }, ]; diff --git a/src/plugins/custom_integrations/public/components/fleet_integration/overview_component.tsx b/src/plugins/custom_integrations/public/components/fleet_integration/overview_component.tsx deleted file mode 100644 index c5b8584e1e4a9..0000000000000 --- a/src/plugins/custom_integrations/public/components/fleet_integration/overview_component.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiTitle } from '@elastic/eui'; - -/* -Example of Overview component to be shown inside Integrations app -*/ -export interface ReadmeProps { - packageName: string; -} -export const OverviewComponent: React.FC = ({ packageName }) => { - return ( - -

    {packageName} client - Overview

    -
    - ); -}; diff --git a/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx b/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx new file mode 100644 index 0000000000000..c2ca0d62da689 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; + +// eslint-disable-next-line @kbn/eslint/module_migration +import styled from 'styled-components'; +import cuid from 'cuid'; + +import { + EuiButton, + EuiCode, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageSection, + EuiSpacer, + EuiText, + EuiTitle, + EuiPanel, + EuiImage, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import icon from '../../../assets/language_clients/es.svg'; + +const CenterColumn = styled(EuiFlexItem)` + max-width: 740px; +`; + +const FixedHeader = styled.div` + width: 100%; + height: 196px; + border-bottom: 1px solid ${euiThemeVars.euiColorLightShade}; +`; + +const IconPanel = styled(EuiPanel)` + padding: ${(props) => props.theme.eui.euiSizeXL}; + width: ${(props) => + parseFloat(props.theme.eui.euiSize) * 6 + parseFloat(props.theme.eui.euiSizeXL) * 2}px; + svg, + img { + height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + width: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + } + .euiFlexItem { + height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + justify-content: center; + } +`; + +const TopFlexGroup = styled(EuiFlexGroup)` + max-width: 1150px; + margin-left: auto; + margin-right: auto; + padding: calc(${euiThemeVars.euiSizeXL} * 2) ${euiThemeVars.euiSizeM} 0 ${euiThemeVars.euiSizeM}; +`; + +export const SampleClientReadme = () => { + const [apiKey, setApiKey] = useState(null); + + return ( + <> + + + + + + + + + +

    + +

    +
    +
    +
    +
    + + + + + + + + + + } + /> + + + + +

    + +

    +
    + + + + + {`# Grab the sample language client from NPM and install it in your project \n`} + {`$ npm install @elastic/elasticsearch-sample`} + +
    + + + +

    + +

    +
    + + + + + + + + + + setApiKey(cuid())} disabled={!!apiKey}> + Generate API key + + + + {apiKey && ( + + + {apiKey} + + + )} + +
    + + + +

    + +

    +
    + + + elastic.config.json, + }} + /> + + + + + + {` +{ + "apiKey": "${apiKey || 'YOUR_API_KEY'} +} + + `} + +
    +
    +
    +
    +
    + + ); +}; diff --git a/src/plugins/custom_integrations/public/mocks.tsx b/src/plugins/custom_integrations/public/mocks.tsx index 038fdb1780458..2503008ea90ec 100644 --- a/src/plugins/custom_integrations/public/mocks.tsx +++ b/src/plugins/custom_integrations/public/mocks.tsx @@ -24,7 +24,7 @@ function createCustomIntegrationsStart(): jest.Mocked { const services = servicesFactory({ startPlugins: {}, coreStart: coreMock.createStart() }); return { - languageClientsUiComponents: new Map(), + languageClientsUiComponents: {}, ContextProvider: jest.fn(({ children }) => ( {children} diff --git a/src/plugins/custom_integrations/public/plugin.tsx b/src/plugins/custom_integrations/public/plugin.tsx index 90a796955a595..827d31ce3749d 100755 --- a/src/plugins/custom_integrations/public/plugin.tsx +++ b/src/plugins/custom_integrations/public/plugin.tsx @@ -19,12 +19,10 @@ import { ROUTES_APPEND_CUSTOM_INTEGRATIONS, ROUTES_REPLACEMENT_CUSTOM_INTEGRATIONS, } from '../common'; -import { languageIntegrations } from '../common/language_integrations'; - -import { OverviewComponent } from './components/fleet_integration/overview_component'; import { CustomIntegrationsServicesProvider } from './services'; import { servicesFactory } from './services/kibana'; +import { SampleClientReadme } from './components/fleet_integration/sample/sample_client_readme'; export class CustomIntegrationsPlugin implements Plugin @@ -48,16 +46,7 @@ export class CustomIntegrationsPlugin ): CustomIntegrationsStart { const services = servicesFactory({ coreStart, startPlugins }); - const languageClientsUiComponents = new Map(); - - // Set the language clients components to render in Fleet plugin under Integrations app - // Export component only if the integration has exportLanguageUiComponent = true - languageIntegrations - .filter((int) => int.exportLanguageUiComponent) - .map((int) => { - const ReadmeComponent = () => ; - languageClientsUiComponents.set(`language_client.${int.id}`, ReadmeComponent); - }); + const languageClientsUiComponents = { sample: SampleClientReadme }; const ContextProvider: React.FC = ({ children }) => ( diff --git a/src/plugins/custom_integrations/public/types.ts b/src/plugins/custom_integrations/public/types.ts index db8acc3e2e07e..60b8aefe23dd1 100755 --- a/src/plugins/custom_integrations/public/types.ts +++ b/src/plugins/custom_integrations/public/types.ts @@ -15,7 +15,7 @@ export interface CustomIntegrationsSetup { export interface CustomIntegrationsStart { ContextProvider: React.FC; - languageClientsUiComponents: Map; + languageClientsUiComponents: Record; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx index 7ef04979969e0..1d5416cf0483d 100644 --- a/x-pack/plugins/fleet/.storybook/context/index.tsx +++ b/x-pack/plugins/fleet/.storybook/context/index.tsx @@ -72,7 +72,7 @@ export const StorybookContext: React.FC<{ storyContext?: Parameters }, customIntegrations: { ContextProvider: getStorybookContextProvider(), - languageClientsUiComponents: new Map(), + languageClientsUiComponents: {}, }, docLinks: getDocLinks(), http: getHttp(), diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom_languages_overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom_languages_overview.tsx index 93e7c6e0fac22..c8c1914599f08 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom_languages_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom_languages_overview.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; import { useParams, Redirect } from 'react-router-dom'; +import { capitalize } from 'lodash'; import { getCustomIntegrationsStart } from '../../../../../../services/custom_integrations'; -import { useLink } from '../../../../../../hooks'; +import { useLink, useBreadcrumbs } from '../../../../hooks'; export interface CustomLanguageClientsParams { pkgkey: string; } @@ -20,10 +21,9 @@ export interface CustomLanguageClientsParams { export const CustomLanguagesOverview = () => { const { pkgkey } = useParams(); const { getPath } = useLink(); + useBreadcrumbs('integration_details_overview', { pkgTitle: capitalize(pkgkey) }); - const Component = getCustomIntegrationsStart().languageClientsUiComponents.get( - `language_client.${pkgkey}` - ); + const Component = getCustomIntegrationsStart().languageClientsUiComponents[pkgkey]; return Component ? : ; }; From 9ad2a9c16c75ed9ca7ce7204e0b86085ef5c2673 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 29 Sep 2022 10:58:43 +0200 Subject: [PATCH 160/172] [Lens] Replace layer dimension groups required flag with more generic one (#141675) * :fire: remove required flag in favour of requiredMinDimensionCount * :ok_hand: Integrate feedback + add more test cases * Update x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx --- .../config_panel/layer_panel.test.tsx | 106 +++++++++++++----- .../editor_frame/config_panel/layer_panel.tsx | 35 +++--- x-pack/plugins/lens/public/types.ts | 1 - .../datatable/visualization.tsx | 2 +- .../gauge/visualization.test.ts | 16 +-- .../visualizations/gauge/visualization.tsx | 4 +- .../heatmap/visualization.test.ts | 18 +-- .../visualizations/heatmap/visualization.tsx | 6 +- .../legacy_metric/visualization.tsx | 2 +- .../__snapshots__/visualization.test.ts.snap | 8 +- .../visualizations/metric/visualization.tsx | 8 +- .../partition/visualization.tsx | 4 +- .../visualizations/xy/annotations/helpers.tsx | 2 +- .../xy/reference_line_helpers.tsx | 2 +- .../visualizations/xy/visualization.test.ts | 6 +- .../visualizations/xy/visualization.tsx | 9 +- 16 files changed, 138 insertions(+), 91 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index baece355d1d54..74394e89f0d63 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -243,7 +243,7 @@ describe('LayerPanel', () => { filterOperations: () => true, supportsMoreColumns: true, dataTestSubj: 'lnsGroup', - required: true, + requiredMinDimensionCount: 1, }, ], }); @@ -303,37 +303,83 @@ describe('LayerPanel', () => { expect(groups.findWhere((e) => e.prop('error') === '')).toHaveLength(1); }); - it('should render the required warning when only one group is configured (with requiredMinDimensionCount)', async () => { - mockVisualization.getConfiguration.mockReturnValue({ - groups: [ - { - groupLabel: 'A', - groupId: 'a', - accessors: [{ columnId: 'x' }], - filterOperations: () => true, - supportsMoreColumns: false, - dataTestSubj: 'lnsGroup', - }, - { - groupLabel: 'B', - groupId: 'b', - accessors: [{ columnId: 'y' }], - filterOperations: () => true, - supportsMoreColumns: true, - dataTestSubj: 'lnsGroup', - requiredMinDimensionCount: 2, - }, - ], - }); - - const { instance } = await mountWithProvider(); + it.each` + minDimensions | accessors | errors + ${1} | ${0} | ${1} + ${2} | ${0} | ${2} + ${2} | ${1} | ${2} + `( + 'should render the required warning for $errors fields when only one group is configured with requiredMinDimensionCount: $minDimensions and $accessors accessors', + async ({ minDimensions, accessors, errors }) => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [{ columnId: 'x' }], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + { + groupLabel: 'B', + groupId: 'b', + accessors: [{ columnId: 'y' }].slice(0, accessors), + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + requiredMinDimensionCount: minDimensions, + }, + ], + }); + const { instance } = await mountWithProvider(); + + const errorMessage = errors === 1 ? 'Requires field' : 'Requires 2 fields'; + + const group = instance.find(EuiFormRow).findWhere((e) => e.prop('error') === errorMessage); + + expect(group).toHaveLength(1); + } + ); + + it.each` + minDimensions | accessors + ${0} | ${0} + ${0} | ${1} + ${1} | ${1} + ${1} | ${2} + ${2} | ${2} + `( + 'should not render the required warning when only one group is configured with requiredMinDimensionCount: $minDimensions and $accessors accessors', + async ({ minDimensions, accessors }) => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [{ columnId: 'x' }], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + { + groupLabel: 'B', + groupId: 'b', + accessors: [{ columnId: 'y' }, { columnId: 'z' }].slice(0, accessors), + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + requiredMinDimensionCount: minDimensions, + }, + ], + }); + const { instance } = await mountWithProvider(); - const group = instance - .find(EuiFormRow) - .findWhere((e) => e.prop('error') === 'Requires 2 fields'); + const group = instance.find(EuiFormRow).findWhere((e) => e.prop('error')); - expect(group).toHaveLength(1); - }); + expect(group).toHaveLength(0); + } + ); it('should render the datasource and visualization panels inside the dimension container', async () => { mockVisualization.getConfiguration.mockReturnValueOnce({ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index ed6d6b8c0553d..cc748df7c3ecd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -381,20 +381,25 @@ export function LayerPanel( let errorText: string = ''; if (!isEmptyLayer) { - if (group.requiredMinDimensionCount) { - errorText = i18n.translate( - 'xpack.lens.editorFrame.requiresTwoOrMoreFieldsWarningLabel', - { - defaultMessage: 'Requires {requiredMinDimensionCount} fields', - values: { - requiredMinDimensionCount: group.requiredMinDimensionCount, - }, - } - ); - } else if (group.required && group.accessors.length === 0) { - errorText = i18n.translate('xpack.lens.editorFrame.requiresFieldWarningLabel', { - defaultMessage: 'Requires field', - }); + if ( + group.requiredMinDimensionCount && + group.requiredMinDimensionCount > group.accessors.length + ) { + if (group.requiredMinDimensionCount > 1) { + errorText = i18n.translate( + 'xpack.lens.editorFrame.requiresTwoOrMoreFieldsWarningLabel', + { + defaultMessage: 'Requires {requiredMinDimensionCount} fields', + values: { + requiredMinDimensionCount: group.requiredMinDimensionCount, + }, + } + ); + } else { + errorText = i18n.translate('xpack.lens.editorFrame.requiresFieldWarningLabel', { + defaultMessage: 'Requires field', + }); + } } else if (group.dimensionsTooMany && group.dimensionsTooMany > 0) { errorText = i18n.translate( 'xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel', @@ -408,7 +413,7 @@ export function LayerPanel( ); } } - const isOptional = !group.required && !group.suggestedValue; + const isOptional = !group.requiredMinDimensionCount && !group.suggestedValue; return ( !op.isBucketed, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsDatatable_metrics', enableDimensionEditor: true, }, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts index 94c9ce28987bf..e999cac9dd0e6 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts @@ -105,7 +105,7 @@ describe('gauge', () => { accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsGauge_metricDimensionPanel', enableDimensionEditor: true, enableFormatSelector: true, @@ -155,7 +155,7 @@ describe('gauge', () => { accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsGauge_goalDimensionPanel', enableFormatSelector: false, supportStaticValue: true, @@ -187,7 +187,7 @@ describe('gauge', () => { accessors: [], filterOperations: isNumericDynamicMetric, supportsMoreColumns: true, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsGauge_metricDimensionPanel', enableDimensionEditor: true, enableFormatSelector: true, @@ -237,7 +237,7 @@ describe('gauge', () => { accessors: [], filterOperations: isNumericMetric, supportsMoreColumns: true, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsGauge_goalDimensionPanel', enableFormatSelector: false, supportStaticValue: true, @@ -275,7 +275,7 @@ describe('gauge', () => { accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsGauge_metricDimensionPanel', enableDimensionEditor: true, enableFormatSelector: true, @@ -325,7 +325,7 @@ describe('gauge', () => { accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsGauge_goalDimensionPanel', enableFormatSelector: false, supportStaticValue: true, @@ -368,7 +368,7 @@ describe('gauge', () => { accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsGauge_metricDimensionPanel', enableDimensionEditor: true, enableFormatSelector: true, @@ -422,7 +422,7 @@ describe('gauge', () => { accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsGauge_goalDimensionPanel', enableFormatSelector: false, supportStaticValue: true, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index 37d10918d4129..19fd46459b2b7 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -275,7 +275,7 @@ export const getGaugeVisualization = ({ : [], filterOperations: isNumericDynamicMetric, supportsMoreColumns: !metricAccessor, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsGauge_metricDimensionPanel', enableDimensionEditor: true, }, @@ -352,7 +352,7 @@ export const getGaugeVisualization = ({ accessors: state.goalAccessor ? [{ columnId: state.goalAccessor }] : [], filterOperations: isNumericMetric, supportsMoreColumns: !state.goalAccessor, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsGauge_goalDimensionPanel', }, ], diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts index ee6a7030a0c9d..38cfa9bf10b54 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts @@ -136,7 +136,7 @@ describe('heatmap', () => { accessors: [{ columnId: 'x-accessor' }], filterOperations: filterOperationsAxis, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_xDimensionPanel', }, { @@ -146,7 +146,7 @@ describe('heatmap', () => { accessors: [{ columnId: 'y-accessor' }], filterOperations: filterOperationsAxis, supportsMoreColumns: false, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsHeatmap_yDimensionPanel', }, { @@ -165,7 +165,7 @@ describe('heatmap', () => { ], filterOperations: isCellValueSupported, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_cellPanel', enableDimensionEditor: true, }, @@ -194,7 +194,7 @@ describe('heatmap', () => { accessors: [{ columnId: 'x-accessor' }], filterOperations: filterOperationsAxis, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_xDimensionPanel', }, { @@ -204,7 +204,7 @@ describe('heatmap', () => { accessors: [], filterOperations: filterOperationsAxis, supportsMoreColumns: true, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsHeatmap_yDimensionPanel', }, { @@ -217,7 +217,7 @@ describe('heatmap', () => { accessors: [], filterOperations: isCellValueSupported, supportsMoreColumns: true, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_cellPanel', enableDimensionEditor: true, }, @@ -250,7 +250,7 @@ describe('heatmap', () => { accessors: [{ columnId: 'x-accessor' }], filterOperations: filterOperationsAxis, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_xDimensionPanel', }, { @@ -260,7 +260,7 @@ describe('heatmap', () => { accessors: [{ columnId: 'y-accessor' }], filterOperations: filterOperationsAxis, supportsMoreColumns: false, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsHeatmap_yDimensionPanel', }, { @@ -278,7 +278,7 @@ describe('heatmap', () => { ], filterOperations: isCellValueSupported, supportsMoreColumns: false, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_cellPanel', enableDimensionEditor: true, }, diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index 09548df0a67e4..1fc48b4d71c3e 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -181,7 +181,7 @@ export const getHeatmapVisualization = ({ accessors: state.xAccessor ? [{ columnId: state.xAccessor }] : [], filterOperations: filterOperationsAxis, supportsMoreColumns: !state.xAccessor, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_xDimensionPanel', }, { @@ -191,7 +191,7 @@ export const getHeatmapVisualization = ({ accessors: state.yAccessor ? [{ columnId: state.yAccessor }] : [], filterOperations: filterOperationsAxis, supportsMoreColumns: !state.yAccessor, - required: false, + requiredMinDimensionCount: 0, dataTestSubj: 'lnsHeatmap_yDimensionPanel', }, { @@ -224,7 +224,7 @@ export const getHeatmapVisualization = ({ filterOperations: isCellValueSupported, supportsMoreColumns: !state.valueAccessor, enableDimensionEditor: true, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsHeatmap_cellPanel', }, ], diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx index f1b1ffd1f00b4..02a4cd23ad4f8 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx @@ -258,7 +258,7 @@ export const getLegacyMetricVisualization = ({ filterOperations: (op: OperationMetadata) => !op.isBucketed && legacyMetricSupportedTypes.has(op.dataType), enableDimensionEditor: true, - required: true, + requiredMinDimensionCount: 1, }, ], }; diff --git a/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap b/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap index 8c565cbed53be..7d1df68158266 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap +++ b/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap @@ -24,7 +24,7 @@ Object { "paramEditorCustomProps": Object { "headingLabel": "Value", }, - "required": true, + "requiredMinDimensionCount": 1, "supportsMoreColumns": false, }, Object { @@ -46,7 +46,7 @@ Object { "paramEditorCustomProps": Object { "headingLabel": "Value", }, - "required": false, + "requiredMinDimensionCount": 0, "supportsMoreColumns": false, }, Object { @@ -70,7 +70,7 @@ Object { "headingLabel": "Value", }, "prioritizedOperation": "max", - "required": false, + "requiredMinDimensionCount": 0, "supportStaticValue": true, "supportsMoreColumns": false, }, @@ -91,7 +91,7 @@ Object { "groupId": "breakdownBy", "groupLabel": "Break down by", "layerId": "first", - "required": false, + "requiredMinDimensionCount": 0, "supportsMoreColumns": false, }, ], diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index b58068b3ec202..81f7f08fe3223 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -315,7 +315,7 @@ export const getMetricVisualization = ({ enableDimensionEditor: true, enableFormatSelector: true, formatSelectorOptions: formatterOptions, - required: true, + requiredMinDimensionCount: 1, }, { groupId: GROUP_ID.SECONDARY_METRIC, @@ -341,7 +341,7 @@ export const getMetricVisualization = ({ enableDimensionEditor: true, enableFormatSelector: true, formatSelectorOptions: formatterOptions, - required: false, + requiredMinDimensionCount: 0, }, { groupId: GROUP_ID.MAX, @@ -367,7 +367,7 @@ export const getMetricVisualization = ({ formatSelectorOptions: formatterOptions, supportStaticValue: true, prioritizedOperation: 'max', - required: false, + requiredMinDimensionCount: 0, groupTooltip: i18n.translate('xpack.lens.metric.maxTooltip', { defaultMessage: 'If the maximum value is specified, the minimum value is fixed at zero.', @@ -393,7 +393,7 @@ export const getMetricVisualization = ({ enableDimensionEditor: true, enableFormatSelector: true, formatSelectorOptions: formatterOptions, - required: false, + requiredMinDimensionCount: 0, }, ], }; diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index a60578aa1f0ba..2a8bdc2dd4ab0 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -168,7 +168,7 @@ export const getPieVisualization = ({ } const primaryGroupConfigBaseProps = { - required: true, + requiredMinDimensionCount: 1, groupId: 'primaryGroups', accessors, enableDimensionEditor: true, @@ -284,7 +284,7 @@ export const getPieVisualization = ({ accessors: layer.metric ? [{ columnId: layer.metric }] : [], supportsMoreColumns: !layer.metric, filterOperations: numberMetricOperations, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsPie_sizeByDimensionPanel', }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx index 34aff26582771..baaed78ec0237 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx @@ -414,7 +414,7 @@ export const getAnnotationsConfiguration = ({ invalidMessage: i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp', { defaultMessage: 'Annotations require a time based chart to work. Add a date histogram.', }), - required: false, + requiredMinDimensionCount: 0, supportsMoreColumns: true, supportFieldFormat: false, enableDimensionEditor: true, diff --git a/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx index 362b63c46eb2d..84f0a35d3b95e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx @@ -461,7 +461,7 @@ export const getReferenceConfiguration = ({ accessors: config.map(({ forAccessor, color }) => getSingleColorConfig(forAccessor, color)), filterOperations: isNumericMetric, supportsMoreColumns: true, - required: false, + requiredMinDimensionCount: 0, enableDimensionEditor: true, supportStaticValue: true, paramEditorCustomProps: { diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts index 818df64fba94f..556a89c9a8553 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts @@ -1047,7 +1047,7 @@ describe('xy_visualization', () => { frame, layerId: 'first', }).groups; - expect(splitGroup.required).toBe(true); + expect(splitGroup.requiredMinDimensionCount).toBe(1); }); test.each([ @@ -1087,7 +1087,7 @@ describe('xy_visualization', () => { frame, layerId: 'first', }).groups; - expect(splitGroup.required).toBe(false); + expect(splitGroup.requiredMinDimensionCount).toBe(0); } ); @@ -1236,7 +1236,7 @@ describe('xy_visualization', () => { frame, layerId: 'first', }).groups; - expect(splitGroup.required).toBe(true); + expect(splitGroup.requiredMinDimensionCount).toBe(1); } ); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 61bdd4151219c..34ab6c88ffa19 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -276,10 +276,6 @@ export const getXyVisualization = ({ accessors: sortedAccessors, }); - if (isReferenceLayer(layer)) { - return getReferenceConfiguration({ state, frame, layer, sortedAccessors }); - } - const dataLayer: XYDataLayerConfig = layer; const dataLayers = getDataLayers(state.layers); @@ -332,7 +328,7 @@ export const getXyVisualization = ({ accessors: mappedAccessors, filterOperations: isNumericDynamicMetric, supportsMoreColumns: true, - required: true, + requiredMinDimensionCount: 1, dataTestSubj: 'lnsXY_yDimensionPanel', enableDimensionEditor: true, }, @@ -357,7 +353,8 @@ export const getXyVisualization = ({ filterOperations: isBucketed, supportsMoreColumns: !dataLayer.splitAccessor, dataTestSubj: 'lnsXY_splitDimensionPanel', - required: dataLayer.seriesType.includes('percentage') && hasOnlyOneAccessor, + requiredMinDimensionCount: + dataLayer.seriesType.includes('percentage') && hasOnlyOneAccessor ? 1 : 0, enableDimensionEditor: true, }, ], From ef73713337ea217ff8c4602696f2b0328f3e50b2 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 29 Sep 2022 11:01:48 +0200 Subject: [PATCH 161/172] clear state transfer on navigating away (#141969) --- x-pack/plugins/lens/public/app_plugin/mounter.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7cf1331282d4e..ef1933dbd5ca1 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -386,5 +386,6 @@ export async function mountApp( lensServices.inspector.close(); unlistenParentHistory(); lensStore.dispatch(navigateAway()); + stateTransfer.clearEditorState?.(APP_ID); }; } From 226efc0c1f54d634880d5399ce2b85b384f6a19f Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 29 Sep 2022 11:02:11 +0200 Subject: [PATCH 162/172] [Lens] Quickfunction help (#141399) * quickfunction help * adjust copy * Update x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dimension_panel/dimension_editor.tsx | 91 ++++++++++++++++++- .../definitions/calculations/counter_rate.tsx | 8 ++ .../calculations/cumulative_sum.tsx | 8 ++ .../definitions/calculations/differences.tsx | 8 ++ .../calculations/moving_average.tsx | 8 ++ .../operations/definitions/cardinality.tsx | 8 ++ .../operations/definitions/count.tsx | 5 + .../operations/definitions/date_histogram.tsx | 8 ++ .../definitions/filters/filters.tsx | 8 ++ .../operations/definitions/index.ts | 1 + .../operations/definitions/last_value.tsx | 8 ++ .../operations/definitions/metrics.tsx | 40 ++++++++ .../operations/definitions/percentile.tsx | 8 ++ .../definitions/percentile_ranks.tsx | 8 ++ .../operations/definitions/ranges/ranges.tsx | 5 + .../operations/definitions/terms/index.tsx | 5 + 16 files changed, 224 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index fbd9a5650013d..5a975482e6250 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -20,6 +20,11 @@ import { useEuiTheme, EuiFlexGroup, EuiFlexItem, + EuiPopover, + EuiPopoverTitle, + EuiPanel, + EuiBasicTable, + EuiButtonIcon, } from '@elastic/eui'; import ReactDOM from 'react-dom'; import type { IndexPatternDimensionEditorProps } from './dimension_panel'; @@ -116,6 +121,10 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn && operationDefinitionMap[selectedColumn.operationType]; const [temporaryState, setTemporaryState] = useState('none'); + const [isHelpOpen, setIsHelpOpen] = useState(false); + + const onHelpClick = () => setIsHelpOpen((prevIsHelpOpen) => !prevIsHelpOpen); + const closeHelp = () => setIsHelpOpen(false); const temporaryQuickFunction = Boolean(temporaryState === quickFunctionsName); const temporaryStaticValue = Boolean(temporaryState === staticValueOperationName); @@ -596,12 +605,88 @@ export function DimensionEditor(props: DimensionEditorProps) { ...services, }; + const helpButton = ; + + const columnsSidebar = [ + { + field: 'function', + name: i18n.translate('xpack.lens.indexPattern.functionTable.functionHeader', { + defaultMessage: 'Function', + }), + width: '150px', + }, + { + field: 'description', + name: i18n.translate('xpack.lens.indexPattern.functionTable.descriptionHeader', { + defaultMessage: 'Description', + }), + }, + ]; + + const items = sideNavItems + .filter((item) => operationDefinitionMap[item.id!].quickFunctionDocumentation) + .map((item) => { + const operationDefinition = operationDefinitionMap[item.id!]!; + return { + id: item.id!, + function: operationDefinition.displayName, + description: operationDefinition.quickFunctionDocumentation!, + }; + }); + const quickFunctions = ( <> + + + + {i18n.translate('xpack.lens.indexPattern.quickFunctions.popoverTitle', { + defaultMessage: 'Quick functions', + })} + + + + + + + + {i18n.translate('xpack.lens.indexPattern.functionsLabel', { + defaultMessage: 'Functions', + })} + + + } fullWidth > ); }, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.dateHistogram.documentation.quick', + { + defaultMessage: ` +The date or date range values distributed into intervals. + `, + } + ), }; function parseInterval(currentInterval: string) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index da06ea8ae1bf4..b972129109e1e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -158,6 +158,14 @@ export const filtersOperation: OperationDefinition< }, getMaxPossibleNumValues: (column) => column.params.filters.length, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.filters.documentation.quick', + { + defaultMessage: ` + Divides values into predefined subsets. + `, + } + ), }; export const FilterList = ({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 5ce13adbf5ca8..6c79d314d10fa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -364,6 +364,7 @@ interface BaseOperationDefinitionProps< description: string; section: 'elasticsearch' | 'calculation'; }; + quickFunctionDocumentation?: string; /** * React component for operation field specific behaviour */ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 2709d22b2a323..4206cd8109760 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -428,4 +428,12 @@ Example: Get the current status of server A: `, }), }, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.lastValue.documentation.quick', + { + defaultMessage: ` +The value of a field from the last document, ordered by the default time field of the data view. + `, + } + ), }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index 09dc8576a042a..e8c8c54c1cefe 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -60,6 +60,7 @@ function buildMetricOperation>({ hideZeroOption, aggConfigParams, documentationDescription, + quickFunctionDocumentation, }: { type: T['operationType']; displayName: string; @@ -71,6 +72,7 @@ function buildMetricOperation>({ hideZeroOption?: boolean; aggConfigParams?: Record; documentationDescription?: string; + quickFunctionDocumentation?: string; }) { const labelLookup = (name: string, column?: BaseIndexPatternColumn) => { const label = ofName(name); @@ -240,6 +242,7 @@ Example: Get the {metric} of price for orders from the UK: }, }), }, + quickFunctionDocumentation, shiftable: true, } as OperationDefinition; } @@ -265,6 +268,12 @@ export const minOperation = buildMetricOperation({ defaultMessage: 'A single-value metrics aggregation that returns the minimum value among the numeric values extracted from the aggregated documents.', }), + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.min.quickFunctionDescription', + { + defaultMessage: 'The minimum value of a number field.', + } + ), supportsDate: true, }); @@ -282,6 +291,12 @@ export const maxOperation = buildMetricOperation({ defaultMessage: 'A single-value metrics aggregation that returns the maximum value among the numeric values extracted from the aggregated documents.', }), + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.max.quickFunctionDescription', + { + defaultMessage: 'The maximum value of a number field.', + } + ), supportsDate: true, }); @@ -300,6 +315,12 @@ export const averageOperation = buildMetricOperation({ defaultMessage: 'A single-value metric aggregation that computes the average of numeric values that are extracted from the aggregated documents', }), + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.avg.quickFunctionDescription', + { + defaultMessage: 'The average value of a number field.', + } + ), }); export const standardDeviationOperation = buildMetricOperation( @@ -334,6 +355,13 @@ To get the variance of price for orders from the UK, use \`square(standard_devia `, } ), + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.standardDeviation.quickFunctionDescription', + { + defaultMessage: + 'The standard deviation of the values of a number field which is the amount of variation of the fields values.', + } + ), } ); @@ -354,6 +382,12 @@ export const sumOperation = buildMetricOperation({ 'A single-value metrics aggregation that sums up numeric values that are extracted from the aggregated documents.', }), hideZeroOption: true, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.sum.quickFunctionDescription', + { + defaultMessage: 'The total amount of the values of a number field.', + } + ), }); export const medianOperation = buildMetricOperation({ @@ -371,4 +405,10 @@ export const medianOperation = buildMetricOperation({ defaultMessage: 'A single-value metrics aggregation that computes the median value that are extracted from the aggregated documents.', }), + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.median.quickFunctionDescription', + { + defaultMessage: 'The median value of a number field.', + } + ), }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index 3bc91b91ed3d6..ec4a3d569e7da 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -417,4 +417,12 @@ Example: Get the number of bytes larger than 95 % of values: `, }), }, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.percentile.documentation.quick', + { + defaultMessage: ` + The largest value that is smaller than n percent of the values that occur in all documents. + `, + } + ), }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx index 7cb8d7ea64aea..45bd05f37372f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx @@ -266,4 +266,12 @@ Example: Get the percentage of values which are below of 100: `, }), }, + quickFunctionDocumentation: i18n.translate( + 'xpack.lens.indexPattern.percentileRanks.documentation.quick', + { + defaultMessage: ` +The percentage of values that are below a specific value. For example, when a value is greater than or equal to 95% of the calculated values, the value is the 95th percentile rank. + `, + } + ), }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index aa84c727ae507..52c9471aefe0e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -267,4 +267,9 @@ export const rangeOperation: OperationDefinition< /> ); }, + quickFunctionDocumentation: i18n.translate('xpack.lens.indexPattern.ranges.documentation.quick', { + defaultMessage: ` + Buckets values along defined numeric ranges. + `, + }), }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 9017e91bff089..1f4b4846906d4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -557,6 +557,11 @@ export const termsOperation: OperationDefinition< ); }, + quickFunctionDocumentation: i18n.translate('xpack.lens.indexPattern.terms.documentation.quick', { + defaultMessage: ` +The top values of a specified field ranked by the chosen metric. + `, + }), paramEditor: function ParamEditor({ layer, paramEditorUpdater, From 574e96a868ccd05be0fde59b09ef8cd03e7e6a15 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:38:30 +0200 Subject: [PATCH 163/172] Update dependency vega-lite to ^5.5.0 (#137794) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index eedb8db4b605a..3bbcabba0cdd6 100644 --- a/package.json +++ b/package.json @@ -642,7 +642,7 @@ "uuid": "3.3.2", "vega": "^5.22.1", "vega-interpreter": "^1.0.4", - "vega-lite": "^5.3.0", + "vega-lite": "^5.5.0", "vega-schema-url-parser": "^2.2.0", "vega-spec-injector": "^0.0.2", "vega-tooltip": "^0.28.0", diff --git a/yarn.lock b/yarn.lock index 4996c786772dd..804502ebeb4c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27950,10 +27950,10 @@ vega-label@~1.2.0: vega-scenegraph "^4.9.2" vega-util "^1.15.2" -vega-lite@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.3.0.tgz#b9b9ecd80e869e823e6848c67d0a8ad94954bdee" - integrity sha512-6giodZ/bJnWyLq6Gj4OyiDt7EndoGyC9f5xDQjo82yPpUiO4MuG9iiPMqR1SPKmG9/qPBf+klWQR0v/7Mgju0Q== +vega-lite@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.5.0.tgz#07345713d538cd63278748ec119c261722be66ff" + integrity sha512-MQBJt/iaUegvhRTS/hZVWfMOSF5ai4awlR2qtwTgHd84bErf9v7GtaZ9ArhJqXCb+FizvZ2jatmoYCzovgAhkg== dependencies: "@types/clone" "~2.1.1" array-flat-polyfill "^1.0.1" From d915169c5038ef962347448229d095532c463f12 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 29 Sep 2022 10:43:27 +0100 Subject: [PATCH 164/172] [Security Solution] Update visualizationType (#141235) * update visualizationType * update labels * add snapshots for charts --- .../__snapshots__/authentication.test.ts.snap | 276 ++++++++++++++++++ .../__snapshots__/external_alert.test.ts.snap | 231 +++++++++++++++ .../common/authentication.test.ts | 45 +++ .../lens_attributes/common/authentication.ts | 8 +- .../common/external_alert.test.ts | 45 +++ .../lens_attributes/common/external_alert.ts | 5 +- .../hosts/__snapshots__/event.test.ts.snap | 205 +++++++++++++ .../__snapshots__/kpi_host_area.test.ts.snap | 196 +++++++++++++ .../kpi_host_metric.test.ts.snap | 143 +++++++++ .../kpi_unique_ips_area.test.ts.snap | 255 ++++++++++++++++ .../kpi_unique_ips_bar.test.ts.snap | 265 +++++++++++++++++ ...unique_ips_destination_metric.test.ts.snap | 143 +++++++++ .../kpi_unique_ips_source_metric.test.ts.snap | 143 +++++++++ .../lens_attributes/hosts/event.test.ts | 45 +++ .../hosts/kpi_host_area.test.ts | 45 +++ .../lens_attributes/hosts/kpi_host_area.ts | 3 +- .../hosts/kpi_host_metric.test.ts | 45 +++ .../lens_attributes/hosts/kpi_host_metric.ts | 2 +- .../hosts/kpi_unique_ips_area.test.ts | 45 +++ .../hosts/kpi_unique_ips_bar.test.ts | 45 +++ .../hosts/kpi_unique_ips_bar.ts | 6 +- .../kpi_unique_ips_destination_metric.test.ts | 45 +++ .../kpi_unique_ips_destination_metric.ts | 2 +- .../kpi_unique_ips_source_metric.test.ts | 45 +++ .../hosts/kpi_unique_ips_source_metric.ts | 2 +- .../dns_top_domains.test.ts.snap | 245 ++++++++++++++++ .../kpi_dns_queries.test.ts.snap | 189 ++++++++++++ .../kpi_network_events.test.ts.snap | 193 ++++++++++++ .../kpi_tls_handshakes.test.ts.snap | 212 ++++++++++++++ .../kpi_unique_flow_ids.test.ts.snap | 176 +++++++++++ .../kpi_unique_private_ips_area.test.ts.snap | 261 +++++++++++++++++ .../kpi_unique_private_ips_bar.test.ts.snap | 276 ++++++++++++++++++ ...rivate_ips_destination_metric.test.ts.snap | 149 ++++++++++ ...que_private_ips_source_metric.test.ts.snap | 149 ++++++++++ .../network/dns_top_domains.test.ts | 45 +++ .../network/dns_top_domains.ts | 5 +- .../network/kpi_dns_queries.test.ts | 45 +++ .../network/kpi_dns_queries.ts | 2 +- .../network/kpi_network_events.test.ts | 45 +++ .../network/kpi_network_events.ts | 2 +- .../network/kpi_tls_handshakes.test.ts | 45 +++ .../network/kpi_tls_handshakes.ts | 2 +- .../network/kpi_unique_flow_ids.test.ts | 45 +++ .../network/kpi_unique_flow_ids.ts | 2 +- .../kpi_unique_private_ips_area.test.ts | 45 +++ .../network/kpi_unique_private_ips_area.ts | 5 +- .../kpi_unique_private_ips_bar.test.ts | 45 +++ .../network/kpi_unique_private_ips_bar.ts | 6 +- ...que_private_ips_destination_metric.test.ts | 45 +++ ...i_unique_private_ips_destination_metric.ts | 2 +- ...i_unique_private_ips_source_metric.test.ts | 45 +++ .../kpi_unique_private_ips_source_metric.ts | 2 +- .../kpi_total_users_area.test.ts.snap | 151 ++++++++++ .../kpi_total_users_metric.test.ts.snap | 97 ++++++ ...authentication_metric_failure.test.ts.snap | 126 ++++++++ ...kpi_user_authentications_area.test.ts.snap | 240 +++++++++++++++ .../kpi_user_authentications_bar.test.ts.snap | 240 +++++++++++++++ ...uthentications_metric_success.test.ts.snap | 127 ++++++++ .../users/kpi_total_users_area.test.ts | 45 +++ .../users/kpi_total_users_area.ts | 3 +- .../users/kpi_total_users_metric.test.ts | 45 +++ .../users/kpi_total_users_metric.ts | 4 +- ...user_authentication_metric_failure.test.ts | 45 +++ .../kpi_user_authentication_metric_failure.ts | 2 +- .../kpi_user_authentications_area.test.ts | 45 +++ .../users/kpi_user_authentications_area.ts | 5 +- .../kpi_user_authentications_bar.test.ts | 45 +++ ...ser_authentications_metric_success.test.ts | 45 +++ ...kpi_user_authentications_metric_success.ts | 2 +- .../visualization_actions/mocks.tsx | 65 +++++ .../visualization_actions/translations.ts | 32 ++ .../use_lens_attributes.test.tsx | 65 +---- 72 files changed, 5909 insertions(+), 93 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_area.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_metric.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentication_metric_failure.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_bar.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_metric_success.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap new file mode 100644 index 0000000000000..747487203066b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap @@ -0,0 +1,276 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`authenticationLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-3fd0c5d5-f762-4a27-8c56-14eee0223e13", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-bef502be-e5ff-442f-9e3e-229f86ca2afa", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "6f4dbdc7-35b6-4e20-ac53-1272167e3919", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "3fd0c5d5-f762-4a27-8c56-14eee0223e13": Object { + "columnOrder": Array [ + "b41a2958-650b-470a-84c4-c6fd8f0c6d37", + "5417777d-d9d9-4268-9cdc-eb29b873bd65", + ], + "columns": Object { + "5417777d-d9d9-4268-9cdc-eb29b873bd65": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome : \\"success\\"", + }, + "isBucketed": false, + "label": "Success", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "b41a2958-650b-470a-84c4-c6fd8f0c6d37": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + "bef502be-e5ff-442f-9e3e-229f86ca2afa": Object { + "columnOrder": Array [ + "cded27f7-8ef8-458c-8d9b-70db48ae340d", + "a3bf9dc1-c8d2-42d6-9e60-31892a4c509e", + ], + "columns": Object { + "a3bf9dc1-c8d2-42d6-9e60-31892a4c509e": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome : \\"failure\\"", + }, + "isBucketed": false, + "label": "Failure", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "cded27f7-8ef8-458c-8d9b-70db48ae340d": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "6f4dbdc7-35b6-4e20-ac53-1272167e3919", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"must\\":[{\\"term\\":{\\"event.category\\":\\"authentication\\"}}]}}", + }, + "query": Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "event.category": "authentication", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "5417777d-d9d9-4268-9cdc-eb29b873bd65", + ], + "layerId": "3fd0c5d5-f762-4a27-8c56-14eee0223e13", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "xAccessor": "b41a2958-650b-470a-84c4-c6fd8f0c6d37", + "yConfig": Array [ + Object { + "color": "#54b399", + "forAccessor": "5417777d-d9d9-4268-9cdc-eb29b873bd65", + }, + ], + }, + Object { + "accessors": Array [ + "a3bf9dc1-c8d2-42d6-9e60-31892a4c509e", + ], + "layerId": "bef502be-e5ff-442f-9e3e-229f86ca2afa", + "layerType": "data", + "seriesType": "bar_stacked", + "xAccessor": "cded27f7-8ef8-458c-8d9b-70db48ae340d", + "yConfig": Array [ + Object { + "color": "#da8b45", + "forAccessor": "a3bf9dc1-c8d2-42d6-9e60-31892a4c509e", + }, + ], + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Authentication", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap new file mode 100644 index 0000000000000..ac42b228012fe --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap @@ -0,0 +1,231 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getExternalAlertLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-a3c54471-615f-4ff9-9fda-69b5b2ea3eef", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "723c4653-681b-4105-956e-abef287bf025", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "a04472fc-94a3-4b8d-ae05-9d30ea8fbd6a", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "a3c54471-615f-4ff9-9fda-69b5b2ea3eef": Object { + "columnOrder": Array [ + "42334c6e-98d9-47a2-b4cb-a445abb44c93", + "37bdf546-3c11-4b08-8c5d-e37debc44f1d", + "0a923af2-c880-4aa3-aa93-a0b9c2801f6d", + ], + "columns": Object { + "0a923af2-c880-4aa3-aa93-a0b9c2801f6d": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "37bdf546-3c11-4b08-8c5d-e37debc44f1d": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "42334c6e-98d9-47a2-b4cb-a445abb44c93": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.dataset", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "0a923af2-c880-4aa3-aa93-a0b9c2801f6d", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 10, + }, + "scale": "ordinal", + "sourceField": "event.dataset", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "a04472fc-94a3-4b8d-ae05-9d30ea8fbd6a", + "key": "event.kind", + "negate": false, + "params": Object { + "query": "alert", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "event.kind": "alert", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "accessors": Array [ + "0a923af2-c880-4aa3-aa93-a0b9c2801f6d", + ], + "layerId": "a3c54471-615f-4ff9-9fda-69b5b2ea3eef", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "42334c6e-98d9-47a2-b4cb-a445abb44c93", + "xAccessor": "37bdf546-3c11-4b08-8c5d-e37debc44f1d", + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "External alerts", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts new file mode 100644 index 0000000000000..808789db397e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { authenticationLensAttributes } from './authentication'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('authenticationLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: authenticationLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts index 199f78d372cd8..15d7f029ae612 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + AUTHENCICATION_FAILURE_CHART_LABEL, + AUTHENCICATION_SUCCESS_CHART_LABEL, +} from '../../translations'; import type { LensAttributes } from '../../types'; export const authenticationLensAttributes: LensAttributes = { @@ -110,7 +114,7 @@ export const authenticationLensAttributes: LensAttributes = { }, }, '5417777d-d9d9-4268-9cdc-eb29b873bd65': { - label: 'Success', + label: AUTHENCICATION_SUCCESS_CHART_LABEL, dataType: 'number', operationType: 'count', isBucketed: false, @@ -143,7 +147,7 @@ export const authenticationLensAttributes: LensAttributes = { }, }, 'a3bf9dc1-c8d2-42d6-9e60-31892a4c509e': { - label: 'Failure', + label: AUTHENCICATION_FAILURE_CHART_LABEL, dataType: 'number', operationType: 'count', isBucketed: false, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts new file mode 100644 index 0000000000000..da890c4d49fe0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { getExternalAlertLensAttributes } from './external_alert'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('getExternalAlertLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getExternalAlertLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts index e24db03b3302f..a815d442b043e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { COUNT, TOP_VALUE } from '../../translations'; import type { GetLensAttributes, LensAttributes } from '../../types'; export const getExternalAlertLensAttributes: GetLensAttributes = ( @@ -86,7 +87,7 @@ export const getExternalAlertLensAttributes: GetLensAttributes = ( }, }, '0a923af2-c880-4aa3-aa93-a0b9c2801f6d': { - label: 'Count of records', + label: COUNT, dataType: 'number', operationType: 'count', isBucketed: false, @@ -94,7 +95,7 @@ export const getExternalAlertLensAttributes: GetLensAttributes = ( sourceField: '___records___', }, '42334c6e-98d9-47a2-b4cb-a445abb44c93': { - label: `Top values of ${stackByField}`, // could be event.category + label: TOP_VALUE(`${stackByField}`), // could be event.category dataType: 'string', operationType: 'terms', scale: 'ordinal', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap new file mode 100644 index 0000000000000..7f3fdb6c66107 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap @@ -0,0 +1,205 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getEventsHistogramLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.dataset", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "asc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 10, + }, + "scale": "ordinal", + "sourceField": "event.dataset", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Host - events", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap new file mode 100644 index 0000000000000..c5e4e272ec9c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap @@ -0,0 +1,196 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiHostAreaLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "416b6fad-1923-4f6a-a2df-b223bb287e30": Object { + "columnOrder": Array [ + "5eea817b-67b7-4268-8ecb-7688d1094721", + "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + ], + "columns": Object { + "5eea817b-67b7-4268-8ecb-7688d1094721": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "b00c65ea-32be-4163-bfc8-f795b1ef9d06": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Unique count of host.name", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "host.name", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": false, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + ], + "layerId": "416b6fad-1923-4f6a-a2df-b223bb287e30", + "layerType": "data", + "seriesType": "area", + "xAccessor": "5eea817b-67b7-4268-8ecb-7688d1094721", + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "area", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Host] Hosts - area", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap new file mode 100644 index 0000000000000..3669de2d30109 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiHostMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "416b6fad-1923-4f6a-a2df-b223bb287e30": Object { + "columnOrder": Array [ + "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + ], + "columns": Object { + "b00c65ea-32be-4163-bfc8-f795b1ef9d06": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "host.name", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + "layerId": "416b6fad-1923-4f6a-a2df-b223bb287e30", + "layerType": "data", + }, + }, + "title": "[Host] Hosts - metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap new file mode 100644 index 0000000000000..acaf78556269f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap @@ -0,0 +1,255 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniqueIpsAreaLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-ca05ecdb-0fa4-49a8-9305-b23d91012a46", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "8be0156b-d423-4a39-adf1-f54d4c9f2e69": Object { + "columnOrder": Array [ + "a0cb6400-f708-46c3-ad96-24788f12dae4", + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + ], + "columns": Object { + "a0cb6400-f708-46c3-ad96-24788f12dae4": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Src.", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + }, + "incompleteColumns": Object {}, + }, + "ca05ecdb-0fa4-49a8-9305-b23d91012a46": Object { + "columnOrder": Array [ + "f95e74e6-99dd-4b11-8faf-439b4d959df9", + "e7052671-fb9e-481f-8df3-7724c98cfc6f", + ], + "columns": Object { + "e7052671-fb9e-481f-8df3-7724c98cfc6f": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Dest.", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + "f95e74e6-99dd-4b11-8faf-439b4d959df9": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + ], + "layerId": "8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "layerType": "data", + "seriesType": "area", + "xAccessor": "a0cb6400-f708-46c3-ad96-24788f12dae4", + "yConfig": Array [ + Object { + "color": "#d36186", + "forAccessor": "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + }, + ], + }, + Object { + "accessors": Array [ + "e7052671-fb9e-481f-8df3-7724c98cfc6f", + ], + "layerId": "ca05ecdb-0fa4-49a8-9305-b23d91012a46", + "layerType": "data", + "seriesType": "area", + "xAccessor": "f95e74e6-99dd-4b11-8faf-439b4d959df9", + "yConfig": Array [ + Object { + "color": "#9170b8", + "forAccessor": "e7052671-fb9e-481f-8df3-7724c98cfc6f", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "area", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Host] Unique IPs - area", + "type": "lens", + "updated_at": "2022-02-09T17:44:03.359Z", + "version": "WzI5MTI5OSwzXQ==", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap new file mode 100644 index 0000000000000..9f702ecb06412 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap @@ -0,0 +1,265 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniqueIpsBarLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-ec84ba70-2adb-4647-8ef0-8ad91a0e6d4e", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "8be0156b-d423-4a39-adf1-f54d4c9f2e69": Object { + "columnOrder": Array [ + "f8bfa719-5c1c-4bf2-896e-c318d77fc08e", + "32f66676-f4e1-48fd-b7f8-d4de38318601", + ], + "columns": Object { + "32f66676-f4e1-48fd-b7f8-d4de38318601": Object { + "dataType": "number", + "isBucketed": false, + "label": "Unique count of source.ip", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + "f8bfa719-5c1c-4bf2-896e-c318d77fc08e": Object { + "dataType": "string", + "isBucketed": true, + "label": "Filters", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "", + }, + "label": "Src.", + }, + ], + }, + "scale": "ordinal", + }, + }, + "incompleteColumns": Object {}, + }, + "ec84ba70-2adb-4647-8ef0-8ad91a0e6d4e": Object { + "columnOrder": Array [ + "c72aad6a-fc9c-43dc-9194-e13ca3ee8aff", + "b7e59b08-96e6-40d1-84fd-e97b977d1c47", + ], + "columns": Object { + "b7e59b08-96e6-40d1-84fd-e97b977d1c47": Object { + "dataType": "number", + "isBucketed": false, + "label": "Unique count of destination.ip", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + "c72aad6a-fc9c-43dc-9194-e13ca3ee8aff": Object { + "customLabel": true, + "dataType": "string", + "isBucketed": true, + "label": "Dest.", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "", + }, + "label": "Dest.", + }, + ], + }, + "scale": "ordinal", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "32f66676-f4e1-48fd-b7f8-d4de38318601", + ], + "layerId": "8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "layerType": "data", + "seriesType": "bar_horizontal_stacked", + "xAccessor": "f8bfa719-5c1c-4bf2-896e-c318d77fc08e", + "yConfig": Array [ + Object { + "color": "#d36186", + "forAccessor": "32f66676-f4e1-48fd-b7f8-d4de38318601", + }, + ], + }, + Object { + "accessors": Array [ + "b7e59b08-96e6-40d1-84fd-e97b977d1c47", + ], + "layerId": "ec84ba70-2adb-4647-8ef0-8ad91a0e6d4e", + "layerType": "data", + "seriesType": "bar_horizontal_stacked", + "xAccessor": "c72aad6a-fc9c-43dc-9194-e13ca3ee8aff", + "yConfig": Array [ + Object { + "color": "#9170b8", + "forAccessor": "b7e59b08-96e6-40d1-84fd-e97b977d1c47", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "bar_horizontal_stacked", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Host] Unique IPs - bar", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap new file mode 100644 index 0000000000000..ebeb85e27a44f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniqueIpsDestinationMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "8be0156b-d423-4a39-adf1-f54d4c9f2e69": Object { + "columnOrder": Array [ + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + ], + "columns": Object { + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + "layerId": "8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "layerType": "data", + }, + }, + "title": "[Host] Unique IPs - destination metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap new file mode 100644 index 0000000000000..f8ec7bb8c70d7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniqueIpsSourceMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "8be0156b-d423-4a39-adf1-f54d4c9f2e69": Object { + "columnOrder": Array [ + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + ], + "columns": Object { + "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.name", + "negate": false, + "params": Object { + "query": "mockHost", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.name": "mockHost", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"query\\": {\\"bool\\": {\\"filter\\": [{\\"bool\\": {\\"should\\": [{\\"exists\\": {\\"field\\": \\"host.name\\"}}],\\"minimum_should_match\\": 1}}]}}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "host.name", + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", + "layerId": "8be0156b-d423-4a39-adf1-f54d4c9f2e69", + "layerType": "data", + }, + }, + "title": "[Host] Unique IPs - source metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts new file mode 100644 index 0000000000000..107b63716f404 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { getEventsHistogramLensAttributes } from './events'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('getEventsHistogramLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getEventsHistogramLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts new file mode 100644 index 0000000000000..5248444872942 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiHostAreaLensAttributes } from './kpi_host_area'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiHostAreaLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiHostAreaLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.ts index f4486b77390b2..64f62133e9406 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { UNIQUE_COUNT } from '../../translations'; import type { LensAttributes } from '../../types'; export const kpiHostAreaLensAttributes: LensAttributes = { @@ -32,7 +33,7 @@ export const kpiHostAreaLensAttributes: LensAttributes = { customLabel: true, dataType: 'number', isBucketed: false, - label: ' ', + label: UNIQUE_COUNT('host.name'), operationType: 'unique_count', scale: 'ratio', sourceField: 'host.name', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts new file mode 100644 index 0000000000000..06a884c15e4d6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiHostMetricLensAttributes } from './kpi_host_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiHostMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiHostMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.ts index c5fcae45df0f0..00ab0239acb40 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.ts @@ -40,7 +40,7 @@ export const kpiHostMetricLensAttributes: LensAttributes = { }, }, title: '[Host] Hosts - metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts new file mode 100644 index 0000000000000..63f50b141b5b0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniqueIpsAreaLensAttributes } from './kpi_unique_ips_area'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniqueIpsAreaLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniqueIpsAreaLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts new file mode 100644 index 0000000000000..f04f0de2b8be7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniqueIpsBarLensAttributes } from './kpi_unique_ips_bar'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniqueIpsBarLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniqueIpsBarLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts index b55fcb64ba49c..cf7dbf21913b5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts @@ -6,7 +6,7 @@ */ import type { LensAttributes } from '../../types'; -import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL } from '../../translations'; +import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL, UNIQUE_COUNT } from '../../translations'; export const kpiUniqueIpsBarLensAttributes: LensAttributes = { description: '', @@ -23,7 +23,7 @@ export const kpiUniqueIpsBarLensAttributes: LensAttributes = { '32f66676-f4e1-48fd-b7f8-d4de38318601': { dataType: 'number', isBucketed: false, - label: 'Unique count of source.ip', + label: UNIQUE_COUNT('source.ip'), operationType: 'unique_count', scale: 'ratio', sourceField: 'source.ip', @@ -50,7 +50,7 @@ export const kpiUniqueIpsBarLensAttributes: LensAttributes = { 'b7e59b08-96e6-40d1-84fd-e97b977d1c47': { dataType: 'number', isBucketed: false, - label: 'Unique count of destination.ip', + label: UNIQUE_COUNT('destination.ip'), operationType: 'unique_count', scale: 'ratio', sourceField: 'destination.ip', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts new file mode 100644 index 0000000000000..9dd67a2d5ab40 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniqueIpsDestinationMetricLensAttributes } from './kpi_unique_ips_destination_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniqueIpsDestinationMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniqueIpsDestinationMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.ts index ae18c08be800c..5c4aa31f65833 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.ts @@ -40,7 +40,7 @@ export const kpiUniqueIpsDestinationMetricLensAttributes: LensAttributes = { }, }, title: '[Host] Unique IPs - destination metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts new file mode 100644 index 0000000000000..6e69495e63a0e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniqueIpsSourceMetricLensAttributes } from './kpi_unique_ips_source_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'hosts', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniqueIpsSourceMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniqueIpsSourceMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.ts index 8a0b778975ab9..4d308b95d796d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.ts @@ -40,7 +40,7 @@ export const kpiUniqueIpsSourceMetricLensAttributes: LensAttributes = { }, }, title: '[Host] Unique IPs - source metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap new file mode 100644 index 0000000000000..6e0f9c2bbd516 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap @@ -0,0 +1,245 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`dnsTopDomainsLensAttributes should render 1`] = ` +Object { + "description": "Security Solution Network DNS", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-b1c3efc6-c886-4fba-978f-3b6bb5e7948a", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "filter-index-pattern-0", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "b1c3efc6-c886-4fba-978f-3b6bb5e7948a": Object { + "columnOrder": Array [ + "e8842815-2a45-4c74-86de-c19a391e2424", + "d1452b87-0e9e-4fc0-a725-3727a18e0b37", + "2a4d5e20-f570-48e4-b9ab-ff3068919377", + ], + "columns": Object { + "2a4d5e20-f570-48e4-b9ab-ff3068919377": Object { + "dataType": "number", + "isBucketed": false, + "label": "Unique count of dns.question.registered_domain", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "dns.question.registered_domain", + }, + "d1452b87-0e9e-4fc0-a725-3727a18e0b37": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e8842815-2a45-4c74-86de-c19a391e2424": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of dns.question.name", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "2a4d5e20-f570-48e4-b9ab-ff3068919377", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "size": 6, + }, + "scale": "ordinal", + "sourceField": "dns.question.name", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "dns.question.type", + "negate": true, + "params": Object { + "query": "PTR", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "dns.question.type": "PTR", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "2a4d5e20-f570-48e4-b9ab-ff3068919377", + ], + "layerId": "b1c3efc6-c886-4fba-978f-3b6bb5e7948a", + "layerType": "data", + "position": "top", + "seriesType": "bar", + "showGridlines": false, + "splitAccessor": "e8842815-2a45-4c74-86de-c19a391e2424", + "xAccessor": "d1452b87-0e9e-4fc0-a725-3727a18e0b37", + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Top domains by dns.question.registered_domain", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap new file mode 100644 index 0000000000000..39f16779abaf4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap @@ -0,0 +1,189 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiDnsQueriesLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "196d783b-3779-4c39-898e-6606fe633d05", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "cea37c70-8f91-43bf-b9fe-72d8c049f6a3": Object { + "columnOrder": Array [ + "0374e520-eae0-4ac1-bcfe-37565e7fc9e3", + ], + "columns": Object { + "0374e520-eae0-4ac1-bcfe-37565e7fc9e3": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "196d783b-3779-4c39-898e-6606fe633d05", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"dns.question.name\\"}},{\\"term\\":{\\"suricata.eve.dns.type\\":{\\"value\\":\\"query\\"}}},{\\"exists\\":{\\"field\\":\\"zeek.dns.query\\"}}],\\"minimum_should_match\\":1}}", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "dns.question.name", + }, + }, + Object { + "term": Object { + "suricata.eve.dns.type": Object { + "value": "query", + }, + }, + }, + Object { + "exists": Object { + "field": "zeek.dns.query", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "0374e520-eae0-4ac1-bcfe-37565e7fc9e3", + "colorMode": "None", + "layerId": "cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "layerType": "data", + }, + }, + "title": "[Network] DNS metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap new file mode 100644 index 0000000000000..03bacfac49ad7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap @@ -0,0 +1,193 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiNetworkEventsLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-eaadfec7-deaa-4aeb-a403-3b4e516416d2", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "861af17d-be25-45a3-a82d-d6e697b76e51", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "09617767-f732-410e-af53-bebcbd0bf4b9", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "eaadfec7-deaa-4aeb-a403-3b4e516416d2": Object { + "columnOrder": Array [ + "370ebd07-5ce0-4f46-a847-0e363c50d037", + ], + "columns": Object { + "370ebd07-5ce0-4f46-a847-0e363c50d037": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "security-solution-default", + "key": "source.ip", + "negate": false, + "type": "exists", + "value": "exists", + }, + "query": Object { + "exists": Object { + "field": "source.ip", + }, + }, + }, + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "security-solution-default", + "key": "destination.ip", + "negate": false, + "type": "exists", + "value": "exists", + }, + "query": Object { + "exists": Object { + "field": "destination.ip", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "370ebd07-5ce0-4f46-a847-0e363c50d037", + "layerId": "eaadfec7-deaa-4aeb-a403-3b4e516416d2", + "layerType": "data", + }, + }, + "title": "[Network] Network events", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap new file mode 100644 index 0000000000000..6e695484fdc0f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap @@ -0,0 +1,212 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiTlsHandshakesLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-1f48a633-8eee-45ae-9471-861227e9ca03", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "1f48a633-8eee-45ae-9471-861227e9ca03": Object { + "columnOrder": Array [ + "21052b6b-5504-4084-a2e2-c17f772345cf", + ], + "columns": Object { + "21052b6b-5504-4084-a2e2-c17f772345cf": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "32ee22d9-2e77-4aee-8073-87750e92c3ee", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"source.ip\\"}},{\\"exists\\":{\\"field\\":\\"destination.ip\\"}}],\\"minimum_should_match\\":1}}", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + }, + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "1e93f984-9374-4755-a198-de57751533c6", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"tls.version\\"}},{\\"exists\\":{\\"field\\":\\"suricata.eve.tls.version\\"}},{\\"exists\\":{\\"field\\":\\"zeek.ssl.version\\"}}],\\"minimum_should_match\\":1}}", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "tls.version", + }, + }, + Object { + "exists": Object { + "field": "suricata.eve.tls.version", + }, + }, + Object { + "exists": Object { + "field": "zeek.ssl.version", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "21052b6b-5504-4084-a2e2-c17f772345cf", + "layerId": "1f48a633-8eee-45ae-9471-861227e9ca03", + "layerType": "data", + }, + }, + "title": "[Network] TLS handshakes", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap new file mode 100644 index 0000000000000..1e3f1f63c40c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap @@ -0,0 +1,176 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniqueFlowIdsLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-5d46d48f-6ce8-46be-a797-17ad50642564", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "5d46d48f-6ce8-46be-a797-17ad50642564": Object { + "columnOrder": Array [ + "a27f3503-9c73-4fc1-86bb-12461dae4b70", + ], + "columns": Object { + "a27f3503-9c73-4fc1-86bb-12461dae4b70": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "network.community_id", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "c01edc8a-90ce-4d49-95f0-76954a034eb2", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"source.ip\\"}},{\\"exists\\":{\\"field\\":\\"destination.ip\\"}}],\\"minimum_should_match\\":1}}", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "a27f3503-9c73-4fc1-86bb-12461dae4b70", + "layerId": "5d46d48f-6ce8-46be-a797-17ad50642564", + "layerType": "data", + }, + }, + "title": "[Network] Unique flow IDs", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap new file mode 100644 index 0000000000000..2415dcc6c750c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap @@ -0,0 +1,261 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniquePrivateIpsAreaLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-72dc4b99-b07d-4dc9-958b-081d259e11fa", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7": Object { + "columnOrder": Array [ + "662cd5e5-82bf-4325-a703-273f84b97e09", + "5f317308-cfbb-4ee5-bfb9-07653184fabf", + ], + "columns": Object { + "5f317308-cfbb-4ee5-bfb9-07653184fabf": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "\\"source.ip\\": \\"10.0.0.0/8\\" or \\"source.ip\\": \\"192.168.0.0/16\\" or \\"source.ip\\": \\"172.16.0.0/12\\" or \\"source.ip\\": \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "Src.", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + "662cd5e5-82bf-4325-a703-273f84b97e09": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + "72dc4b99-b07d-4dc9-958b-081d259e11fa": Object { + "columnOrder": Array [ + "36444b8c-7e10-4069-8298-6c1b46912be2", + "ac1eb80c-ddde-46c4-a90c-400261926762", + ], + "columns": Object { + "36444b8c-7e10-4069-8298-6c1b46912be2": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "ac1eb80c-ddde-46c4-a90c-400261926762": Object { + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "\\"destination.ip\\": \\"10.0.0.0/8\\" or \\"destination.ip\\": \\"192.168.0.0/16\\" or \\"destination.ip\\": \\"172.16.0.0/12\\" or \\"destination.ip\\": \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "Dest.", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "5f317308-cfbb-4ee5-bfb9-07653184fabf", + ], + "layerId": "38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7", + "layerType": "data", + "seriesType": "area", + "xAccessor": "662cd5e5-82bf-4325-a703-273f84b97e09", + "yConfig": Array [ + Object { + "color": "#d36186", + "forAccessor": "5f317308-cfbb-4ee5-bfb9-07653184fabf", + }, + ], + }, + Object { + "accessors": Array [ + "ac1eb80c-ddde-46c4-a90c-400261926762", + ], + "layerId": "72dc4b99-b07d-4dc9-958b-081d259e11fa", + "layerType": "data", + "seriesType": "area", + "xAccessor": "36444b8c-7e10-4069-8298-6c1b46912be2", + "yConfig": Array [ + Object { + "color": "#9170b8", + "forAccessor": "ac1eb80c-ddde-46c4-a90c-400261926762", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "area", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Network] Unique private IPs - area chart", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap new file mode 100644 index 0000000000000..2ea658869183c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap @@ -0,0 +1,276 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniquePrivateIpsBarLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-e406bf4f-942b-41ac-b516-edb5cef06ec8", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7": Object { + "columnOrder": Array [ + "4607c585-3af3-43b9-804f-e49b27796d79", + "d27e0966-daf9-41f4-9033-230cf1e76dc9", + ], + "columns": Object { + "4607c585-3af3-43b9-804f-e49b27796d79": Object { + "dataType": "string", + "isBucketed": true, + "label": "Filters", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "", + }, + "label": "Dest.", + }, + ], + }, + "scale": "ordinal", + }, + "d27e0966-daf9-41f4-9033-230cf1e76dc9": Object { + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "\\"destination.ip\\": \\"10.0.0.0/8\\" or \\"destination.ip\\": \\"192.168.0.0/16\\" or \\"destination.ip\\": \\"172.16.0.0/12\\" or \\"destination.ip\\": \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "Unique count of destination.ip", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + }, + "incompleteColumns": Object {}, + }, + "e406bf4f-942b-41ac-b516-edb5cef06ec8": Object { + "columnOrder": Array [ + "d9c438c5-f776-4436-9d20-d62dc8c03be8", + "5acd4c9d-dc3b-4b21-9632-e4407944c36d", + ], + "columns": Object { + "5acd4c9d-dc3b-4b21-9632-e4407944c36d": Object { + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "source.ip: \\"10.0.0.0/8\\" or source.ip: \\"192.168.0.0/16\\" or source.ip: \\"172.16.0.0/12\\" or source.ip: \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "Unique count of source.ip", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + "d9c438c5-f776-4436-9d20-d62dc8c03be8": Object { + "dataType": "string", + "isBucketed": true, + "label": "Filters", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "", + }, + "label": "Src.", + }, + ], + }, + "scale": "ordinal", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "5acd4c9d-dc3b-4b21-9632-e4407944c36d", + ], + "layerId": "e406bf4f-942b-41ac-b516-edb5cef06ec8", + "layerType": "data", + "position": "top", + "seriesType": "bar_horizontal_stacked", + "showGridlines": false, + "xAccessor": "d9c438c5-f776-4436-9d20-d62dc8c03be8", + "yConfig": Array [ + Object { + "color": "#d36186", + "forAccessor": "5acd4c9d-dc3b-4b21-9632-e4407944c36d", + }, + ], + }, + Object { + "accessors": Array [ + "d27e0966-daf9-41f4-9033-230cf1e76dc9", + ], + "layerId": "38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7", + "layerType": "data", + "seriesType": "bar_horizontal_stacked", + "xAccessor": "4607c585-3af3-43b9-804f-e49b27796d79", + "yConfig": Array [ + Object { + "color": "#9170b8", + "forAccessor": "d27e0966-daf9-41f4-9033-230cf1e76dc9", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "bar_horizontal_stacked", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Network] Unique private IPs - bar chart", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap new file mode 100644 index 0000000000000..37311a980c6b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniquePrivateIpsDestinationMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "cea37c70-8f91-43bf-b9fe-72d8c049f6a3": Object { + "columnOrder": Array [ + "bd17c23e-4f83-4108-8005-2669170d064b", + ], + "columns": Object { + "bd17c23e-4f83-4108-8005-2669170d064b": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "\\"destination.ip\\": \\"10.0.0.0/8\\" or \\"destination.ip\\": \\"192.168.0.0/16\\" or \\"destination.ip\\": \\"172.16.0.0/12\\" or \\"destination.ip\\": \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "destination.ip", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "bd17c23e-4f83-4108-8005-2669170d064b", + "layerId": "cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "layerType": "data", + }, + }, + "title": "[Network] Unique private IPs - destination metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap new file mode 100644 index 0000000000000..2f7ba7d2997b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUniquePrivateIpsSourceMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "cea37c70-8f91-43bf-b9fe-72d8c049f6a3": Object { + "columnOrder": Array [ + "bd17c23e-4f83-4108-8005-2669170d064b", + ], + "columns": Object { + "bd17c23e-4f83-4108-8005-2669170d064b": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "source.ip: \\"10.0.0.0/8\\" or source.ip: \\"192.168.0.0/16\\" or source.ip: \\"172.16.0.0/12\\" or source.ip: \\"fd00::/8\\"", + }, + "isBucketed": false, + "label": "", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "source.ip", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": "", + "disabled": false, + "key": "bool", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"source.ip\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\": \\"destination.ip\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "destination.ip", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "bd17c23e-4f83-4108-8005-2669170d064b", + "layerId": "cea37c70-8f91-43bf-b9fe-72d8c049f6a3", + "layerType": "data", + }, + }, + "title": "[Network] Unique private IPs - source metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts new file mode 100644 index 0000000000000..a726a44e34e39 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { dnsTopDomainsLensAttributes } from './dns_top_domains'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('dnsTopDomainsLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: dnsTopDomainsLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts index 07a3badc3b96e..ef75bea77c3e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TOP_VALUE, UNIQUE_COUNT } from '../../translations'; import type { LensAttributes } from '../../types'; /* Exported from Kibana Saved Object */ @@ -104,7 +105,7 @@ export const dnsTopDomainsLensAttributes: LensAttributes = { }, }, '2a4d5e20-f570-48e4-b9ab-ff3068919377': { - label: 'Unique count of dns.question.registered_domain', + label: UNIQUE_COUNT('dns.question.registered_domain'), dataType: 'number', operationType: 'unique_count', scale: 'ratio', @@ -112,7 +113,7 @@ export const dnsTopDomainsLensAttributes: LensAttributes = { isBucketed: false, }, 'e8842815-2a45-4c74-86de-c19a391e2424': { - label: 'Top values of dns.question.name', + label: TOP_VALUE('dns.question.name'), dataType: 'string', operationType: 'terms', scale: 'ordinal', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts new file mode 100644 index 0000000000000..b19b5c1f2f1ba --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiDnsQueriesLensAttributes } from './kpi_dns_queries'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiDnsQueriesLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiDnsQueriesLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts index 1d6cddb7f1b61..681cd278214b1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts @@ -10,7 +10,7 @@ import type { LensAttributes } from '../../types'; export const kpiDnsQueriesLensAttributes: LensAttributes = { title: '[Network] DNS metric', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { layerId: 'cea37c70-8f91-43bf-b9fe-72d8c049f6a3', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts new file mode 100644 index 0000000000000..4ca26f222021a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiNetworkEventsLensAttributes } from './kpi_network_events'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiNetworkEventsLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiNetworkEventsLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts index 013ad35b31ecc..534ffeb2024e6 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts @@ -10,7 +10,7 @@ import type { LensAttributes } from '../../types'; export const kpiNetworkEventsLensAttributes: LensAttributes = { title: '[Network] Network events', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { layerId: 'eaadfec7-deaa-4aeb-a403-3b4e516416d2', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts new file mode 100644 index 0000000000000..f06f478ca0e2b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiTlsHandshakesLensAttributes } from './kpi_tls_handshakes'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiTlsHandshakesLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiTlsHandshakesLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts index 343c61dbd2be1..367fe6fd40f6f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts @@ -9,7 +9,7 @@ import type { LensAttributes } from '../../types'; export const kpiTlsHandshakesLensAttributes: LensAttributes = { title: '[Network] TLS handshakes', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { layerId: '1f48a633-8eee-45ae-9471-861227e9ca03', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts new file mode 100644 index 0000000000000..64b9be02a1d18 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniqueFlowIdsLensAttributes } from './kpi_unique_flow_ids'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniqueFlowIdsLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniqueFlowIdsLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts index 3646e3c0a70bd..5f31645c75eca 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts @@ -10,7 +10,7 @@ import type { LensAttributes } from '../../types'; export const kpiUniqueFlowIdsLensAttributes: LensAttributes = { title: '[Network] Unique flow IDs', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { layerId: '5d46d48f-6ce8-46be-a797-17ad50642564', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts new file mode 100644 index 0000000000000..8bb98ddaf95cf --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniquePrivateIpsAreaLensAttributes } from './kpi_unique_private_ips_area'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniquePrivateIpsAreaLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniquePrivateIpsAreaLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts index 2d3792e399372..394bc227e871c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DESTINATION_CHART_LABEL, SOURCE_CHART_LABEL } from '../../translations'; import type { LensAttributes } from '../../types'; export const kpiUniquePrivateIpsAreaLensAttributes: LensAttributes = { @@ -97,7 +98,7 @@ export const kpiUniquePrivateIpsAreaLensAttributes: LensAttributes = { }, }, '5f317308-cfbb-4ee5-bfb9-07653184fabf': { - label: 'Src.', + label: SOURCE_CHART_LABEL, dataType: 'number', operationType: 'unique_count', scale: 'ratio', @@ -131,7 +132,7 @@ export const kpiUniquePrivateIpsAreaLensAttributes: LensAttributes = { }, }, 'ac1eb80c-ddde-46c4-a90c-400261926762': { - label: 'Dest.', + label: DESTINATION_CHART_LABEL, dataType: 'number', operationType: 'unique_count', scale: 'ratio', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts new file mode 100644 index 0000000000000..894144d383b58 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniquePrivateIpsBarLensAttributes } from './kpi_unique_private_ips_bar'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniquePrivateIpsBarLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniquePrivateIpsBarLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts index bf4ad0e704081..fe4a698aedf5e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL } from '../../translations'; +import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL, UNIQUE_COUNT } from '../../translations'; import type { LensAttributes } from '../../types'; export const kpiUniquePrivateIpsBarLensAttributes: LensAttributes = { @@ -90,7 +90,7 @@ export const kpiUniquePrivateIpsBarLensAttributes: LensAttributes = { 'e406bf4f-942b-41ac-b516-edb5cef06ec8': { columns: { '5acd4c9d-dc3b-4b21-9632-e4407944c36d': { - label: SOURCE_CHART_LABEL, + label: UNIQUE_COUNT('source.ip'), dataType: 'number', isBucketed: false, operationType: 'unique_count', @@ -130,7 +130,7 @@ export const kpiUniquePrivateIpsBarLensAttributes: LensAttributes = { '38aa6532-6bf9-4c8f-b2a6-da8d32f7d0d7': { columns: { 'd27e0966-daf9-41f4-9033-230cf1e76dc9': { - label: DESTINATION_CHART_LABEL, + label: UNIQUE_COUNT('destination.ip'), dataType: 'number', isBucketed: false, operationType: 'unique_count', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts new file mode 100644 index 0000000000000..7d65e042554b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniquePrivateIpsDestinationMetricLensAttributes } from './kpi_unique_private_ips_destination_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniquePrivateIpsDestinationMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniquePrivateIpsDestinationMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.ts index a2bccef3b624b..6e3d440619e76 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.ts @@ -48,7 +48,7 @@ export const kpiUniquePrivateIpsDestinationMetricLensAttributes: LensAttributes }, }, title: '[Network] Unique private IPs - destination metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts new file mode 100644 index 0000000000000..88042d8663250 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUniquePrivateIpsSourceMetricLensAttributes } from './kpi_unique_private_ips_source_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'network', + tabName: 'events', + }, + ]), +})); + +describe('kpiUniquePrivateIpsSourceMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUniquePrivateIpsSourceMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.ts index a95745c7b96ed..3f1110d706300 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.ts @@ -47,7 +47,7 @@ export const kpiUniquePrivateIpsSourceMetricLensAttributes: LensAttributes = { }, }, title: '[Network] Unique private IPs - source metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_area.test.ts.snap new file mode 100644 index 0000000000000..11df964f2eca1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_area.test.ts.snap @@ -0,0 +1,151 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiTotalUsersAreaLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "416b6fad-1923-4f6a-a2df-b223bb287e30": Object { + "columnOrder": Array [ + "5eea817b-67b7-4268-8ecb-7688d1094721", + "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + ], + "columns": Object { + "5eea817b-67b7-4268-8ecb-7688d1094721": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "b00c65ea-32be-4163-bfc8-f795b1ef9d06": Object { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Unique count of user.name", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "user.name", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": false, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "b00c65ea-32be-4163-bfc8-f795b1ef9d06", + ], + "layerId": "416b6fad-1923-4f6a-a2df-b223bb287e30", + "layerType": "data", + "seriesType": "area", + "xAccessor": "5eea817b-67b7-4268-8ecb-7688d1094721", + }, + ], + "legend": Object { + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "area", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[User] Users - area", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_metric.test.ts.snap new file mode 100644 index 0000000000000..b53e1bd24d303 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_total_users_metric.test.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiTotalUsersMetricLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "416b6fad-1923-4f6a-a2df-b223bb287e30": Object { + "columnOrder": Array [ + "3e51b035-872c-4b44-824b-fe069c222e91", + ], + "columns": Object { + "3e51b035-872c-4b44-824b-fe069c222e91": Object { + "dataType": "number", + "isBucketed": false, + "label": " ", + "operationType": "unique_count", + "scale": "ratio", + "sourceField": "user.name", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "3e51b035-872c-4b44-824b-fe069c222e91", + "layerId": "416b6fad-1923-4f6a-a2df-b223bb287e30", + "layerType": "data", + }, + }, + "title": "[User] Users - metric", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentication_metric_failure.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentication_metric_failure.test.ts.snap new file mode 100644 index 0000000000000..37c20b7e80265 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentication_metric_failure.test.ts.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUserAuthenticationsMetricFailureLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "4590dafb-4ac7-45aa-8641-47a3ff0b817c": Object { + "columnOrder": Array [ + "0eb97c09-a351-4280-97da-944e4bd30dd7", + ], + "columns": Object { + "0eb97c09-a351-4280-97da-944e4bd30dd7": Object { + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome : \\"failure\\" ", + }, + "isBucketed": false, + "label": "", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"term\\":{\\"event.category\\":\\"authentication\\"}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "event.category": "authentication", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "0eb97c09-a351-4280-97da-944e4bd30dd7", + "layerId": "4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "layerType": "data", + }, + }, + "title": "[Host] User authentications - metric failure ", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap new file mode 100644 index 0000000000000..1954bccfaffbe --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUserAuthenticationsAreaLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "{dataViewId}", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f", + "type": "{dataViewId}", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "type": "{dataViewId}", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "31213ae3-905b-4e88-b987-0cccb1f3209f": Object { + "columnOrder": Array [ + "33a6163d-0c0a-451d-aa38-8ca6010dd5bf", + "2b27c80e-a20d-46f1-8fb2-79626ef4563c", + ], + "columns": Object { + "2b27c80e-a20d-46f1-8fb2-79626ef4563c": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome: \\"failure\\" ", + }, + "isBucketed": false, + "label": "Fail", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "33a6163d-0c0a-451d-aa38-8ca6010dd5bf": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + "4590dafb-4ac7-45aa-8641-47a3ff0b817c": Object { + "columnOrder": Array [ + "49a42fe6-ebe8-4adb-8eed-1966a5297b7e", + "0eb97c09-a351-4280-97da-944e4bd30dd7", + ], + "columns": Object { + "0eb97c09-a351-4280-97da-944e4bd30dd7": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome : \\"success\\" ", + }, + "isBucketed": false, + "label": "Succ.", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "49a42fe6-ebe8-4adb-8eed-1966a5297b7e": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"term\\":{\\"event.category\\":\\"authentication\\"}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "event.category": "authentication", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": true, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "0eb97c09-a351-4280-97da-944e4bd30dd7", + ], + "layerId": "4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "layerType": "data", + "seriesType": "area", + "xAccessor": "49a42fe6-ebe8-4adb-8eed-1966a5297b7e", + "yConfig": Array [ + Object { + "color": "#54b399", + "forAccessor": "0eb97c09-a351-4280-97da-944e4bd30dd7", + }, + ], + }, + Object { + "accessors": Array [ + "2b27c80e-a20d-46f1-8fb2-79626ef4563c", + ], + "layerId": "31213ae3-905b-4e88-b987-0cccb1f3209f", + "layerType": "data", + "seriesType": "area", + "xAccessor": "33a6163d-0c0a-451d-aa38-8ca6010dd5bf", + "yConfig": Array [ + Object { + "color": "#e7664c", + "forAccessor": "2b27c80e-a20d-46f1-8fb2-79626ef4563c", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "area", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Host] User authentications - area ", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_bar.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_bar.test.ts.snap new file mode 100644 index 0000000000000..5335dca6057a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_bar.test.ts.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUserAuthenticationsBarLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-b9acd453-f476-4467-ad38-203e37b73e55", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "31213ae3-905b-4e88-b987-0cccb1f3209f": Object { + "columnOrder": Array [ + "430e690c-9992-414f-9bce-00812d99a5e7", + "938b445a-a291-4bbc-84fe-4f47b69c20e4", + ], + "columns": Object { + "430e690c-9992-414f-9bce-00812d99a5e7": Object { + "dataType": "string", + "isBucketed": true, + "label": "Filters", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "event.outcome : \\"success\\" ", + }, + "label": "Succ.", + }, + ], + }, + "scale": "ordinal", + }, + "938b445a-a291-4bbc-84fe-4f47b69c20e4": Object { + "dataType": "number", + "isBucketed": false, + "label": "Succ.", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + "b9acd453-f476-4467-ad38-203e37b73e55": Object { + "columnOrder": Array [ + "e959c351-a3a2-4525-b244-9623f215a8fd", + "c8165fc3-7180-4f1b-8c87-bc3ea04c6df7", + ], + "columns": Object { + "c8165fc3-7180-4f1b-8c87-bc3ea04c6df7": Object { + "dataType": "number", + "isBucketed": false, + "label": "Fail", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "e959c351-a3a2-4525-b244-9623f215a8fd": Object { + "customLabel": true, + "dataType": "string", + "isBucketed": true, + "label": "Fail", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "event.outcome:\\"failure\\" ", + }, + "label": "Fail", + }, + ], + }, + "scale": "ordinal", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"term\\":{\\"event.category\\":\\"authentication\\"}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "event.category": "authentication", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "fittingFunction": "None", + "gridlinesVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "labelsOrientation": Object { + "x": 0, + "yLeft": 0, + "yRight": 0, + }, + "layers": Array [ + Object { + "accessors": Array [ + "938b445a-a291-4bbc-84fe-4f47b69c20e4", + ], + "layerId": "31213ae3-905b-4e88-b987-0cccb1f3209f", + "layerType": "data", + "seriesType": "bar_horizontal_stacked", + "xAccessor": "430e690c-9992-414f-9bce-00812d99a5e7", + "yConfig": Array [], + }, + Object { + "accessors": Array [ + "c8165fc3-7180-4f1b-8c87-bc3ea04c6df7", + ], + "layerId": "b9acd453-f476-4467-ad38-203e37b73e55", + "layerType": "data", + "seriesType": "bar_horizontal_stacked", + "xAccessor": "e959c351-a3a2-4525-b244-9623f215a8fd", + "yConfig": Array [ + Object { + "color": "#e7664c", + "forAccessor": "c8165fc3-7180-4f1b-8c87-bc3ea04c6df7", + }, + ], + }, + ], + "legend": Object { + "isVisible": false, + "position": "right", + "showSingleSeries": false, + }, + "preferredSeriesType": "bar_horizontal_stacked", + "tickLabelsVisibilitySettings": Object { + "x": true, + "yLeft": true, + "yRight": true, + }, + "valueLabels": "hide", + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "[Host] User authentications - bar ", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_metric_success.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_metric_success.test.ts.snap new file mode 100644 index 0000000000000..4cadcaf19e91e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_metric_success.test.ts.snap @@ -0,0 +1,127 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kpiUserAuthenticationsMetricSuccessLensAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern", + }, + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "indexpattern": Object { + "layers": Object { + "4590dafb-4ac7-45aa-8641-47a3ff0b817c": Object { + "columnOrder": Array [ + "0eb97c09-a351-4280-97da-944e4bd30dd7", + ], + "columns": Object { + "0eb97c09-a351-4280-97da-944e4bd30dd7": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "event.outcome : \\"success\\" ", + }, + "isBucketed": false, + "label": " ", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "query", + "negate": false, + "type": "custom", + "value": "{\\"bool\\":{\\"filter\\":[{\\"term\\":{\\"event.category\\":\\"authentication\\"}}]}}", + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "event.category": "authentication", + }, + }, + ], + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "accessor": "0eb97c09-a351-4280-97da-944e4bd30dd7", + "layerId": "4590dafb-4ac7-45aa-8641-47a3ff0b817c", + "layerType": "data", + }, + }, + "title": "[Host] User authentications - metric success ", + "visualizationType": "lnsLegacyMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts new file mode 100644 index 0000000000000..43cb5be3a9735 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiTotalUsersAreaLensAttributes } from './kpi_total_users_area'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiTotalUsersAreaLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiTotalUsersAreaLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts index d958f9304b8ab..c97748077a6be 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { UNIQUE_COUNT } from '../../translations'; import type { LensAttributes } from '../../types'; export const kpiTotalUsersAreaLensAttributes: LensAttributes = { @@ -32,7 +33,7 @@ export const kpiTotalUsersAreaLensAttributes: LensAttributes = { customLabel: true, dataType: 'number', isBucketed: false, - label: ' ', + label: UNIQUE_COUNT('user.name'), operationType: 'unique_count', scale: 'ratio', sourceField: 'user.name', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts new file mode 100644 index 0000000000000..0a5ec9891d26f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiTotalUsersMetricLensAttributes } from './kpi_total_users_metric'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiTotalUsersMetricLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiTotalUsersMetricLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts index 08e5756337dce..faa6b62e18b65 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts @@ -19,7 +19,7 @@ export const kpiTotalUsersMetricLensAttributes: LensAttributes = { '3e51b035-872c-4b44-824b-fe069c222e91': { dataType: 'number', isBucketed: false, - label: 'Unique count of user.name', + label: ' ', operationType: 'unique_count', scale: 'ratio', sourceField: 'user.name', @@ -39,7 +39,7 @@ export const kpiTotalUsersMetricLensAttributes: LensAttributes = { }, }, title: '[User] Users - metric', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', references: [ { id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts new file mode 100644 index 0000000000000..e251ccf51c5e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUserAuthenticationsMetricFailureLensAttributes } from './kpi_user_authentication_metric_failure'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiUserAuthenticationsMetricFailureLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUserAuthenticationsMetricFailureLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts index e4690f66998c8..3f421f8a1c30a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts @@ -10,7 +10,7 @@ import type { LensAttributes } from '../../types'; export const kpiUserAuthenticationsMetricFailureLensAttributes: LensAttributes = { title: '[Host] User authentications - metric failure ', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { accessor: '0eb97c09-a351-4280-97da-944e4bd30dd7', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts new file mode 100644 index 0000000000000..b51d3e474a705 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUserAuthenticationsAreaLensAttributes } from './kpi_user_authentications_area'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiUserAuthenticationsAreaLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUserAuthenticationsAreaLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts index cf9902bb2413a..d96ea21489bb2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { FAIL_CHART_LABEL, SUCCESS_CHART_LABEL } from '../../translations'; import type { LensAttributes } from '../../types'; export const kpiUserAuthenticationsAreaLensAttributes: LensAttributes = { @@ -124,7 +125,7 @@ export const kpiUserAuthenticationsAreaLensAttributes: LensAttributes = { query: 'event.outcome: "failure" ', }, isBucketed: false, - label: 'Fail', + label: FAIL_CHART_LABEL, operationType: 'count', scale: 'ratio', sourceField: '___records___', @@ -157,7 +158,7 @@ export const kpiUserAuthenticationsAreaLensAttributes: LensAttributes = { query: 'event.outcome : "success" ', }, isBucketed: false, - label: 'Succ.', + label: SUCCESS_CHART_LABEL, operationType: 'count', scale: 'ratio', sourceField: '___records___', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts new file mode 100644 index 0000000000000..41590d330cd45 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUserAuthenticationsBarLensAttributes } from './kpi_user_authentications_bar'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiUserAuthenticationsBarLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUserAuthenticationsBarLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts new file mode 100644 index 0000000000000..570e03325ec34 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../mocks'; + +import { useLensAttributes } from '../../use_lens_attributes'; + +import { kpiUserAuthenticationsMetricSuccessLensAttributes } from './kpi_user_authentications_metric_success'; + +jest.mock('../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + }), +})); + +jest.mock('../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockHost', + pageName: 'users', + tabName: 'events', + }, + ]), +})); + +describe('kpiUserAuthenticationsMetricSuccessLensAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: kpiUserAuthenticationsMetricSuccessLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts index 66f30bf2378a8..3af6f5734d458 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts @@ -10,7 +10,7 @@ import type { LensAttributes } from '../../types'; export const kpiUserAuthenticationsMetricSuccessLensAttributes: LensAttributes = { title: '[Host] User authentications - metric success ', description: '', - visualizationType: 'lnsMetric', + visualizationType: 'lnsLegacyMetric', state: { visualization: { accessor: '0eb97c09-a351-4280-97da-944e4bd30dd7', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx new file mode 100644 index 0000000000000..7f4e7e4e6d6eb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx @@ -0,0 +1,65 @@ +/* + * 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 { cloneDeep } from 'lodash/fp'; + +import { + TestProviders, + mockGlobalState, + SUB_PLUGINS_REDUCER, + kibanaObservable, + createSecuritySolutionStorageMock, +} from '../../mock'; +import type { State } from '../../store'; +import { createStore } from '../../store'; + +export const queryFromSearchBar = { + query: 'host.name: *', + language: 'kql', +}; + +export const filterFromSearchBar = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'host.id', + params: { + query: '123', + }, + }, + query: { + match_phrase: { + 'host.id': '123', + }, + }, + }, +]; + +export const mockCreateStoreWithQueryFilters = () => { + const { storage } = createSecuritySolutionStorageMock(); + + const state: State = mockGlobalState; + + const myState = cloneDeep(state); + + myState.inputs = { + ...myState.inputs, + global: { + ...myState.inputs.global, + query: queryFromSearchBar, + filters: filterFromSearchBar, + }, + }; + return createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); +}; + +export const wrapper = ({ children }: { children: React.ReactElement }) => ( + {children} +); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts index 9814a98817ef4..4dcceb29323b1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts @@ -67,9 +67,41 @@ export const SUCCESS_CHART_LABEL = i18n.translate( } ); +export const AUTHENCICATION_SUCCESS_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.visualizationActions.userAuthentications.authentication.successChartLabel', + { + defaultMessage: 'Success', + } +); + export const FAIL_CHART_LABEL = i18n.translate( 'xpack.securitySolution.visualizationActions.userAuthentications.failChartLabel', { defaultMessage: 'Fail', } ); + +export const AUTHENCICATION_FAILURE_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.visualizationActions.userAuthentications.authentication.failureChartLabel', + { + defaultMessage: 'Failure', + } +); + +export const UNIQUE_COUNT = (field: string) => + i18n.translate('xpack.securitySolution.visualizationActions.uniqueCountLabel', { + values: { field }, + + defaultMessage: 'Unique count of {field}', + }); + +export const TOP_VALUE = (field: string) => + i18n.translate('xpack.securitySolution.visualizationActions.topValueLabel', { + values: { field }, + + defaultMessage: 'Top values of {field}', + }); + +export const COUNT = i18n.translate('xpack.securitySolution.visualizationActions.countLabel', { + defaultMessage: 'Count of records', +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 7db97ab5526a4..76e2f54c62cea 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -6,21 +6,12 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import React from 'react'; -import { cloneDeep } from 'lodash/fp'; -import { - TestProviders, - mockGlobalState, - SUB_PLUGINS_REDUCER, - kibanaObservable, - createSecuritySolutionStorageMock, -} from '../../mock'; import { getExternalAlertLensAttributes } from './lens_attributes/common/external_alert'; import { useLensAttributes } from './use_lens_attributes'; import { hostNameExistsFilter, getHostDetailsPageFilter, getIndexFilters } from './utils'; -import type { State } from '../../store'; -import { createStore } from '../../store'; + +import { filterFromSearchBar, queryFromSearchBar, wrapper } from './mocks'; jest.mock('../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ @@ -40,51 +31,7 @@ jest.mock('../../utils/route/use_route_spy', () => ({ })); describe('useLensAttributes', () => { - const state: State = mockGlobalState; - const { storage } = createSecuritySolutionStorageMock(); - const queryFromSearchBar = { - query: 'host.name: *', - language: 'kql', - }; - - const filterFromSearchBar = [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'host.id', - params: { - query: '123', - }, - }, - query: { - match_phrase: { - 'host.id': '123', - }, - }, - }, - ]; - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - - beforeEach(() => { - const myState = cloneDeep(state); - myState.inputs = { - ...myState.inputs, - global: { - ...myState.inputs.global, - query: queryFromSearchBar, - filters: filterFromSearchBar, - }, - }; - store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - }); - it('should add query', () => { - const wrapper = ({ children }: { children: React.ReactElement }) => ( - {children} - ); const { result } = renderHook( () => useLensAttributes({ @@ -94,13 +41,10 @@ describe('useLensAttributes', () => { { wrapper } ); - expect(result?.current?.state.query).toEqual({ query: 'host.name: *', language: 'kql' }); + expect(result?.current?.state.query).toEqual(queryFromSearchBar); }); it('should add filters', () => { - const wrapper = ({ children }: { children: React.ReactElement }) => ( - {children} - ); const { result } = renderHook( () => useLensAttributes({ @@ -120,9 +64,6 @@ describe('useLensAttributes', () => { }); it('should add data view id to references', () => { - const wrapper = ({ children }: { children: React.ReactElement }) => ( - {children} - ); const { result } = renderHook( () => useLensAttributes({ From 4700107d791ad8402d0fd9d04e9fd33019d80655 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Thu, 29 Sep 2022 11:47:18 +0200 Subject: [PATCH 165/172] Add panel loader to entity analytics risk panels (#142122) --- .../entity_analytics/host_risk_score/index.tsx | 11 ++++++++--- .../entity_analytics/user_risk_score/index.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx index 794b33c6b33ef..49df0b1b0e456 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React, { useEffect, useMemo, useState } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { RiskScoresDeprecated } from '../../../../common/components/risk_score/risk_score_deprecated'; @@ -35,6 +35,8 @@ import { EntityAnalyticsHostRiskScoreDisable } from '../../../../common/componen import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries'; +import { Loader } from '../../../../common/components/loader'; +import { Panel } from '../../../../common/components/panel'; const TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; @@ -149,7 +151,7 @@ const EntityAnalyticsHostRiskScoresComponent = () => { return ( - + } titleSize="s" @@ -207,7 +209,10 @@ const EntityAnalyticsHostRiskScoresComponent = () => { )} - + {(isTableLoading || isKpiLoading) && ( + + )} + ); }; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx index 3b3905d4604a6..034e62bb37ad3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React, { useEffect, useMemo, useState } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { RiskScoresDeprecated } from '../../../../common/components/risk_score/risk_score_deprecated'; import { SeverityFilterGroup } from '../../../../common/components/severity/severity_filter_group'; @@ -35,6 +35,8 @@ import { EntityAnalyticsUserRiskScoreDisable } from '../../../../common/componen import { RiskScoreHeaderTitle } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../common/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries'; +import { Loader } from '../../../../common/components/loader'; +import { Panel } from '../../../../common/components/panel'; const TABLE_QUERY_ID = 'userRiskDashboardTable'; const USER_RISK_KPI_QUERY_ID = 'headerUserRiskScoreKpiQuery'; @@ -149,7 +151,7 @@ const EntityAnalyticsUserRiskScoresComponent = () => { return ( - + } titleSize="s" @@ -207,7 +209,10 @@ const EntityAnalyticsUserRiskScoresComponent = () => { )} - + {(isTableLoading || isKpiLoading) && ( + + )} + ); }; From 3f9687e3f06527a873933f29de5e261555041406 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Thu, 29 Sep 2022 12:22:58 +0200 Subject: [PATCH 166/172] [Actionable observability] Alert summary widget new design (#141236) * Implement new alert summary widget design and remove chart * Fix padding issue * Remove unused translation * Fix type and test errors * Fix types and remove extra aggs logic * Add triggers_actions_ui to storybook list * Fix small alignment issue by removing extra EuiFlexGroup * Fix UI issue for small mobile * Update spacing Co-authored-by: Kevin Delemme Co-authored-by: Kevin Delemme Co-authored-by: Xavier Mouligneau --- .../steps/storybooks/build_and_upload.ts | 1 + src/dev/storybook/aliases.ts | 1 + .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../triggers_actions_ui/.storybook/main.js | 8 + .../triggers_actions_ui/.storybook/preview.js | 10 + .../use_load_rule_alerts_aggregations.test.ts | 11 +- .../use_load_rule_alerts_aggregations.ts | 105 +---- .../mock/rule_details/alert_summary/index.ts | 372 ------------------ .../components/alert_summary_widget_error.tsx | 39 ++ .../alert_summary_widget_ui.stories.tsx | 21 + .../components/alert_summary_widget_ui.tsx | 87 ++++ .../alert_summary/components/index.ts | 9 + .../alert_summary/components/types.ts | 12 + .../components/alert_summary/helpers.tsx | 41 -- .../components/alert_summary/index.tsx | 3 +- .../rule_alerts_summary.test.tsx | 173 -------- .../alert_summary/rule_alerts_summary.tsx | 200 +--------- .../components/alert_summary/types.ts | 9 - 20 files changed, 209 insertions(+), 896 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/main.js create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/preview.js create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_error.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/index.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/helpers.tsx diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index 8575ee683d82f..dcceca7848910 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -41,6 +41,7 @@ const STORYBOOKS = [ 'presentation', 'security_solution', 'shared_ux', + 'triggers_actions_ui', 'ui_actions_enhanced', 'unified_search', ]; diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 1705c9ac2ec9c..b4224e154def5 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -43,6 +43,7 @@ export const storybookAliases = { security_solution: 'x-pack/plugins/security_solution/.storybook', shared_ux: 'packages/shared-ux/storybook/config', threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook', + triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook', ui_actions_enhanced: 'src/plugins/ui_actions_enhanced/.storybook', unified_search: 'src/plugins/unified_search/.storybook', }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index cdaa18f14c6e5..60b692e720ec0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -32529,7 +32529,6 @@ "xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "La durée dépasse le temps d'exécution attendu de la règle.", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "Actif", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "Toutes les alertes", - "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.recentAlertHistoryTitle": "Historique récent des alertes", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "Alertes", "xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "Supprimer la règle", "xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "Désactiver", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a7d49509c7756..0aa5fa39b93e0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -32503,7 +32503,6 @@ "xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "期間がルールの想定実行時間を超えています。", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "アクティブ", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "すべてのアラート", - "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.recentAlertHistoryTitle": "最近のアラート履歴", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "アラート", "xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "ルールの削除", "xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "無効にする", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 25bd585b13431..baf98e70b8dd0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -32540,7 +32540,6 @@ "xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "持续时间超出了规则的预期运行时间。", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "活动", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "所有告警", - "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.recentAlertHistoryTitle": "最近告警历史记录", "xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "告警", "xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "删除规则", "xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "禁用", diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/main.js b/x-pack/plugins/triggers_actions_ui/.storybook/main.js new file mode 100644 index 0000000000000..86b48c32f103e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/main.js @@ -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. + */ + +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/preview.js b/x-pack/plugins/triggers_actions_ui/.storybook/preview.js new file mode 100644 index 0000000000000..3200746243d47 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/preview.js @@ -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 { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common'; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.test.ts index 293c1e992ac18..03be9f6b9bd5a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.test.ts @@ -8,7 +8,7 @@ import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { renderHook } from '@testing-library/react-hooks'; import { useKibana } from '../../common/lib/kibana'; -import { mockAggsResponse, mockChartData } from '../mock/rule_details/alert_summary'; +import { mockAggsResponse } from '../mock/rule_details/alert_summary'; import { useLoadRuleAlertsAggs } from './use_load_rule_alerts_aggregations'; jest.mock('../../common/lib/kibana'); @@ -21,7 +21,7 @@ describe('useLoadRuleAlertsAggs', () => { useKibanaMock().services.http.get = jest.fn().mockResolvedValue({ index_name: ['mock_index'] }); }); - it('should return the expected chart data from the Elasticsearch Aggs. query', async () => { + it('should return the expected data from the Elasticsearch Aggs. query', async () => { const { result, waitForNextUpdate } = renderHook(() => useLoadRuleAlertsAggs({ features: ALERTS_FEATURE_ID, @@ -31,18 +31,15 @@ describe('useLoadRuleAlertsAggs', () => { expect(result.current).toEqual({ isLoadingRuleAlertsAggs: true, ruleAlertsAggs: { active: 0, recovered: 0 }, - alertsChartData: [], }); await waitForNextUpdate(); - const { ruleAlertsAggs, errorRuleAlertsAggs, alertsChartData } = result.current; + const { ruleAlertsAggs, errorRuleAlertsAggs } = result.current; expect(ruleAlertsAggs).toEqual({ active: 1, recovered: 7, }); - expect(alertsChartData).toEqual(mockChartData()); expect(errorRuleAlertsAggs).toBeFalsy(); - expect(alertsChartData.length).toEqual(33); }); it('should have the correct query body sent to Elasticsearch', async () => { @@ -55,7 +52,7 @@ describe('useLoadRuleAlertsAggs', () => { ); await waitForNextUpdate(); - const body = `{"index":"mock_index","size":0,"query":{"bool":{"must":[{"term":{"kibana.alert.rule.uuid":"${ruleId}"}},{"range":{"@timestamp":{"gte":"now-30d","lt":"now"}}},{"bool":{"should":[{"term":{"kibana.alert.status":"active"}},{"term":{"kibana.alert.status":"recovered"}}]}}]}},"aggs":{"total":{"filters":{"filters":{"totalActiveAlerts":{"term":{"kibana.alert.status":"active"}},"totalRecoveredAlerts":{"term":{"kibana.alert.status":"recovered"}}}}},"statusPerDay":{"date_histogram":{"field":"@timestamp","fixed_interval":"1d","extended_bounds":{"min":"now-30d","max":"now"}},"aggs":{"alertStatus":{"terms":{"field":"kibana.alert.status"}}}}}}`; + const body = `{"index":"mock_index","size":0,"query":{"bool":{"must":[{"term":{"kibana.alert.rule.uuid":"${ruleId}"}},{"range":{"@timestamp":{"gte":"now-30d","lt":"now"}}},{"bool":{"should":[{"term":{"kibana.alert.status":"active"}},{"term":{"kibana.alert.status":"recovered"}}]}}]}},"aggs":{"total":{"filters":{"filters":{"totalActiveAlerts":{"term":{"kibana.alert.status":"active"}},"totalRecoveredAlerts":{"term":{"kibana.alert.status":"recovered"}}}}}}}`; expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( '/internal/rac/alerts/find', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.ts index c938b0b2cc13f..3df1722abc006 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_alerts_aggregations.ts @@ -10,7 +10,6 @@ import { AsApiContract } from '@kbn/actions-plugin/common'; import { HttpSetup } from '@kbn/core/public'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants'; import { useKibana } from '../../common/lib/kibana'; -import { AlertChartData } from '../sections/rule_details/components/alert_summary'; interface UseLoadRuleAlertsAggs { features: string; @@ -29,7 +28,6 @@ interface LoadRuleAlertsAggs { recovered: number; }; errorRuleAlertsAggs?: string; - alertsChartData: AlertChartData[]; } interface IndexName { index: string; @@ -40,7 +38,6 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg const [ruleAlertsAggs, setRuleAlertsAggs] = useState({ isLoadingRuleAlertsAggs: true, ruleAlertsAggs: { active: 0, recovered: 0 }, - alertsChartData: [], }); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -54,7 +51,7 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg http, features, }); - const { active, recovered, error, alertsChartData } = await fetchRuleAlertsAggByTimeRange({ + const { active, recovered, error } = await fetchRuleAlertsAggByTimeRange({ http, index, ruleId, @@ -68,7 +65,6 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg active, recovered, }, - alertsChartData, isLoadingRuleAlertsAggs: false, })); } @@ -79,7 +75,6 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg ...oldState, isLoadingRuleAlertsAggs: false, errorRuleAlertsAggs: error, - alertsChartData: [], })); } } @@ -92,7 +87,7 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg return ruleAlertsAggs; } -export async function fetchIndexNameAPI({ +async function fetchIndexNameAPI({ http, features, }: { @@ -107,14 +102,7 @@ export async function fetchIndexNameAPI({ }; } -interface RuleAlertsAggs { - active: number; - recovered: number; - error?: string; - alertsChartData: AlertChartData[]; -} - -export async function fetchRuleAlertsAggByTimeRange({ +async function fetchRuleAlertsAggByTimeRange({ http, index, ruleId, @@ -183,109 +171,22 @@ export async function fetchRuleAlertsAggByTimeRange({ }, }, }, - statusPerDay: { - date_histogram: { - field: '@timestamp', - fixed_interval: '1d', - extended_bounds: { - min: 'now-30d', - max: 'now', - }, - }, - aggs: { - alertStatus: { - terms: { - field: 'kibana.alert.status', - }, - }, - }, - }, }, }), }); const active = res?.aggregations?.total.buckets.totalActiveAlerts?.doc_count ?? 0; const recovered = res?.aggregations?.total.buckets.totalRecoveredAlerts?.doc_count ?? 0; - let maxTotalAlertPerDay = 0; - res?.aggregations?.statusPerDay.buckets.forEach( - (dayAlerts: { - key: number; - doc_count: number; - alertStatus: { - buckets: Array<{ - key: 'active' | 'recovered'; - doc_count: number; - }>; - }; - }) => { - if (dayAlerts.doc_count > maxTotalAlertPerDay) { - maxTotalAlertPerDay = dayAlerts.doc_count; - } - } - ); - const alertsChartData = [ - ...res?.aggregations?.statusPerDay.buckets.reduce( - ( - acc: AlertChartData[], - dayAlerts: { - key: number; - doc_count: number; - alertStatus: { - buckets: Array<{ - key: 'active' | 'recovered'; - doc_count: number; - }>; - }; - } - ) => { - // We are adding this to each day to construct the 30 days bars (background bar) when there is no data for a given day or to show the delta today alerts/total alerts. - const totalDayAlerts = { - date: dayAlerts.key, - count: maxTotalAlertPerDay === 0 ? 1 : maxTotalAlertPerDay, - status: 'total', - }; - - if (dayAlerts.doc_count > 0) { - const localAlertChartData = acc; - // If there are alerts in this day, we construct the chart data - dayAlerts.alertStatus.buckets.forEach((alert) => { - localAlertChartData.push({ - date: dayAlerts.key, - count: alert.doc_count, - status: alert.key, - }); - }); - const deltaAlertsCount = maxTotalAlertPerDay - dayAlerts.doc_count; - if (deltaAlertsCount > 0) { - localAlertChartData.push({ - date: dayAlerts.key, - count: deltaAlertsCount, - status: 'total', - }); - } - return localAlertChartData; - } - return [...acc, totalDayAlerts]; - }, - [] - ), - ]; return { active, recovered, - alertsChartData: [ - ...alertsChartData.filter((acd) => acd.status === 'recovered'), - ...alertsChartData.filter((acd) => acd.status === 'active'), - ...alertsChartData.filter((acd) => acd.status === 'total'), - ], }; } catch (error) { return { error, active: 0, recovered: 0, - alertsChartData: [], } as RuleAlertsAggs; } } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/mock/rule_details/alert_summary/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/mock/rule_details/alert_summary/index.ts index b0e5416bbf63d..2d5af068ae55d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/mock/rule_details/alert_summary/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/mock/rule_details/alert_summary/index.ts @@ -6,7 +6,6 @@ */ import { Rule } from '../../../../types'; -import { AlertChartData } from '../../../sections/rule_details/components/alert_summary'; export const mockRule = (): Rule => { return { @@ -63,383 +62,12 @@ export const mockRule = (): Rule => { }; }; -export function mockChartData(): AlertChartData[] { - return [ - { - date: 1660608000000, - count: 6, - status: 'recovered', - }, - { - date: 1660694400000, - count: 1, - status: 'recovered', - }, - { - date: 1660694400000, - count: 1, - status: 'active', - }, - { - date: 1658102400000, - count: 6, - status: 'total', - }, - { - date: 1658188800000, - count: 6, - status: 'total', - }, - { - date: 1658275200000, - count: 6, - status: 'total', - }, - { - date: 1658361600000, - count: 6, - status: 'total', - }, - { - date: 1658448000000, - count: 6, - status: 'total', - }, - { - date: 1658534400000, - count: 6, - status: 'total', - }, - { - date: 1658620800000, - count: 6, - status: 'total', - }, - { - date: 1658707200000, - count: 6, - status: 'total', - }, - { - date: 1658793600000, - count: 6, - status: 'total', - }, - { - date: 1658880000000, - count: 6, - status: 'total', - }, - { - date: 1658966400000, - count: 6, - status: 'total', - }, - { - date: 1659052800000, - count: 6, - status: 'total', - }, - { - date: 1659139200000, - count: 6, - status: 'total', - }, - { - date: 1659225600000, - count: 6, - status: 'total', - }, - { - date: 1659312000000, - count: 6, - status: 'total', - }, - { - date: 1659398400000, - count: 6, - status: 'total', - }, - { - date: 1659484800000, - count: 6, - status: 'total', - }, - { - date: 1659571200000, - count: 6, - status: 'total', - }, - { - date: 1659657600000, - count: 6, - status: 'total', - }, - { - date: 1659744000000, - count: 6, - status: 'total', - }, - { - date: 1659830400000, - count: 6, - status: 'total', - }, - { - date: 1659916800000, - count: 6, - status: 'total', - }, - { - date: 1660003200000, - count: 6, - status: 'total', - }, - { - date: 1660089600000, - count: 6, - status: 'total', - }, - { - date: 1660176000000, - count: 6, - status: 'total', - }, - { - date: 1660262400000, - count: 6, - status: 'total', - }, - { - date: 1660348800000, - count: 6, - status: 'total', - }, - { - date: 1660435200000, - count: 6, - status: 'total', - }, - { - date: 1660521600000, - count: 6, - status: 'total', - }, - { - date: 1660694400000, - count: 4, - status: 'total', - }, - ]; -} - export const mockAggsResponse = () => { return { aggregations: { total: { buckets: { totalActiveAlerts: { doc_count: 1 }, totalRecoveredAlerts: { doc_count: 7 } }, }, - statusPerDay: { - buckets: [ - { - key_as_string: '2022-07-18T00:00:00.000Z', - key: 1658102400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-19T00:00:00.000Z', - key: 1658188800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-20T00:00:00.000Z', - key: 1658275200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-21T00:00:00.000Z', - key: 1658361600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-22T00:00:00.000Z', - key: 1658448000000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-23T00:00:00.000Z', - key: 1658534400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-24T00:00:00.000Z', - key: 1658620800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-25T00:00:00.000Z', - key: 1658707200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-26T00:00:00.000Z', - key: 1658793600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-27T00:00:00.000Z', - key: 1658880000000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-28T00:00:00.000Z', - key: 1658966400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-29T00:00:00.000Z', - key: 1659052800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-30T00:00:00.000Z', - key: 1659139200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-07-31T00:00:00.000Z', - key: 1659225600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-01T00:00:00.000Z', - key: 1659312000000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-02T00:00:00.000Z', - key: 1659398400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-03T00:00:00.000Z', - key: 1659484800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-04T00:00:00.000Z', - key: 1659571200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-05T00:00:00.000Z', - key: 1659657600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-06T00:00:00.000Z', - key: 1659744000000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-07T00:00:00.000Z', - key: 1659830400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-08T00:00:00.000Z', - key: 1659916800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-09T00:00:00.000Z', - key: 1660003200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-10T00:00:00.000Z', - key: 1660089600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-11T00:00:00.000Z', - key: 1660176000000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-12T00:00:00.000Z', - key: 1660262400000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-13T00:00:00.000Z', - key: 1660348800000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-14T00:00:00.000Z', - key: 1660435200000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-15T00:00:00.000Z', - key: 1660521600000, - doc_count: 0, - alertStatus: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, - }, - { - key_as_string: '2022-08-16T00:00:00.000Z', - key: 1660608000000, - doc_count: 6, - alertStatus: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'recovered', doc_count: 6 }], - }, - }, - { - key_as_string: '2022-08-17T00:00:00.000Z', - key: 1660694400000, - doc_count: 2, - alertStatus: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 'active', doc_count: 1 }, - { key: 'recovered', doc_count: 1 }, - ], - }, - }, - ], - }, }, }; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_error.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_error.tsx new file mode 100644 index 0000000000000..1ee04fe2f69d1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_error.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const AlertSummaryWidgetError = () => { + return ( + + + + } + body={ +

    + { + + } +

    + } + /> + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx new file mode 100644 index 0000000000000..ab064893ae6fe --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AlertsSummaryWidgetUI as Component } from './alert_summary_widget_ui'; + +export default { + component: Component, + title: 'app/AlertsSummaryWidgetUI', +}; + +export const Overview = { + args: { + active: 15, + recovered: 53, + timeRange: 'Last 30 days', + }, +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx new file mode 100644 index 0000000000000..e4003e803032d --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx @@ -0,0 +1,87 @@ +/* + * 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 { euiLightVars } from '@kbn/ui-theme'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { AlertsSummaryWidgetUIProps } from './types'; + +export const AlertsSummaryWidgetUI = ({ + active, + recovered, + timeRange, +}: AlertsSummaryWidgetUIProps) => { + return ( + + + + + + +
    + +
    +
    + {!!timeRange && ( + <> + + + {timeRange} + + + )} +
    + + + + + +

    {active + recovered}

    +
    + + + +
    + + + +

    {recovered}

    +
    +
    + + + +
    + + +

    {active}

    +
    + + + +
    +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/index.ts new file mode 100644 index 0000000000000..e1f4ef4d231e7 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/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 { AlertsSummaryWidgetUI } from './alert_summary_widget_ui'; +export { AlertSummaryWidgetError } from './alert_summary_widget_error'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts new file mode 100644 index 0000000000000..81437071fcea2 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.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 interface AlertsSummaryWidgetUIProps { + active: number; + recovered: number; + timeRange: JSX.Element | string; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/helpers.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/helpers.tsx deleted file mode 100644 index 167c98a678f52..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/helpers.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { LIGHT_THEME, XYChartSeriesIdentifier } from '@elastic/charts'; -import { AlertChartData } from './types'; - -export const formatChartAlertData = ( - data: AlertChartData[] -): Array<{ x: number; y: number; g: string }> => - data.map((alert) => ({ - x: alert.date, - y: alert.count, - g: alert.status, - })); - -export const getColorSeries = ({ seriesKeys }: XYChartSeriesIdentifier) => { - switch (seriesKeys[0]) { - case 'active': - return LIGHT_THEME.colors.vizColors[2]; - case 'recovered': - return LIGHT_THEME.colors.vizColors[1]; - case 'total': - return '#f5f7fa'; - default: - return null; - } -}; - -/** - * This function may be passed to `Array.find()` to locate the `P1DT` - * configuration (sub) setting, a string array that contains two entries - * like the following example: `['P1DT', 'YYYY-MM-DD']`. - */ -export const isP1DTFormatterSetting = (formatNameFormatterPair?: string[]) => - Array.isArray(formatNameFormatterPair) && - formatNameFormatterPair[0] === 'P1DT' && - formatNameFormatterPair.length === 2; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/index.tsx index 80f221f436c49..ede15d4bac294 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/index.tsx @@ -6,5 +6,4 @@ */ export { RuleAlertsSummary } from './rule_alerts_summary'; -export type { RuleAlertsSummaryProps, AlertChartData, AlertsChartProps } from './types'; -export { formatChartAlertData, getColorSeries } from './helpers'; +export type { RuleAlertsSummaryProps } from './types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx index 39264020f30a3..019d4f2ba549b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx @@ -12,7 +12,6 @@ import { mount, ReactWrapper } from 'enzyme'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { mockRule } from '../../../../mock/rule_details/alert_summary'; -import { AlertChartData } from './types'; jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({ useUiSetting: jest.fn().mockImplementation(() => true), @@ -25,7 +24,6 @@ jest.mock('../../../../hooks/use_load_rule_types', () => ({ jest.mock('../../../../hooks/use_load_rule_alerts_aggregations', () => ({ useLoadRuleAlertsAggs: jest.fn().mockReturnValue({ ruleAlertsAggs: { active: 1, recovered: 7 }, - alertsChartData: mockChartData(), }), })); @@ -80,174 +78,3 @@ describe('Rule Alert Summary', () => { expect(wrapper.find('[data-test-subj="totalAlertsCount"]').text()).toBe('8'); }); }); - -// This function should stay in the same file as the test otherwise the test will fail. -function mockChartData(): AlertChartData[] { - return [ - { - date: 1660608000000, - count: 6, - status: 'recovered', - }, - { - date: 1660694400000, - count: 1, - status: 'recovered', - }, - { - date: 1660694400000, - count: 1, - status: 'active', - }, - { - date: 1658102400000, - count: 6, - status: 'total', - }, - { - date: 1658188800000, - count: 6, - status: 'total', - }, - { - date: 1658275200000, - count: 6, - status: 'total', - }, - { - date: 1658361600000, - count: 6, - status: 'total', - }, - { - date: 1658448000000, - count: 6, - status: 'total', - }, - { - date: 1658534400000, - count: 6, - status: 'total', - }, - { - date: 1658620800000, - count: 6, - status: 'total', - }, - { - date: 1658707200000, - count: 6, - status: 'total', - }, - { - date: 1658793600000, - count: 6, - status: 'total', - }, - { - date: 1658880000000, - count: 6, - status: 'total', - }, - { - date: 1658966400000, - count: 6, - status: 'total', - }, - { - date: 1659052800000, - count: 6, - status: 'total', - }, - { - date: 1659139200000, - count: 6, - status: 'total', - }, - { - date: 1659225600000, - count: 6, - status: 'total', - }, - { - date: 1659312000000, - count: 6, - status: 'total', - }, - { - date: 1659398400000, - count: 6, - status: 'total', - }, - { - date: 1659484800000, - count: 6, - status: 'total', - }, - { - date: 1659571200000, - count: 6, - status: 'total', - }, - { - date: 1659657600000, - count: 6, - status: 'total', - }, - { - date: 1659744000000, - count: 6, - status: 'total', - }, - { - date: 1659830400000, - count: 6, - status: 'total', - }, - { - date: 1659916800000, - count: 6, - status: 'total', - }, - { - date: 1660003200000, - count: 6, - status: 'total', - }, - { - date: 1660089600000, - count: 6, - status: 'total', - }, - { - date: 1660176000000, - count: 6, - status: 'total', - }, - { - date: 1660262400000, - count: 6, - status: 'total', - }, - { - date: 1660348800000, - count: 6, - status: 'total', - }, - { - date: 1660435200000, - count: 6, - status: 'total', - }, - { - date: 1660521600000, - count: 6, - status: 'total', - }, - { - date: 1660694400000, - count: 4, - status: 'total', - }, - ]; -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx index 708b12ceb7b4a..da43783568658 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx @@ -5,65 +5,17 @@ * 2.0. */ -import { - BarSeries, - Chart, - FilterPredicate, - LIGHT_THEME, - ScaleType, - Settings, - TooltipType, -} from '@elastic/charts'; -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useEffect, useMemo, useState } from 'react'; -import { - EUI_CHARTS_THEME_DARK, - EUI_CHARTS_THEME_LIGHT, - EUI_SPARKLINE_THEME_PARTIAL, -} from '@elastic/eui/dist/eui_charts_theme'; -import { useUiSetting } from '@kbn/kibana-react-plugin/public'; -import moment from 'moment'; +import React, { useEffect, useState } from 'react'; import { useLoadRuleAlertsAggs } from '../../../../hooks/use_load_rule_alerts_aggregations'; import { useLoadRuleTypes } from '../../../../hooks/use_load_rule_types'; -import { formatChartAlertData, getColorSeries } from '.'; import { RuleAlertsSummaryProps } from '.'; -import { isP1DTFormatterSetting } from './helpers'; +import { AlertSummaryWidgetError, AlertsSummaryWidgetUI } from './components'; -const Y_ACCESSORS = ['y']; -const X_ACCESSORS = ['x']; -const G_ACCESSORS = ['g']; -const FALLBACK_DATE_FORMAT_SCALED_P1DT = 'YYYY-MM-DD'; export const RuleAlertsSummary = ({ rule, filteredRuleTypes }: RuleAlertsSummaryProps) => { const [features, setFeatures] = useState(''); - const isDarkMode = useUiSetting('theme:darkMode'); - - const scaledDateFormatPreference = useUiSetting('dateFormat:scaled'); - const maybeP1DTFormatter = Array.isArray(scaledDateFormatPreference) - ? scaledDateFormatPreference.find(isP1DTFormatterSetting) - : null; - const p1dtFormat = - Array.isArray(maybeP1DTFormatter) && maybeP1DTFormatter.length === 2 - ? maybeP1DTFormatter[1] - : FALLBACK_DATE_FORMAT_SCALED_P1DT; - - const theme = useMemo( - () => [ - EUI_SPARKLINE_THEME_PARTIAL, - isDarkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme, - ], - [isDarkMode] - ); const { ruleTypes } = useLoadRuleTypes({ filteredRuleTypes, }); @@ -71,21 +23,10 @@ export const RuleAlertsSummary = ({ rule, filteredRuleTypes }: RuleAlertsSummary ruleAlertsAggs: { active, recovered }, isLoadingRuleAlertsAggs, errorRuleAlertsAggs, - alertsChartData, } = useLoadRuleAlertsAggs({ ruleId: rule.id, features, }); - const chartData = useMemo(() => formatChartAlertData(alertsChartData), [alertsChartData]); - const tooltipSettings = useMemo( - () => ({ - type: TooltipType.VerticalCursor, - headerFormatter: ({ value }: { value: number }) => { - return <>{moment(value).format(p1dtFormat)}; - }, - }), - [p1dtFormat] - ); useEffect(() => { const matchedRuleType = ruleTypes.find((type) => type.id === rule.ruleTypeId); @@ -95,133 +36,18 @@ export const RuleAlertsSummary = ({ rule, filteredRuleTypes }: RuleAlertsSummary }, [rule, ruleTypes]); if (isLoadingRuleAlertsAggs) return ; - if (errorRuleAlertsAggs) - return ( - - - - } - body={ -

    - { - - } -

    - } - /> - ); - const isVisibleFunction: FilterPredicate = (series) => series.splitAccessors.get('g') !== 'total'; + if (errorRuleAlertsAggs) return ; return ( - - - - - - -
    - -
    -
    - - - - -
    - - - - - - - - - -

    {active + recovered}

    -
    -
    -
    - - - - - - -

    {active}

    -
    -
    -
    - - - - - - - -

    {recovered}

    -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    - -
    -
    -
    -
    - - - - - -
    + } + /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/types.ts index ae4d8696572b0..7ae05d1eaa10e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/types.ts @@ -11,12 +11,3 @@ export interface RuleAlertsSummaryProps { rule: Rule; filteredRuleTypes: string[]; } -export interface AlertChartData { - status: 'active' | 'recovered' | 'total'; - count: number; - date: number; -} - -export interface AlertsChartProps { - data: AlertChartData[]; -} From 4bab191faf62b88fde6604353a3d32f0b5aaf3b4 Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:23:52 +0100 Subject: [PATCH 167/172] [APM] Create infraMetricsClient (#141735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create infraMetricsClient * remove infraclient from setup request and create its own * Remove the setupInfraMetrics, use createInfraMetricsClient * fix types * typing createInfraMetrics client * Fix types and simplify `get_host_names` * fix get_service_overview_container_metadata types * pass resources as a param instead Co-authored-by: Søren Louv-Jansen --- .../create_infra_metrics_client.ts | 41 +++++++ .../routes/infrastructure/get_host_names.ts | 113 +++++------------- .../apm/server/routes/infrastructure/route.ts | 11 +- ...get_service_instance_container_metadata.ts | 17 +-- ...get_service_overview_container_metadata.ts | 38 +----- .../apm/server/routes/services/route.ts | 35 ++---- 6 files changed, 93 insertions(+), 162 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts new file mode 100644 index 0000000000000..4cd9df6bf2137 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import { APMRouteHandlerResources } from '../../../../routes/typings'; +import { getInfraMetricIndices } from '../../get_infra_metric_indices'; + +type InfraMetricsSearchParams = Omit; + +export type InfraMetricsClient = ReturnType; + +export function createInfraMetricsClient(resources: APMRouteHandlerResources) { + return { + async search( + opts: TParams + ): Promise> { + const { + savedObjects: { client: savedObjectsClient }, + elasticsearch: { client: esClient }, + } = await resources.context.core; + + const indexName = await getInfraMetricIndices({ + infraPlugin: resources.plugins.infra, + savedObjectsClient, + }); + + const searchParams = { + index: [indexName], + ...opts, + }; + + return esClient.asCurrentUser.search( + searchParams + ) as Promise; + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts index 4890f8ab909eb..8f47c2d1664bf 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts @@ -5,106 +5,55 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; import { rangeQuery } from '@kbn/observability-plugin/server'; -import { InfraPluginStart, InfraPluginSetup } from '@kbn/infra-plugin/server'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { CONTAINER_ID, HOST_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { ApmPluginRequestHandlerContext } from '../typings'; -import { getInfraMetricIndices } from '../../lib/helpers/get_infra_metric_indices'; +import { InfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; -interface Aggs extends estypes.AggregationsMultiBucketAggregateBase { - buckets: Array<{ - key: string; - key_as_string?: string; - }>; -} - -interface InfraPlugin { - setup: InfraPluginSetup; - start: () => Promise; -} - -const getHostNames = async ({ - esClient, +export async function getContainerHostNames({ containerIds, - index, + infraMetricsClient, start, end, }: { - esClient: ElasticsearchClient; containerIds: string[]; - index: string; + infraMetricsClient: InfraMetricsClient; start: number; end: number; -}) => { - const response = await esClient.search({ - index: [index], - body: { - size: 0, - query: { - bool: { - filter: [ - { - terms: { - [CONTAINER_ID]: containerIds, - }, +}): Promise { + if (!containerIds.length) { + return []; + } + + const response = await infraMetricsClient.search({ + size: 0, + query: { + bool: { + filter: [ + { + terms: { + [CONTAINER_ID]: containerIds, }, - ...rangeQuery(start, end), - ], - }, - }, - aggs: { - hostNames: { - terms: { - field: HOST_NAME, - size: 500, }, + ...rangeQuery(start, end), + ], + }, + }, + aggs: { + hostNames: { + terms: { + field: HOST_NAME, + size: 500, }, }, }, }); - return { - hostNames: - response.aggregations?.hostNames?.buckets.map( - (bucket) => bucket.key as string - ) ?? [], - }; -}; + const hostNames = response.aggregations?.hostNames?.buckets.map( + (bucket) => bucket.key as string + ); -export const getContainerHostNames = async ({ - containerIds, - context, - infra, - start, - end, -}: { - containerIds: string[]; - context: ApmPluginRequestHandlerContext; - infra: InfraPlugin; - start: number; - end: number; -}): Promise => { - if (containerIds.length) { - const esClient = (await context.core).elasticsearch.client.asCurrentUser; - const savedObjectsClient = (await context.core).savedObjects.client; - const metricIndices = await getInfraMetricIndices({ - infraPlugin: infra, - savedObjectsClient, - }); - - const containerHostNames = await getHostNames({ - esClient, - containerIds, - index: metricIndices, - start, - end, - }); - return containerHostNames.hostNames; - } - return []; -}; + return hostNames ?? []; +} diff --git a/x-pack/plugins/apm/server/routes/infrastructure/route.ts b/x-pack/plugins/apm/server/routes/infrastructure/route.ts index 756b29d5d4571..678f380bc4fd8 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/route.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/route.ts @@ -10,6 +10,7 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getInfrastructureData } from './get_infrastructure_data'; import { getContainerHostNames } from './get_host_names'; +import { createInfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; const infrastructureRoute = createApmServerRoute({ endpoint: @@ -29,12 +30,9 @@ const infrastructureRoute = createApmServerRoute({ podNames: string[]; }> => { const setup = await setupRequest(resources); + const infraMetricsClient = createInfraMetricsClient(resources); - const { - context, - params, - plugins: { infra }, - } = resources; + const { params } = resources; const { path: { serviceName }, @@ -54,8 +52,7 @@ const infrastructureRoute = createApmServerRoute({ // due some limitations on the data we get from apm-metrics indices, if we have a service running in a container we want to query, to get the host.name, filtering by container.id const containerHostNames = await getContainerHostNames({ containerIds, - context, - infra, + infraMetricsClient, start, end, }); diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts index 729c2f1f1b1eb..ebce4f9338714 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; import { rangeQuery } from '@kbn/observability-plugin/server'; import { - CONTAINER, CONTAINER_ID, CONTAINER_IMAGE, KUBERNETES, @@ -21,6 +19,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { Kubernetes } from '../../../typings/es_schemas/raw/fields/kubernetes'; import { maybe } from '../../../common/utils/maybe'; +import { InfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; type ServiceInstanceContainerMetadataDetails = | { @@ -29,22 +28,16 @@ type ServiceInstanceContainerMetadataDetails = | undefined; export const getServiceInstanceContainerMetadata = async ({ - esClient, - indexName, + infraMetricsClient, containerId, start, end, }: { - esClient: ElasticsearchClient; - indexName?: string; + infraMetricsClient: InfraMetricsClient; containerId: string; start: number; end: number; }): Promise => { - if (!indexName) { - return undefined; - } - const should = [ { exists: { field: KUBERNETES } }, { exists: { field: CONTAINER_IMAGE } }, @@ -56,9 +49,7 @@ export const getServiceInstanceContainerMetadata = async ({ { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, ]; - const response = await esClient.search({ - index: [indexName], - _source: [KUBERNETES, CONTAINER], + const response = await infraMetricsClient.search({ size: 1, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts index 5b267e4faa3fc..c1dcfe97e208b 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; import { rangeQuery } from '@kbn/observability-plugin/server'; import { - CONTAINER, CONTAINER_ID, CONTAINER_IMAGE, KUBERNETES, @@ -19,43 +17,19 @@ import { KUBERNETES_REPLICASET_NAME, KUBERNETES_DEPLOYMENT_NAME, } from '../../../common/elasticsearch_fieldnames'; - -type ServiceOverviewContainerMetadataDetails = - | { - kubernetes: { - deployments?: string[]; - replicasets?: string[]; - namespaces?: string[]; - containerImages?: string[]; - }; - } - | undefined; - -interface ResponseAggregations { - [key: string]: { - buckets: Array<{ - key: string; - }>; - }; -} +import { InfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; export const getServiceOverviewContainerMetadata = async ({ - esClient, - indexName, + infraMetricsClient, containerIds, start, end, }: { - esClient: ElasticsearchClient; - indexName?: string; + infraMetricsClient: InfraMetricsClient; containerIds: string[]; start: number; end: number; -}): Promise => { - if (!indexName) { - return undefined; - } - +}) => { const should = [ { exists: { field: KUBERNETES } }, { exists: { field: CONTAINER_IMAGE } }, @@ -67,9 +41,7 @@ export const getServiceOverviewContainerMetadata = async ({ { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, ]; - const response = await esClient.search({ - index: [indexName], - _source: [KUBERNETES, CONTAINER], + const response = await infraMetricsClient.search({ size: 0, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 6b73d72367d0f..79ff09101aeca 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -55,7 +55,8 @@ import { ServiceHealthStatus } from '../../../common/service_health_status'; import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; -import { getInfraMetricIndices } from '../../lib/helpers/get_infra_metric_indices'; +import { createInfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; + const servicesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services', params: t.type({ @@ -274,7 +275,8 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ import('./get_service_metadata_details').ServiceMetadataDetails > => { const setup = await setupRequest(resources); - const { params, context, plugins } = resources; + const infraMetricsClient = createInfraMetricsClient(resources); + const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; @@ -295,19 +297,8 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ }); if (serviceMetadataDetails?.container?.ids) { - const { - savedObjects: { client: savedObjectsClient }, - elasticsearch: { client: esClient }, - } = await context.core; - - const indexName = await getInfraMetricIndices({ - infraPlugin: plugins.infra, - savedObjectsClient, - }); - const containerMetadata = await getServiceOverviewContainerMetadata({ - esClient: esClient.asCurrentUser, - indexName, + infraMetricsClient, containerIds: serviceMetadataDetails.container.ids, start, end, @@ -911,7 +902,8 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ | undefined; }> => { const setup = await setupRequest(resources); - const { params, context, plugins } = resources; + const infraMetricsClient = createInfraMetricsClient(resources); + const { params } = resources; const { serviceName, serviceNodeName } = params.path; const { start, end } = params.query; @@ -925,19 +917,8 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ }); if (serviceInstanceMetadataDetails?.container?.id) { - const { - savedObjects: { client: savedObjectsClient }, - elasticsearch: { client: esClient }, - } = await context.core; - - const indexName = await getInfraMetricIndices({ - infraPlugin: plugins.infra, - savedObjectsClient, - }); - const containerMetadata = await getServiceInstanceContainerMetadata({ - esClient: esClient.asCurrentUser, - indexName, + infraMetricsClient, containerId: serviceInstanceMetadataDetails.container.id, start, end, From 94fe1e63530d73807fca991898c8caf42e5d4dca Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Thu, 29 Sep 2022 13:39:14 +0300 Subject: [PATCH 168/172] [Visualizations] Navigate to lens agg based vis library tests. (#141353) * Added tests for convert_to_lens lib at vis_types. Co-authored-by: Uladzislau Lasitsa --- .../convert_to_lens/lib/buckets/index.test.ts | 267 +++++++++ .../lib/convert/column.test.ts | 105 ++++ .../lib/convert/date_histogram.test.ts | 90 +++ .../lib/convert/filters.test.ts | 54 ++ .../lib/convert/formula.test.ts | 54 ++ .../lib/convert/last_value.test.ts | 120 ++++ .../lib/convert/metric.test.ts | 92 ++++ .../lib/convert/parent_pipeline.test.ts | 438 +++++++++++++++ .../lib/convert/parent_pipeline.ts | 3 +- .../lib/convert/percentile.test.ts | 142 +++++ .../lib/convert/percentile_rank.test.ts | 146 +++++ .../convert_to_lens/lib/convert/range.test.ts | 74 +++ .../lib/convert/sibling_pipeline.test.ts | 79 +++ .../lib/convert/std_deviation.test.ts | 115 ++++ .../convert_to_lens/lib/convert/terms.test.ts | 241 ++++++++ .../lib/metrics/formula.test.ts | 474 ++++++++++++++++ .../lib/metrics/metrics.test.ts | 368 +++++++++++++ .../lib/metrics/percentage_formula.test.ts | 98 ++++ .../common/convert_to_lens/lib/utils.test.ts | 521 ++++++++++++++++++ .../common/convert_to_lens/lib/utils.ts | 2 +- .../public/convert_to_lens/schemas.test.ts | 2 +- .../public/convert_to_lens/schemas.ts | 4 +- 22 files changed, 3484 insertions(+), 5 deletions(-) create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/column.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts new file mode 100644 index 0000000000000..f0a8e4d32f7c3 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { convertBucketToColumns } from '.'; +import { DateHistogramColumn, FiltersColumn, RangeColumn, TermsColumn } from '../../types'; +import { AggBasedColumn, SchemaConfig } from '../../..'; + +const mockConvertToDateHistogramColumn = jest.fn(); +const mockConvertToFiltersColumn = jest.fn(); +const mockConvertToTermsColumn = jest.fn(); +const mockConvertToRangeColumn = jest.fn(); + +jest.mock('../convert', () => ({ + convertToDateHistogramColumn: jest.fn(() => mockConvertToDateHistogramColumn()), + convertToFiltersColumn: jest.fn(() => mockConvertToFiltersColumn()), + convertToTermsColumn: jest.fn(() => mockConvertToTermsColumn()), + convertToRangeColumn: jest.fn(() => mockConvertToRangeColumn()), +})); + +describe('convertBucketToColumns', () => { + const field = stubLogstashDataView.fields[0].name; + const dateField = stubLogstashDataView.fields.find((f) => f.type === 'date')!.name; + const bucketAggs: SchemaConfig[] = [ + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.FILTERS, + aggParams: { + filters: [], + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.DATE_HISTOGRAM, + aggParams: { + field, + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.TERMS, + aggParams: { + field, + orderBy: '_key', + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.TERMS, + aggParams: { + field: dateField, + orderBy: '_key', + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.HISTOGRAM, + aggParams: { + field, + interval: '1h', + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.RANGE, + aggParams: { + field, + }, + }, + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: BUCKET_TYPES.DATE_RANGE, + aggParams: { + field, + }, + }, + ]; + const aggs: Array> = [ + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + }, + ]; + const metricColumns: AggBasedColumn[] = [ + { + columnId: 'column-1', + operationType: 'average', + isBucketed: false, + isSplit: false, + sourceField: field, + dataType: 'number', + params: {}, + meta: { + aggId: '1', + }, + }, + ]; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each< + [ + string, + Parameters, + () => void, + Partial | null + ] + >([ + [ + 'null if bucket agg type is not supported', + [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns }], + () => {}, + null, + ], + [ + 'null if bucket agg does not have aggParams', + [ + { + dataView: stubLogstashDataView, + agg: { ...bucketAggs[0], aggParams: undefined }, + aggs, + metricColumns, + }, + ], + () => {}, + null, + ], + [ + 'filters column if bucket agg is valid filters agg', + [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns }], + () => { + mockConvertToFiltersColumn.mockReturnValue({ + operationType: 'filters', + }); + }, + { + operationType: 'filters', + }, + ], + [ + 'date histogram column if bucket agg is valid date histogram agg', + [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns }], + () => { + mockConvertToDateHistogramColumn.mockReturnValue({ + operationType: 'date_histogram', + }); + }, + { + operationType: 'date_histogram', + }, + ], + [ + 'date histogram column if bucket agg is valid terms agg with date field', + [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns }], + () => { + mockConvertToDateHistogramColumn.mockReturnValue({ + operationType: 'date_histogram', + }); + }, + { + operationType: 'date_histogram', + }, + ], + [ + 'terms column if bucket agg is valid terms agg with no date field', + [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns }], + () => { + mockConvertToTermsColumn.mockReturnValue({ + operationType: 'terms', + }); + }, + { + operationType: 'terms', + }, + ], + [ + 'range column if bucket agg is valid histogram agg', + [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns }], + () => { + mockConvertToRangeColumn.mockReturnValue({ + operationType: 'range', + }); + }, + { + operationType: 'range', + }, + ], + [ + 'range column if bucket agg is valid range agg', + [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns }], + () => { + mockConvertToRangeColumn.mockReturnValue({ + operationType: 'range', + }); + }, + { + operationType: 'range', + }, + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(convertBucketToColumns(...input)).toBeNull(); + } else { + expect(convertBucketToColumns(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.test.ts new file mode 100644 index 0000000000000..74e9f2a57a9fe --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { createColumn } from './column'; +import { GeneralColumnWithMeta } from './types'; + +describe('createColumn', () => { + const field = stubLogstashDataView.fields[0]; + const aggId = `some-id`; + const customLabel = 'some-custom-label'; + const label = 'some label'; + const timeShift = '1h'; + + const agg: SchemaConfig = { + accessor: 0, + label, + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggId, + aggParams: { + field: field.name, + }, + }; + + const aggWithCustomLabel: SchemaConfig = { + ...agg, + aggParams: { + field: field.name, + customLabel, + }, + }; + + const aggWithTimeShift: SchemaConfig = { + ...agg, + aggParams: { + field: field.name, + timeShift, + }, + }; + + const extraColumnFields = { isBucketed: true, isSplit: true, reducedTimeRange: '1m' }; + + test.each<[string, Parameters, Partial]>([ + [ + 'with default params', + [agg, field], + { + dataType: 'number', + isBucketed: false, + isSplit: false, + label, + meta: { aggId }, + }, + ], + [ + 'with custom label', + [aggWithCustomLabel, field], + { + dataType: 'number', + isBucketed: false, + isSplit: false, + label: customLabel, + meta: { aggId }, + }, + ], + [ + 'with timeShift', + [aggWithTimeShift, field], + { + dataType: 'number', + isBucketed: false, + isSplit: false, + label, + meta: { aggId }, + timeShift, + }, + ], + [ + 'with extra column fields', + [agg, field, extraColumnFields], + { + dataType: 'number', + isBucketed: extraColumnFields.isBucketed, + isSplit: extraColumnFields.isSplit, + reducedTimeRange: extraColumnFields.reducedTimeRange, + label, + meta: { aggId }, + }, + ], + ])('should create column by agg %s', (_, input, expected) => { + expect(createColumn(...input)).toEqual(expect.objectContaining(expected)); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.test.ts new file mode 100644 index 0000000000000..897e5f876542b --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/date_histogram.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { AggParamsDateHistogram } from '@kbn/data-plugin/common'; +import { convertToDateHistogramColumn } from './date_histogram'; +import { DateHistogramColumn } from './types'; +import { DataType } from '../../types'; + +describe('convertToDateHistogramColumn', () => { + const aggId = `some-id`; + const timeShift = '1h'; + const aggParams: AggParamsDateHistogram = { + interval: '1d', + drop_partials: true, + field: stubLogstashDataView.fields[0].name, + }; + + test.each< + [string, Parameters, Partial | null] + >([ + [ + 'date histogram column if field is provided', + [aggId, aggParams, stubLogstashDataView, false, false], + { + dataType: stubLogstashDataView.fields[0].type as DataType, + isBucketed: true, + isSplit: false, + timeShift: undefined, + sourceField: stubLogstashDataView.fields[0].name, + meta: { aggId }, + params: { + interval: '1d', + includeEmptyRows: true, + dropPartials: true, + }, + }, + ], + [ + 'null if field is not provided', + [aggId, { interval: '1d', field: undefined }, stubLogstashDataView, false, false], + null, + ], + [ + 'date histogram column with isSplit and timeShift if specified', + [aggId, { ...aggParams, timeShift }, stubLogstashDataView, true, false], + { + dataType: stubLogstashDataView.fields[0].type as DataType, + isBucketed: true, + isSplit: true, + sourceField: stubLogstashDataView.fields[0].name, + timeShift, + meta: { aggId }, + params: { + interval: '1d', + includeEmptyRows: true, + dropPartials: true, + }, + }, + ], + [ + 'date histogram column with dropEmptyRowsInDateHistogram if specified', + [aggId, aggParams, stubLogstashDataView, true, true], + { + dataType: stubLogstashDataView.fields[0].type as DataType, + isBucketed: true, + isSplit: true, + sourceField: stubLogstashDataView.fields[0].name, + timeShift: undefined, + meta: { aggId }, + params: { + interval: '1d', + includeEmptyRows: false, + dropPartials: true, + }, + }, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToDateHistogramColumn(...input)).toBeNull(); + } else { + expect(convertToDateHistogramColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.test.ts new file mode 100644 index 0000000000000..1de1dbc9b312d --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/filters.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggParamsFilters } from '@kbn/data-plugin/common'; +import { convertToFiltersColumn } from './filters'; +import { FiltersColumn } from './types'; + +describe('convertToFiltersColumn', () => { + const aggId = `some-id`; + const timeShift = '1h'; + const filters = [{ input: { language: 'lucene', query: 'some other query' }, label: 'split' }]; + const aggParams: AggParamsFilters = { + filters, + }; + + test.each<[string, Parameters, Partial | null]>([ + [ + 'filters column if filters are provided', + [aggId, aggParams], + { + dataType: 'string', + isBucketed: true, + isSplit: false, + timeShift: undefined, + meta: { aggId }, + params: { filters: aggParams.filters! }, + }, + ], + ['null if filters are not provided', [aggId, {}], null], + [ + 'filters column with isSplit and timeShift if specified', + [aggId, { ...aggParams, timeShift }, true], + { + dataType: 'string', + isBucketed: true, + isSplit: true, + timeShift, + meta: { aggId }, + params: { filters: aggParams.filters! }, + }, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToFiltersColumn(...input)).toBeNull(); + } else { + expect(convertToFiltersColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.test.ts new file mode 100644 index 0000000000000..bbde6a04f1a2f --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { createFormulaColumn } from './formula'; + +describe('createFormulaColumn', () => { + const aggId = `some-id`; + const label = 'some label'; + const agg: SchemaConfig = { + accessor: 0, + label, + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.CUMULATIVE_SUM, + aggId, + aggParams: { + customMetric: { + id: 'some-id-metric', + enabled: true, + type: { name: METRIC_TYPES.AVG }, + params: { + field: stubLogstashDataView.fields[0], + }, + } as IAggConfig, + }, + }; + test('should return formula column', () => { + expect(createFormulaColumn('test-formula', agg)).toEqual( + expect.objectContaining({ + isBucketed: false, + isSplit: false, + meta: { + aggId, + }, + operationType: 'formula', + params: { + formula: 'test-formula', + }, + references: [], + }) + ); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts new file mode 100644 index 0000000000000..55ba1e8b5e09d --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggParamsTopHit, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToLastValueColumn } from './last_value'; +import { FiltersColumn } from './types'; + +const mockGetFieldNameFromField = jest.fn(); +const mockGetFieldByName = jest.fn(); +const mockGetLabel = jest.fn(); + +jest.mock('../utils', () => ({ + getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), + getLabel: jest.fn(() => mockGetLabel()), +})); + +describe('convertToLastValueColumn', () => { + const dataView = stubLogstashDataView; + const sortField = dataView.fields[0]; + + const topHitAggParams: AggParamsTopHit = { + sortOrder: { + value: 'desc', + text: 'some text', + }, + sortField, + field: '', + aggregate: 'min', + size: 1, + }; + + const topHitAgg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.TOP_HITS, + aggParams: topHitAggParams, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFieldNameFromField.mockReturnValue(dataView.fields[0]); + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + mockGetLabel.mockReturnValue('someLabel'); + dataView.getFieldByName = mockGetFieldByName; + }); + + test.each<[string, Parameters, Partial | null]>([ + [ + 'null if top hits size is more than 1', + [{ agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, dataView }], + null, + ], + [ + 'null if top hits sord order is not desc', + [ + { + agg: { + ...topHitAgg, + aggParams: { + ...topHitAgg.aggParams!, + sortOrder: { ...topHitAgg.aggParams!.sortOrder!, value: 'asc' }, + }, + }, + dataView, + }, + ], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToLastValueColumn(...input)).toBeNull(); + } else { + expect(convertToLastValueColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); + + test('should skip if top hit field is not specified', () => { + mockGetFieldNameFromField.mockReturnValue(null); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(0); + }); + + test('should skip if top hit field is not present in index pattern', () => { + mockGetFieldByName.mockReturnValue(null); + dataView.getFieldByName = mockGetFieldByName; + + expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + expect(mockGetLabel).toBeCalledTimes(0); + }); + + test('should return top hit column if top hit field is not present in index pattern', () => { + expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toEqual( + expect.objectContaining({ + dataType: 'number', + label: 'someLabel', + operationType: 'last_value', + params: { showArrayValues: true, sortField: 'bytes' }, + sourceField: 'bytes', + }) + ); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + expect(mockGetLabel).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts new file mode 100644 index 0000000000000..3be17abc46ac1 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertMetricAggregationColumnWithoutSpecialParams } from './metric'; +import { SUPPORTED_METRICS } from './supported_metrics'; + +const mockGetFieldByName = jest.fn(); + +describe('convertToLastValueColumn', () => { + const dataView = stubLogstashDataView; + + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggParams: { + field: dataView.fields[0].displayName, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + dataView.getFieldByName = mockGetFieldByName; + }); + + test('should return null metric is not supported', () => { + expect( + convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.TOP_HITS], { + agg, + dataView, + }) + ).toBeNull(); + }); + + test('should skip if field is not present and is required for the aggregation', () => { + mockGetFieldByName.mockReturnValue(null); + dataView.getFieldByName = mockGetFieldByName; + + expect( + convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { + agg, + dataView, + }) + ).toBeNull(); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return column if field is not present and is not required for the aggregation', () => { + mockGetFieldByName.mockReturnValue(null); + dataView.getFieldByName = mockGetFieldByName; + + expect( + convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.COUNT], { + agg, + dataView, + }) + ).toEqual(expect.objectContaining({ operationType: 'count' })); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return column if field is present and is required for the aggregation', () => { + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + dataView.getFieldByName = mockGetFieldByName; + + expect( + convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { + agg, + dataView, + }) + ).toEqual( + expect.objectContaining({ + dataType: 'number', + operationType: 'average', + }) + ); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts new file mode 100644 index 0000000000000..c28324533c837 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts @@ -0,0 +1,438 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { FormulaColumn, AggBasedColumn } from './types'; +import { SchemaConfig } from '../../..'; +import { + convertToOtherParentPipelineAggColumns, + ParentPipelineAggColumn, + convertToCumulativeSumAggColumn, +} from './parent_pipeline'; + +const mockGetMetricFromParentPipelineAgg = jest.fn(); +const mockGetFormulaForPipelineAgg = jest.fn(); +const mockConvertMetricToColumns = jest.fn(); +const mockGetFieldByName = jest.fn(); +const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); + +jest.mock('../utils', () => ({ + getMetricFromParentPipelineAgg: jest.fn(() => mockGetMetricFromParentPipelineAgg()), + getLabel: jest.fn(() => 'label'), + getFieldNameFromField: jest.fn(() => 'document'), +})); + +jest.mock('./metric', () => ({ + convertMetricAggregationColumnWithoutSpecialParams: jest.fn(() => + mockConvertMetricAggregationColumnWithoutSpecialParams() + ), +})); + +jest.mock('../metrics', () => ({ + getFormulaForPipelineAgg: jest.fn(() => mockGetFormulaForPipelineAgg()), + convertMetricToColumns: jest.fn(() => mockConvertMetricToColumns()), +})); + +describe('convertToOtherParentPipelineAggColumns', () => { + const field = stubLogstashDataView.fields[0].name; + const aggs: Array> = [ + { + aggId: '1', + aggType: METRIC_TYPES.AVG, + aggParams: { field }, + accessor: 0, + params: {}, + label: 'average', + format: {}, + }, + { + aggId: '1', + aggType: METRIC_TYPES.MOVING_FN, + aggParams: { metricAgg: '2' }, + accessor: 0, + params: {}, + label: 'Moving Average of Average', + format: {}, + }, + ]; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each< + [ + string, + Parameters, + () => void, + Partial | [Partial, Partial] | null + ] + >([ + [ + 'null if getMetricFromParentPipelineAgg returns null', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'null if cutom metric of parent pipeline agg is not supported', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.GEO_BOUNDS, + }); + }, + null, + ], + [ + 'null if cutom metric of parent pipeline agg is sibling pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.AVG_BUCKET, + }); + }, + null, + ], + [ + 'null if cannot build formula if cutom metric of parent pipeline agg is parent pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.MOVING_FN, + }); + mockGetFormulaForPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'formula column if cutom metric of parent pipeline agg is valid parent pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.MOVING_FN, + }); + mockGetFormulaForPipelineAgg.mockReturnValue('test-formula'); + }, + { + operationType: 'formula', + params: { + formula: 'test-formula', + }, + }, + ], + [ + 'null if cutom metric of parent pipeline agg is invalid not pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.AVG, + }); + mockConvertMetricToColumns.mockReturnValue(null); + }, + null, + ], + [ + 'parent pipeline and metric columns if cutom metric of parent pipeline agg is valid not pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.AVG, + }); + mockConvertMetricToColumns.mockReturnValue([ + { + columnId: 'test-id-1', + operationType: 'average', + sourceField: field, + }, + ]); + }, + [ + { operationType: 'moving_average', references: ['test-id-1'] }, + { + columnId: 'test-id-1', + operationType: 'average', + sourceField: field, + }, + ], + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(convertToOtherParentPipelineAggColumns(...input)).toBeNull(); + } else if (Array.isArray(expected)) { + expect(convertToOtherParentPipelineAggColumns(...input)).toEqual( + expected.map(expect.objectContaining) + ); + } else { + expect(convertToOtherParentPipelineAggColumns(...input)).toEqual( + expect.objectContaining(expected) + ); + } + }); +}); + +describe('convertToCumulativeSumAggColumn', () => { + const field = stubLogstashDataView.fields[0].name; + const aggs: Array> = [ + { + aggId: '1', + aggType: METRIC_TYPES.AVG, + aggParams: { field }, + accessor: 0, + params: {}, + label: 'average', + format: {}, + }, + { + aggId: '1', + aggType: METRIC_TYPES.CUMULATIVE_SUM, + aggParams: { metricAgg: '2' }, + accessor: 0, + params: {}, + label: 'Moving Average of Average', + format: {}, + }, + ]; + + beforeEach(() => { + mockGetFieldByName.mockReturnValue({ + aggregatable: true, + type: 'number', + sourceField: 'bytes', + }); + + stubLogstashDataView.getFieldByName = mockGetFieldByName; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each< + [ + string, + Parameters, + () => void, + Partial | [Partial, Partial] | null + ] + >([ + [ + 'null if cumulative sum does not have aggParams', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: { ...aggs[1], aggParams: undefined } as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'null if getMetricFromParentPipelineAgg returns null', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'null if cutom metric of parent pipeline agg is not supported', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.GEO_BOUNDS, + }); + }, + null, + ], + [ + 'null if cutom metric of parent pipeline agg is sibling pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.AVG_BUCKET, + }); + }, + null, + ], + [ + 'null if cannot build formula if cutom metric of parent pipeline agg is parent pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.MOVING_FN, + }); + mockGetFormulaForPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'formula column if cutom metric of parent pipeline agg is valid parent pipeline agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.MOVING_FN, + }); + mockGetFormulaForPipelineAgg.mockReturnValue('test-formula'); + }, + { + operationType: 'formula', + params: { + formula: 'test-formula', + }, + }, + ], + [ + 'null if cutom metric of parent pipeline agg is invalid sum or count agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.SUM, + }); + mockConvertMetricAggregationColumnWithoutSpecialParams.mockReturnValue(null); + }, + null, + ], + [ + 'cumulative sum and metric columns if cutom metric of parent pipeline agg is valid sum or count agg', + [ + { + dataView: stubLogstashDataView, + aggs, + agg: aggs[1] as SchemaConfig, + }, + ], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggId: '2-metric', + aggType: METRIC_TYPES.SUM, + }); + mockConvertMetricAggregationColumnWithoutSpecialParams.mockReturnValue({ + columnId: 'test-id-1', + operationType: 'sum', + sourceField: field, + }); + }, + [ + { operationType: 'cumulative_sum', references: ['test-id-1'] }, + { + columnId: 'test-id-1', + operationType: 'sum', + sourceField: field, + }, + ], + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(convertToCumulativeSumAggColumn(...input)).toBeNull(); + } else if (Array.isArray(expected)) { + expect(convertToCumulativeSumAggColumn(...input)).toEqual( + expected.map(expect.objectContaining) + ); + } else { + expect(convertToCumulativeSumAggColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts index c1fd75ae19265..ab41ceb259adb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts @@ -122,6 +122,7 @@ export const convertToCumulativeSumAggColumn = ( { agg: metric as SchemaConfig, dataView }, reducedTimeRange ); + if (subMetric === null) { return null; } @@ -134,8 +135,8 @@ export const convertToCumulativeSumAggColumn = ( return [ { operationType: op.name, - references: [subMetric?.columnId], ...createColumn(agg), + references: [subMetric?.columnId], params: {}, timeShift: agg.aggParams?.timeShift, } as ParentPipelineAggColumn, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts new file mode 100644 index 0000000000000..b4cf7f141e928 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToPercentileColumn } from './percentile'; +import { PercentileColumn } from './types'; + +const mockGetFieldNameFromField = jest.fn(); +const mockGetFieldByName = jest.fn(); +const mockGetLabel = jest.fn(); +const mockGetLabelForPercentile = jest.fn(); + +jest.mock('../utils', () => ({ + getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), + getLabel: jest.fn(() => mockGetLabel()), + getLabelForPercentile: jest.fn(() => mockGetLabelForPercentile()), +})); + +describe('convertToPercentileColumn', () => { + const dataView = stubLogstashDataView; + const field = dataView.fields[0].displayName; + const aggId = 'pr.10'; + const percentile = 10; + const percents = [percentile]; + + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.PERCENTILES, + aggParams: { field, percents }, + aggId, + }; + const singlePercentileRankAgg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.SINGLE_PERCENTILE, + aggParams: { field, percentile }, + aggId, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFieldNameFromField.mockReturnValue(dataView.fields[0]); + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + mockGetLabel.mockReturnValue('someLabel'); + mockGetLabelForPercentile.mockReturnValue('someOtherLabel'); + dataView.getFieldByName = mockGetFieldByName; + }); + + test.each< + [string, Parameters, Partial | null] + >([ + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + [ + 'null if no value', + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], + ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], + [ + 'null if values are undefined', + [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView }], + null, + ], + [ + 'null if values are empty', + [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView }], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToPercentileColumn(...input)).toBeNull(); + } else { + expect(convertToPercentileColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); + + test('should return null if field is not specified', () => { + mockGetFieldNameFromField.mockReturnValue(null); + expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(0); + }); + + test('should return null if field absent at the index pattern', () => { + mockGetFieldByName.mockReturnValueOnce(null); + dataView.getFieldByName = mockGetFieldByName; + + expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return percentile rank column for percentiles', () => { + expect(convertToPercentileColumn({ agg, dataView })).toEqual( + expect.objectContaining({ + dataType: 'number', + label: 'someOtherLabel', + meta: { aggId: 'pr.10' }, + operationType: 'percentile', + params: { percentile: 10 }, + sourceField: 'bytes', + }) + ); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return percentile rank column for single percentile', () => { + expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect.objectContaining({ + dataType: 'number', + label: 'someOtherLabel', + meta: { aggId: 'pr.10' }, + operationType: 'percentile', + params: { percentile: 10 }, + sourceField: 'bytes', + }) + ); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts new file mode 100644 index 0000000000000..8a696d51d871b --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToPercentileRankColumn } from './percentile_rank'; +import { PercentileRanksColumn } from './types'; + +const mockGetFieldNameFromField = jest.fn(); +const mockGetFieldByName = jest.fn(); +const mockGetLabel = jest.fn(); +const mockGetLabelForPercentile = jest.fn(); + +jest.mock('../utils', () => ({ + getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), + getLabel: jest.fn(() => mockGetLabel()), + getLabelForPercentile: jest.fn(() => mockGetLabelForPercentile()), +})); + +describe('convertToPercentileRankColumn', () => { + const dataView = stubLogstashDataView; + const field = dataView.fields[0].displayName; + const aggId = 'pr.10'; + const value = 10; + const values = [value]; + + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.PERCENTILE_RANKS, + aggParams: { field, values }, + aggId, + }; + const singlePercentileRankAgg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK, + aggParams: { field, value }, + aggId, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFieldNameFromField.mockReturnValue(dataView.fields[0]); + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + mockGetLabel.mockReturnValue('someLabel'); + mockGetLabelForPercentile.mockReturnValue('someOtherLabel'); + dataView.getFieldByName = mockGetFieldByName; + }); + + test.each< + [ + string, + Parameters, + Partial | null + ] + >([ + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + [ + 'null if no value', + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], + ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], + [ + 'null if values are undefined', + [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView }], + null, + ], + [ + 'null if values are empty', + [{ agg: { ...agg, aggParams: { values: [], field } }, dataView }], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToPercentileRankColumn(...input)).toBeNull(); + } else { + expect(convertToPercentileRankColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); + + test('should return null if field is not specified', () => { + mockGetFieldNameFromField.mockReturnValue(null); + expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(0); + }); + + test('should return null if field absent at the index pattern', () => { + mockGetFieldByName.mockReturnValueOnce(null); + dataView.getFieldByName = mockGetFieldByName; + + expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return percentile rank column for percentile ranks', () => { + expect(convertToPercentileRankColumn({ agg, dataView })).toEqual( + expect.objectContaining({ + dataType: 'number', + label: 'someOtherLabel', + meta: { aggId: 'pr.10' }, + operationType: 'percentile_rank', + params: { value: 10 }, + sourceField: 'bytes', + }) + ); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return percentile rank column for single percentile rank', () => { + expect(convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect.objectContaining({ + dataType: 'number', + label: 'someOtherLabel', + meta: { aggId: 'pr.10' }, + operationType: 'percentile_rank', + params: { value: 10 }, + sourceField: 'bytes', + }) + ); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts new file mode 100644 index 0000000000000..8f535c28c8264 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { AggParamsRange, AggParamsHistogram } from '@kbn/data-plugin/common'; +import { convertToRangeColumn } from './range'; +import { RangeColumn } from './types'; +import { DataType } from '../../types'; +import { RANGE_MODES } from '../../constants'; + +describe('convertToRangeColumn', () => { + const aggId = `some-id`; + const ranges = [ + { + from: 1, + to: 1000, + label: '1', + }, + ]; + const aggParamsRange: AggParamsRange = { + field: stubLogstashDataView.fields[0].name, + ranges, + }; + const aggParamsHistogram: AggParamsHistogram = { + interval: '1d', + field: stubLogstashDataView.fields[0].name, + }; + + test.each<[string, Parameters, Partial | null]>([ + [ + 'range column if provide valid range agg', + [aggId, aggParamsRange, '', stubLogstashDataView], + { + dataType: stubLogstashDataView.fields[0].type as DataType, + isBucketed: true, + isSplit: false, + sourceField: stubLogstashDataView.fields[0].name, + meta: { aggId }, + params: { + type: RANGE_MODES.Range, + maxBars: 'auto', + ranges, + }, + }, + ], + [ + 'range column if provide valid histogram agg', + [aggId, aggParamsHistogram, '', stubLogstashDataView, true], + { + dataType: stubLogstashDataView.fields[0].type as DataType, + isBucketed: true, + isSplit: true, + sourceField: stubLogstashDataView.fields[0].name, + meta: { aggId }, + params: { + type: RANGE_MODES.Histogram, + maxBars: 'auto', + ranges: [], + }, + }, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToRangeColumn(...input)).toBeNull(); + } else { + expect(convertToRangeColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts new file mode 100644 index 0000000000000..759620650b8a6 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToSiblingPipelineColumns } from './sibling_pipeline'; + +const mockConvertMetricToColumns = jest.fn(); +const mockConvertToSchemaConfig = jest.fn(); + +jest.mock('../metrics', () => ({ + convertMetricToColumns: jest.fn(() => mockConvertMetricToColumns()), +})); + +jest.mock('../../../vis_schemas', () => ({ + convertToSchemaConfig: jest.fn(() => mockConvertToSchemaConfig()), +})); + +describe('convertToSiblingPipelineColumns', () => { + const dataView = stubLogstashDataView; + const aggId = 'agg-id-1'; + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + aggParams: { customMetric: {} as IAggConfig }, + aggId, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockConvertMetricToColumns.mockReturnValue([{}]); + mockConvertToSchemaConfig.mockReturnValue({}); + }); + + test('should return null if aggParams are not defined', () => { + expect( + convertToSiblingPipelineColumns({ agg: { ...agg, aggParams: undefined }, aggs: [], dataView }) + ).toBeNull(); + expect(mockConvertMetricToColumns).toBeCalledTimes(0); + }); + + test('should return null if customMetric is not defined', () => { + expect( + convertToSiblingPipelineColumns({ + agg: { ...agg, aggParams: { customMetric: undefined } }, + aggs: [], + dataView, + }) + ).toBeNull(); + expect(mockConvertMetricToColumns).toBeCalledTimes(0); + }); + + test('should return null if sibling agg is not supported', () => { + mockConvertMetricToColumns.mockReturnValue(null); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toBeNull(); + expect(mockConvertToSchemaConfig).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + }); + + test('should return column', () => { + const column = { operationType: 'formula' }; + mockConvertMetricToColumns.mockReturnValue([column]); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toEqual(column); + expect(mockConvertToSchemaConfig).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts new file mode 100644 index 0000000000000..cbb1f03a6dc2e --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToStdDeviationFormulaColumns } from './std_deviation'; +import { FormulaColumn } from './types'; + +const mockGetFieldNameFromField = jest.fn(); +const mockGetFieldByName = jest.fn(); +const mockGetLabel = jest.fn(); + +jest.mock('../utils', () => ({ + getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), + getLabel: jest.fn(() => mockGetLabel()), +})); + +describe('convertToStdDeviationFormulaColumns', () => { + const dataView = stubLogstashDataView; + const stdLowerAggId = 'agg-id.std_lower'; + const stdUpperAggId = 'agg-id.std_upper'; + const label = 'std label'; + const agg: SchemaConfig = { + accessor: 0, + label, + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.STD_DEV, + aggId: stdLowerAggId, + aggParams: { + field: dataView.fields[0].displayName, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFieldNameFromField.mockReturnValue(dataView.fields[0].displayName); + mockGetFieldByName.mockReturnValue(dataView.fields[0]); + mockGetLabel.mockReturnValue('some label'); + dataView.getFieldByName = mockGetFieldByName; + }); + + test.each< + [string, Parameters, Partial | null] + >([['null if no aggId is passed', [{ agg: { ...agg, aggId: undefined }, dataView }], null]])( + 'should return %s', + (_, input, expected) => { + if (expected === null) { + expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); + } else { + expect(convertToStdDeviationFormulaColumns(...input)).toEqual( + expect.objectContaining(expected) + ); + } + } + ); + + test('should return null if field is not present', () => { + mockGetFieldNameFromField.mockReturnValue(null); + expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(0); + }); + + test("should return null if field doesn't exist in dataView", () => { + mockGetFieldByName.mockReturnValue(null); + dataView.getFieldByName = mockGetFieldByName; + expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return null if agg id is invalid', () => { + expect( + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView }) + ).toBeNull(); + expect(mockGetFieldNameFromField).toBeCalledTimes(1); + expect(dataView.getFieldByName).toBeCalledTimes(1); + }); + + test('should return formula column for lower std deviation', () => { + expect( + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdLowerAggId }, dataView }) + ).toEqual( + expect.objectContaining({ + label, + meta: { aggId: 'agg-id.std_lower' }, + operationType: 'formula', + params: { formula: 'average(bytes) - 2 * standard_deviation(bytes)' }, + }) + ); + }); + + test('should return formula column for upper std deviation', () => { + expect( + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdUpperAggId }, dataView }) + ).toEqual( + expect.objectContaining({ + label, + meta: { aggId: 'agg-id.std_upper' }, + operationType: 'formula', + params: { formula: 'average(bytes) + 2 * standard_deviation(bytes)' }, + }) + ); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts new file mode 100644 index 0000000000000..d214ec74b09b1 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts @@ -0,0 +1,241 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { AggParamsTerms, IAggConfig, METRIC_TYPES, BUCKET_TYPES } from '@kbn/data-plugin/common'; +import { convertToTermsColumn } from './terms'; +import { AggBasedColumn, TermsColumn } from './types'; +import { SchemaConfig } from '../../..'; + +const mockConvertMetricToColumns = jest.fn(); + +jest.mock('../metrics', () => ({ + convertMetricToColumns: jest.fn(() => mockConvertMetricToColumns()), +})); + +jest.mock('../../../vis_schemas', () => ({ + convertToSchemaConfig: jest.fn(() => ({})), +})); + +describe('convertToDateHistogramColumn', () => { + const aggId = `some-id`; + const aggParams: AggParamsTerms = { + field: stubLogstashDataView.fields[0].name, + orderBy: '_key', + order: { + value: 'asc', + text: '', + }, + size: 5, + }; + const aggs: Array> = [ + { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggParams: { + field: stubLogstashDataView.fields[0].name, + }, + }, + ]; + const metricColumns: AggBasedColumn[] = [ + { + columnId: 'column-1', + operationType: 'average', + isBucketed: false, + isSplit: false, + sourceField: stubLogstashDataView.fields[0].name, + dataType: 'number', + params: {}, + meta: { + aggId: '1', + }, + }, + ]; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each< + [string, Parameters, Partial | null, () => void] + >([ + [ + 'null if dataview does not include field from terms params', + [ + aggId, + { + agg: { aggParams: { ...aggParams, field: '' } } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + null, + () => {}, + ], + [ + 'terms column with alphabetical orderBy', + [ + aggId, + { + agg: { aggParams } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + { + operationType: 'terms', + sourceField: stubLogstashDataView.fields[0].name, + isBucketed: true, + params: { + size: 5, + include: [], + exclude: [], + parentFormat: { id: 'terms' }, + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + }, + }, + () => {}, + ], + [ + 'terms column with column orderBy if provided column for orderBy is exist', + [ + aggId, + { + agg: { aggParams: { ...aggParams, orderBy: '1' } } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + { + operationType: 'terms', + sourceField: stubLogstashDataView.fields[0].name, + isBucketed: true, + params: { + size: 5, + include: [], + exclude: [], + parentFormat: { id: 'terms' }, + orderBy: { type: 'column', columnId: metricColumns[0].columnId }, + orderAgg: metricColumns[0], + orderDirection: 'asc', + }, + }, + () => {}, + ], + [ + 'null if provided column for orderBy is not exist', + [ + aggId, + { + agg: { aggParams: { ...aggParams, orderBy: '2' } } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + null, + () => {}, + ], + [ + 'null if provided custom orderBy without orderAgg', + [ + aggId, + { + agg: { + aggParams: { ...aggParams, orderBy: 'custom', orderAgg: undefined }, + } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + null, + () => {}, + ], + [ + 'null if provided custom orderBy and not valid orderAgg', + [ + aggId, + { + agg: { + aggParams: { ...aggParams, orderBy: 'custom', orderAgg: {} as IAggConfig }, + } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + null, + () => { + mockConvertMetricToColumns.mockReturnValue(null); + }, + ], + [ + 'terms column with custom orderBy and prepared orderAgg', + [ + aggId, + { + agg: { + aggParams: { ...aggParams, orderBy: 'custom', orderAgg: {} as IAggConfig }, + } as SchemaConfig, + dataView: stubLogstashDataView, + aggs, + metricColumns, + }, + '', + false, + ], + { + operationType: 'terms', + sourceField: stubLogstashDataView.fields[0].name, + isBucketed: true, + params: { + size: 5, + include: [], + exclude: [], + parentFormat: { id: 'terms' }, + orderBy: { type: 'custom' }, + orderAgg: metricColumns[0], + orderDirection: 'asc', + }, + }, + () => { + mockConvertMetricToColumns.mockReturnValue(metricColumns); + }, + ], + ])('should return %s', (_, input, expected, actions) => { + actions(); + if (expected === null) { + expect(convertToTermsColumn(...input)).toBeNull(); + } else { + expect(convertToTermsColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts new file mode 100644 index 0000000000000..95e128e22b092 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts @@ -0,0 +1,474 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { DataViewField, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { SchemaConfig } from '../../..'; +import { getFormulaForPipelineAgg, getFormulaForAgg } from './formula'; + +const mockGetMetricFromParentPipelineAgg = jest.fn(); +const mockIsPercentileAgg = jest.fn(); +const mockIsPercentileRankAgg = jest.fn(); +const mockIsPipeline = jest.fn(); +const mockIsStdDevAgg = jest.fn(); +const mockGetFieldByName = jest.fn(); +const originalGetFieldByName = stubLogstashDataView.getFieldByName; + +jest.mock('../utils', () => ({ + getFieldNameFromField: jest.fn((field) => field), + getMetricFromParentPipelineAgg: jest.fn(() => mockGetMetricFromParentPipelineAgg()), + isPercentileAgg: jest.fn(() => mockIsPercentileAgg()), + isPercentileRankAgg: jest.fn(() => mockIsPercentileRankAgg()), + isPipeline: jest.fn(() => mockIsPipeline()), + isStdDevAgg: jest.fn(() => mockIsStdDevAgg()), +})); + +const dataView = stubLogstashDataView; + +const field = stubLogstashDataView.fields[0].name; +const aggs: Array> = [ + { + aggId: '1', + aggType: METRIC_TYPES.CUMULATIVE_SUM, + aggParams: { customMetric: {} as IAggConfig }, + accessor: 0, + params: {}, + label: 'cumulative sum', + format: {}, + }, + { + aggId: '2', + aggType: METRIC_TYPES.AVG_BUCKET, + aggParams: { customMetric: {} as IAggConfig }, + accessor: 0, + params: {}, + label: 'overall average', + format: {}, + }, + { + aggId: '3.10', + aggType: METRIC_TYPES.PERCENTILES, + aggParams: { percents: [0, 10], field }, + accessor: 0, + params: {}, + label: 'percentile', + format: {}, + }, + { + aggId: '4.5', + aggType: METRIC_TYPES.PERCENTILE_RANKS, + aggParams: { values: [0, 5], field }, + accessor: 0, + params: {}, + label: 'percintile rank', + format: {}, + }, + { + aggId: '5.std_upper', + aggType: METRIC_TYPES.STD_DEV, + aggParams: { field }, + accessor: 0, + params: {}, + label: 'std dev', + format: {}, + }, + { + aggId: '6', + aggType: METRIC_TYPES.AVG, + aggParams: { field }, + accessor: 0, + params: {}, + label: 'average', + format: {}, + }, +]; + +describe('getFormulaForPipelineAgg', () => { + afterEach(() => { + jest.clearAllMocks(); + dataView.getFieldByName = originalGetFieldByName; + }); + + test.each<[string, Parameters, () => void, string | null]>([ + [ + 'null if custom metric is invalid', + [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue(null); + }, + null, + ], + [ + 'null if custom metric type is not supported', + [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValue({ + aggType: METRIC_TYPES.GEO_BOUNDS, + }); + }, + null, + ], + [ + 'correct formula if agg is parent pipeline agg and custom metric is valid and supported pipeline agg', + [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg + .mockReturnValueOnce({ + aggType: METRIC_TYPES.MOVING_FN, + aggParams: {}, + aggId: '2', + }) + .mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '3', + }); + }, + 'cumulative_sum(moving_average(average(bytes)))', + ], + [ + 'correct formula if agg is parent pipeline agg and custom metric is valid and supported not pipeline agg', + [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '2', + }); + }, + 'cumulative_sum(average(bytes))', + ], + [ + 'correct formula if agg is parent pipeline agg and custom metric is valid and supported percentile rank agg', + [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.PERCENTILE_RANKS, + aggParams: { + field, + }, + aggId: '3.10', + }); + }, + 'cumulative_sum(percentile_rank(bytes, value=10))', + ], + [ + 'correct formula if agg is sibling pipeline agg and custom metric is valid and supported agg', + [{ agg: aggs[1] as SchemaConfig, aggs, dataView }], + () => { + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '3', + }); + }, + 'average(bytes)', + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(getFormulaForPipelineAgg(...input)).toBeNull(); + } else { + expect(getFormulaForPipelineAgg(...input)).toEqual(expected); + } + }); + + test('null if agg is sibling pipeline agg, custom metric is valid, agg is supported and field type is not supported', () => { + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '3', + }); + + const field1: DataViewField = { + name: 'bytes', + type: 'geo', + esTypes: ['long'], + aggregatable: true, + searchable: true, + count: 10, + readFromDocValues: true, + scripted: false, + isMapped: true, + } as DataViewField; + + mockGetFieldByName.mockReturnValueOnce(field1); + + dataView.getFieldByName = mockGetFieldByName; + const agg = getFormulaForPipelineAgg({ + agg: aggs[1] as SchemaConfig, + aggs, + dataView, + }); + expect(agg).toBeNull(); + }); + + test('null if agg is sibling pipeline agg, custom metric is valid, agg is supported, field type is supported and field is not aggregatable', () => { + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '3', + }); + + const field1: DataViewField = { + name: 'str', + type: 'string', + esTypes: ['text'], + aggregatable: false, + searchable: true, + count: 10, + readFromDocValues: true, + scripted: false, + isMapped: true, + } as DataViewField; + + mockGetFieldByName.mockReturnValueOnce(field1); + + dataView.getFieldByName = mockGetFieldByName; + const agg = getFormulaForPipelineAgg({ + agg: aggs[1] as SchemaConfig, + aggs, + dataView, + }); + expect(agg).toBeNull(); + }); +}); + +describe('getFormulaForAgg', () => { + beforeEach(() => { + mockIsPercentileAgg.mockReturnValue(false); + mockIsPipeline.mockReturnValue(false); + mockIsStdDevAgg.mockReturnValue(false); + mockIsPercentileRankAgg.mockReturnValue(false); + }); + + afterEach(() => { + jest.clearAllMocks(); + dataView.getFieldByName = originalGetFieldByName; + }); + + test.each<[string, Parameters, () => void, string | null]>([ + [ + 'null if agg type is not supported', + [ + { + agg: { ...aggs[0], aggType: METRIC_TYPES.GEO_BOUNDS, aggParams: { field } }, + aggs, + dataView, + }, + ], + () => {}, + null, + ], + [ + 'correct pipeline formula if agg is valid pipeline agg', + [{ agg: aggs[0], aggs, dataView }], + () => { + mockIsPipeline.mockReturnValue(true); + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '2', + }); + }, + 'cumulative_sum(average(bytes))', + ], + [ + 'correct percentile formula if agg is valid percentile agg', + [{ agg: aggs[2], aggs, dataView }], + () => { + mockIsPercentileAgg.mockReturnValue(true); + }, + 'percentile(bytes, percentile=10)', + ], + [ + 'correct percentile rank formula if agg is valid percentile rank agg', + [{ agg: aggs[3], aggs, dataView }], + () => { + mockIsPercentileRankAgg.mockReturnValue(true); + }, + 'percentile_rank(bytes, value=5)', + ], + [ + 'correct standart deviation formula if agg is valid standart deviation agg', + [{ agg: aggs[4], aggs, dataView }], + () => { + mockIsStdDevAgg.mockReturnValue(true); + }, + 'average(bytes) + 2 * standard_deviation(bytes)', + ], + [ + 'correct metric formula if agg is valid other metric agg', + [{ agg: aggs[5], aggs, dataView }], + () => {}, + 'average(bytes)', + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(getFormulaForAgg(...input)).toBeNull(); + } else { + expect(getFormulaForAgg(...input)).toEqual(expected); + } + }); + + test.each([ + [ + 'null if agg is valid pipeline agg', + aggs[0], + () => { + mockIsPipeline.mockReturnValue(true); + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '2', + }); + }, + ], + [ + 'null if percentile rank agg is valid percentile agg', + aggs[2], + () => { + mockIsPercentileAgg.mockReturnValue(true); + }, + ], + [ + 'null if agg is valid percentile rank agg', + aggs[3], + () => { + mockIsPercentileRankAgg.mockReturnValue(true); + }, + ], + [ + 'null if agg is valid standart deviation agg', + aggs[4], + () => { + mockIsStdDevAgg.mockReturnValue(true); + }, + ], + ['null if agg is valid other metric agg', aggs[5], () => {}], + ])('should return %s and field type is not supported', (_, agg, actions) => { + actions(); + const field1: DataViewField = { + name: 'bytes', + type: 'geo', + esTypes: ['long'], + aggregatable: true, + searchable: true, + count: 10, + readFromDocValues: true, + scripted: false, + isMapped: true, + } as DataViewField; + + mockGetFieldByName.mockReturnValueOnce(field1); + + dataView.getFieldByName = mockGetFieldByName; + const result = getFormulaForPipelineAgg({ + agg: agg as SchemaConfig< + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.SUM_BUCKET + >, + aggs, + dataView, + }); + expect(result).toBeNull(); + }); + + test.each([ + [ + 'null if agg is valid pipeline agg', + aggs[0], + () => { + mockIsPipeline.mockReturnValue(true); + mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ + aggType: METRIC_TYPES.AVG, + aggParams: { + field, + }, + aggId: '2', + }); + }, + ], + [ + 'null if percentile rank agg is valid percentile agg', + aggs[2], + () => { + mockIsPercentileAgg.mockReturnValue(true); + }, + ], + [ + 'null if agg is valid percentile rank agg', + aggs[3], + () => { + mockIsPercentileRankAgg.mockReturnValue(true); + }, + ], + [ + 'null if agg is valid standart deviation agg', + aggs[4], + () => { + mockIsStdDevAgg.mockReturnValue(true); + }, + ], + ['null if agg is valid other metric agg', aggs[5], () => {}], + ])( + 'should return %s, field type is supported and field is not aggregatable', + (_, agg, actions) => { + actions(); + const field1: DataViewField = { + name: 'str', + type: 'string', + esTypes: ['text'], + aggregatable: false, + searchable: true, + count: 10, + readFromDocValues: true, + scripted: false, + isMapped: true, + } as DataViewField; + + mockGetFieldByName.mockReturnValueOnce(field1); + + dataView.getFieldByName = mockGetFieldByName; + const result = getFormulaForPipelineAgg({ + agg: agg as SchemaConfig< + | METRIC_TYPES.CUMULATIVE_SUM + | METRIC_TYPES.DERIVATIVE + | METRIC_TYPES.MOVING_FN + | METRIC_TYPES.AVG_BUCKET + | METRIC_TYPES.MAX_BUCKET + | METRIC_TYPES.MIN_BUCKET + | METRIC_TYPES.SUM_BUCKET + >, + aggs, + dataView, + }); + expect(result).toBeNull(); + } + ); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts new file mode 100644 index 0000000000000..659ab80ea03d5 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts @@ -0,0 +1,368 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertMetricToColumns } from './metrics'; + +const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); +const mockConvertToOtherParentPipelineAggColumns = jest.fn(); +const mockConvertToPercentileColumn = jest.fn(); +const mockConvertToPercentileRankColumn = jest.fn(); +const mockConvertToSiblingPipelineColumns = jest.fn(); +const mockConvertToStdDeviationFormulaColumns = jest.fn(); +const mockConvertToLastValueColumn = jest.fn(); +const mockConvertToCumulativeSumAggColumn = jest.fn(); + +jest.mock('../convert', () => ({ + convertMetricAggregationColumnWithoutSpecialParams: jest.fn(() => + mockConvertMetricAggregationColumnWithoutSpecialParams() + ), + convertToOtherParentPipelineAggColumns: jest.fn(() => + mockConvertToOtherParentPipelineAggColumns() + ), + convertToPercentileColumn: jest.fn(() => mockConvertToPercentileColumn()), + convertToPercentileRankColumn: jest.fn(() => mockConvertToPercentileRankColumn()), + convertToSiblingPipelineColumns: jest.fn(() => mockConvertToSiblingPipelineColumns()), + convertToStdDeviationFormulaColumns: jest.fn(() => mockConvertToStdDeviationFormulaColumns()), + convertToLastValueColumn: jest.fn(() => mockConvertToLastValueColumn()), + convertToCumulativeSumAggColumn: jest.fn(() => mockConvertToCumulativeSumAggColumn()), +})); + +describe('convertMetricToColumns invalid cases', () => { + const dataView = stubLogstashDataView; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(() => { + mockConvertMetricAggregationColumnWithoutSpecialParams.mockReturnValue(null); + mockConvertToOtherParentPipelineAggColumns.mockReturnValue(null); + mockConvertToPercentileColumn.mockReturnValue(null); + mockConvertToPercentileRankColumn.mockReturnValue(null); + mockConvertToSiblingPipelineColumns.mockReturnValue(null); + mockConvertToStdDeviationFormulaColumns.mockReturnValue(null); + mockConvertToLastValueColumn.mockReturnValue(null); + mockConvertToCumulativeSumAggColumn.mockReturnValue(null); + }); + + test.each<[string, Parameters, null, jest.Mock | undefined]>([ + [ + 'null if agg is not supported', + [{ aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, dataView, []], + null, + undefined, + ], + [ + 'null if supported agg AVG is not valid', + [{ aggType: METRIC_TYPES.AVG } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg MIN is not valid', + [{ aggType: METRIC_TYPES.MIN } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg MAX is not valid', + [{ aggType: METRIC_TYPES.MAX } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg SUM is not valid', + [{ aggType: METRIC_TYPES.SUM } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg COUNT is not valid', + [{ aggType: METRIC_TYPES.COUNT } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg CARDINALITY is not valid', + [{ aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg VALUE_COUNT is not valid', + [{ aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg MEDIAN is not valid', + [{ aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, dataView, []], + null, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'null if supported agg STD_DEV is not valid', + [{ aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, dataView, []], + null, + mockConvertToStdDeviationFormulaColumns, + ], + [ + 'null if supported agg PERCENTILES is not valid', + [{ aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, dataView, []], + null, + mockConvertToPercentileColumn, + ], + [ + 'null if supported agg SINGLE_PERCENTILE is not valid', + [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, dataView, []], + null, + mockConvertToPercentileColumn, + ], + [ + 'null if supported agg PERCENTILE_RANKS is not valid', + [{ aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, dataView, []], + null, + mockConvertToPercentileRankColumn, + ], + [ + 'null if supported agg SINGLE_PERCENTILE_RANK is not valid', + [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, dataView, []], + null, + mockConvertToPercentileRankColumn, + ], + [ + 'null if supported agg TOP_HITS is not valid', + [{ aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, dataView, []], + null, + mockConvertToLastValueColumn, + ], + [ + 'null if supported agg TOP_METRICS is not valid', + [{ aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, dataView, []], + null, + mockConvertToLastValueColumn, + ], + [ + 'null if supported agg CUMULATIVE_SUM is not valid', + [{ aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, dataView, []], + null, + mockConvertToCumulativeSumAggColumn, + ], + [ + 'null if supported agg DERIVATIVE is not valid', + [{ aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, dataView, []], + null, + mockConvertToOtherParentPipelineAggColumns, + ], + [ + 'null if supported agg MOVING_FN is not valid', + [{ aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, dataView, []], + null, + mockConvertToOtherParentPipelineAggColumns, + ], + [ + 'null if supported agg SUM_BUCKET is not valid', + [{ aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, dataView, []], + null, + mockConvertToSiblingPipelineColumns, + ], + [ + 'null if supported agg MIN_BUCKET is not valid', + [{ aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, dataView, []], + null, + mockConvertToSiblingPipelineColumns, + ], + [ + 'null if supported agg MAX_BUCKET is not valid', + [{ aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, dataView, []], + null, + mockConvertToSiblingPipelineColumns, + ], + [ + 'null if supported agg AVG_BUCKET is not valid', + [{ aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, dataView, []], + null, + mockConvertToSiblingPipelineColumns, + ], + [ + 'null if supported agg SERIAL_DIFF is not valid', + [{ aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, dataView, []], + null, + undefined, + ], + ])('should return %s', (_, input, expected, mock) => { + expect(convertMetricToColumns(...input)).toBeNull(); + + if (mock) { + expect(mock).toBeCalledTimes(1); + } + }); +}); +describe('convertMetricToColumns valid cases', () => { + const dataView = stubLogstashDataView; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const result = [{}]; + + beforeAll(() => { + mockConvertMetricAggregationColumnWithoutSpecialParams.mockReturnValue(result); + mockConvertToOtherParentPipelineAggColumns.mockReturnValue(result); + mockConvertToPercentileColumn.mockReturnValue(result); + mockConvertToPercentileRankColumn.mockReturnValue(result); + mockConvertToSiblingPipelineColumns.mockReturnValue(result); + mockConvertToStdDeviationFormulaColumns.mockReturnValue(result); + mockConvertToLastValueColumn.mockReturnValue(result); + mockConvertToCumulativeSumAggColumn.mockReturnValue(result); + }); + + test.each<[string, Parameters, Array<{}>, jest.Mock]>([ + [ + 'array of columns if supported agg AVG is valid', + [{ aggType: METRIC_TYPES.AVG } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg MIN is valid', + [{ aggType: METRIC_TYPES.MIN } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg MAX is valid', + [{ aggType: METRIC_TYPES.MAX } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg SUM is valid', + [{ aggType: METRIC_TYPES.SUM } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg COUNT is valid', + [{ aggType: METRIC_TYPES.COUNT } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg CARDINALITY is valid', + [{ aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg VALUE_COUNT is valid', + [{ aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg MEDIAN is valid', + [{ aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, dataView, []], + result, + mockConvertMetricAggregationColumnWithoutSpecialParams, + ], + [ + 'array of columns if supported agg STD_DEV is valid', + [{ aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, dataView, []], + result, + mockConvertToStdDeviationFormulaColumns, + ], + [ + 'array of columns if supported agg PERCENTILES is valid', + [{ aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, dataView, []], + result, + mockConvertToPercentileColumn, + ], + [ + 'array of columns if supported agg SINGLE_PERCENTILE is valid', + [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, dataView, []], + result, + mockConvertToPercentileColumn, + ], + [ + 'array of columns if supported agg PERCENTILE_RANKS is valid', + [{ aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, dataView, []], + result, + mockConvertToPercentileRankColumn, + ], + [ + 'array of columns if supported agg SINGLE_PERCENTILE_RANK is valid', + [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, dataView, []], + result, + mockConvertToPercentileRankColumn, + ], + [ + 'array of columns if supported agg TOP_HITS is valid', + [{ aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, dataView, []], + result, + mockConvertToLastValueColumn, + ], + [ + 'array of columns if supported agg TOP_METRICS is valid', + [{ aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, dataView, []], + result, + mockConvertToLastValueColumn, + ], + [ + 'array of columns if supported agg CUMULATIVE_SUM is valid', + [{ aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, dataView, []], + result, + mockConvertToCumulativeSumAggColumn, + ], + [ + 'array of columns if supported agg DERIVATIVE is valid', + [{ aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, dataView, []], + result, + mockConvertToOtherParentPipelineAggColumns, + ], + [ + 'array of columns if supported agg MOVING_FN is valid', + [{ aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, dataView, []], + result, + mockConvertToOtherParentPipelineAggColumns, + ], + [ + 'array of columns if supported agg SUM_BUCKET is valid', + [{ aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, dataView, []], + result, + mockConvertToSiblingPipelineColumns, + ], + [ + 'array of columns if supported agg MIN_BUCKET is valid', + [{ aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, dataView, []], + result, + mockConvertToSiblingPipelineColumns, + ], + [ + 'array of columns if supported agg MAX_BUCKET is valid', + [{ aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, dataView, []], + result, + mockConvertToSiblingPipelineColumns, + ], + [ + 'array of columns if supported agg AVG_BUCKET is valid', + [{ aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, dataView, []], + result, + mockConvertToSiblingPipelineColumns, + ], + ])('should return %s', (_, input, expected, mock) => { + expect(convertMetricToColumns(...input)).toEqual(expected.map(expect.objectContaining)); + if (mock) { + expect(mock).toBeCalledTimes(1); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts new file mode 100644 index 0000000000000..9855ce44b6602 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { getPercentageColumnFormulaColumn } from './percentage_formula'; +import { FormulaColumn } from '../../types'; +import { SchemaConfig } from '../../..'; + +const mockGetFormulaForAgg = jest.fn(); +const mockCreateFormulaColumn = jest.fn(); + +jest.mock('./formula', () => ({ + getFormulaForAgg: jest.fn(() => mockGetFormulaForAgg()), +})); + +jest.mock('../convert', () => ({ + createFormulaColumn: jest.fn((formula) => mockCreateFormulaColumn(formula)), +})); + +describe('getPercentageColumnFormulaColumn', () => { + const dataView = stubLogstashDataView; + const field = stubLogstashDataView.fields[0].name; + const aggs: Array> = [ + { + aggId: '1', + aggType: METRIC_TYPES.AVG, + aggParams: { field }, + accessor: 0, + params: {}, + label: 'average', + format: {}, + }, + ]; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each< + [ + string, + Parameters, + () => void, + Partial | null + ] + >([ + [ + 'null if cannot build formula for provided agg', + [{ agg: aggs[0], aggs, dataView }], + () => { + mockGetFormulaForAgg.mockReturnValue(null); + }, + null, + ], + [ + 'null if cannot create formula column for provided arguments', + [{ agg: aggs[0], aggs, dataView }], + () => { + mockGetFormulaForAgg.mockReturnValue('test-formula'); + mockCreateFormulaColumn.mockReturnValue(null); + }, + null, + ], + [ + 'formula column if provided arguments are valid', + [{ agg: aggs[0], aggs, dataView }], + () => { + mockGetFormulaForAgg.mockReturnValue('test-formula'); + mockCreateFormulaColumn.mockImplementation((formula) => ({ + operationType: 'formula', + params: { formula }, + label: 'Average', + })); + }, + { + operationType: 'formula', + params: { + formula: `(test-formula) / overall_sum(test-formula)`, + format: { id: 'percent' }, + }, + label: `Average percentages`, + }, + ], + ])('should return %s', (_, input, actions, expected) => { + actions(); + if (expected === null) { + expect(getPercentageColumnFormulaColumn(...input)).toBeNull(); + } else { + expect(getPercentageColumnFormulaColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts new file mode 100644 index 0000000000000..73118b6ad4f03 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts @@ -0,0 +1,521 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { AggBasedColumn, ColumnWithMeta, Operations } from '../..'; +import { SchemaConfig } from '../../types'; +import { + getCustomBucketsFromSiblingAggs, + getFieldNameFromField, + getLabel, + getLabelForPercentile, + getMetricFromParentPipelineAgg, + getValidColumns, + isColumnWithMeta, + isMetricAggWithoutParams, + isPercentileAgg, + isPercentileRankAgg, + isPipeline, + isSchemaConfig, + isSiblingPipeline, + isStdDevAgg, +} from './utils'; + +describe('getLabel', () => { + const label = 'some label'; + const customLabel = 'some custom label'; + + const agg: SchemaConfig = { + accessor: 0, + label, + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggId: 'id', + aggParams: { field: 'some-field' }, + }; + + test('should return label', () => { + const { aggParams, ...aggWithoutAggParams } = agg; + expect(getLabel(aggWithoutAggParams)).toEqual(label); + expect(getLabel(agg)).toEqual(label); + expect(getLabel({ ...agg, aggParams: { ...aggParams!, customLabel: undefined } })).toEqual( + label + ); + }); + + test('should return customLabel', () => { + const aggParams = { ...agg.aggParams!, customLabel }; + const aggWithCustomLabel = { ...agg, aggParams }; + expect(getLabel(aggWithCustomLabel)).toEqual(customLabel); + }); +}); + +describe('getLabelForPercentile', () => { + const label = 'some label'; + const customLabel = 'some custom label'; + + const agg: SchemaConfig = { + accessor: 0, + label, + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.PERCENTILES, + aggId: 'id', + aggParams: { field: 'some-field' }, + }; + + test('should return empty string if no custom label is specified', () => { + const { aggParams, ...aggWithoutAggParams } = agg; + expect(getLabelForPercentile(aggWithoutAggParams)).toEqual(''); + expect(getLabel({ ...agg, aggParams: { ...aggParams!, customLabel: '' } })).toEqual(''); + }); + + test('should return label if custom label is specified', () => { + const aggParams = { ...agg.aggParams!, customLabel }; + const aggWithCustomLabel = { ...agg, aggParams }; + expect(getLabelForPercentile(aggWithCustomLabel)).toEqual(label); + }); +}); + +describe('getValidColumns', () => { + const dataView = stubLogstashDataView; + const columns: AggBasedColumn[] = [ + { + operationType: Operations.AVERAGE, + sourceField: dataView.fields[0].name, + columnId: 'some-id-0', + dataType: 'number', + params: {}, + meta: { aggId: 'aggId-0' }, + isSplit: false, + isBucketed: true, + }, + { + operationType: Operations.SUM, + sourceField: dataView.fields[0].name, + columnId: 'some-id-1', + dataType: 'number', + params: {}, + meta: { aggId: 'aggId-1' }, + isSplit: false, + isBucketed: true, + }, + ]; + test.each<[string, Parameters, AggBasedColumn[] | null]>([ + ['null if array contains null', [[null, ...columns]], null], + ['null if columns is null', [null], null], + ['null if columns is undefined', [undefined], null], + ['columns', [columns], columns], + ['columns if one column is passed', [columns[0]], [columns[0]]], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(getValidColumns(...input)).toBeNull(); + } else { + expect(getValidColumns(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); + +describe('getFieldNameFromField', () => { + test('should return null if no field is passed', () => { + expect(getFieldNameFromField(undefined)).toBeNull(); + }); + + test('should return field name if field is string', () => { + const fieldName = 'some-field-name'; + expect(getFieldNameFromField(fieldName)).toEqual(fieldName); + }); + + test('should return field name if field is DataViewField', () => { + const field = stubLogstashDataView.fields[0]; + expect(getFieldNameFromField(field)).toEqual(field.name); + }); +}); + +describe('isSchemaConfig', () => { + const iAggConfig = { + id: '', + enabled: false, + params: {}, + } as IAggConfig; + + const schemaConfig: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + }; + + test('should be false if is IAggConfig', () => { + expect(isSchemaConfig(iAggConfig)).toBeFalsy(); + }); + + test('should be false if is SchemaConfig', () => { + expect(isSchemaConfig(schemaConfig)).toBeTruthy(); + }); +}); + +describe('isColumnWithMeta', () => { + const column: AggBasedColumn = { + sourceField: '', + columnId: '', + operationType: 'terms', + isBucketed: false, + isSplit: false, + dataType: 'string', + } as AggBasedColumn; + + const columnWithMeta: ColumnWithMeta = { + sourceField: '', + columnId: '', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'some-agg-id' }, + }; + + test('should return false if column without meta', () => { + expect(isColumnWithMeta(column)).toBeFalsy(); + }); + + test('should return true if column with meta', () => { + expect(isColumnWithMeta(columnWithMeta)).toBeTruthy(); + }); +}); + +describe('isSiblingPipeline', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.AVG_BUCKET, true], + [METRIC_TYPES.SUM_BUCKET, true], + [METRIC_TYPES.MAX_BUCKET, true], + [METRIC_TYPES.MIN_BUCKET, true], + [METRIC_TYPES.CUMULATIVE_SUM, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isSiblingPipeline({ ...metric, aggType } as SchemaConfig)).toBe( + expected + ); + }); +}); + +describe('isPipeline', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.AVG_BUCKET, true], + [METRIC_TYPES.SUM_BUCKET, true], + [METRIC_TYPES.MAX_BUCKET, true], + [METRIC_TYPES.MIN_BUCKET, true], + [METRIC_TYPES.CUMULATIVE_SUM, true], + [METRIC_TYPES.DERIVATIVE, true], + [METRIC_TYPES.MOVING_FN, true], + [METRIC_TYPES.AVG, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isPipeline({ ...metric, aggType } as SchemaConfig)).toBe(expected); + }); +}); + +describe('isMetricAggWithoutParams', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.AVG, true], + [METRIC_TYPES.COUNT, true], + [METRIC_TYPES.MAX, true], + [METRIC_TYPES.MIN, true], + [METRIC_TYPES.SUM, true], + [METRIC_TYPES.MEDIAN, true], + [METRIC_TYPES.CARDINALITY, true], + [METRIC_TYPES.VALUE_COUNT, true], + [METRIC_TYPES.DERIVATIVE, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isMetricAggWithoutParams({ ...metric, aggType } as SchemaConfig)).toBe( + expected + ); + }); +}); + +describe('isPercentileAgg', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.PERCENTILES, true], + [METRIC_TYPES.DERIVATIVE, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isPercentileAgg({ ...metric, aggType } as SchemaConfig)).toBe(expected); + }); +}); + +describe('isPercentileRankAgg', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.PERCENTILE_RANKS, true], + [METRIC_TYPES.PERCENTILES, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isPercentileRankAgg({ ...metric, aggType } as SchemaConfig)).toBe( + expected + ); + }); +}); + +describe('isStdDevAgg', () => { + const metric: Omit = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + + test.each<[METRIC_TYPES, boolean]>([ + [METRIC_TYPES.STD_DEV, true], + [METRIC_TYPES.PERCENTILES, false], + ])('for %s should return %s', (aggType, expected) => { + expect(isStdDevAgg({ ...metric, aggType } as SchemaConfig)).toBe(expected); + }); +}); + +describe('getCustomBucketsFromSiblingAggs', () => { + const bucket1 = { + id: 'some-id', + params: { type: 'some-type' }, + type: 'type1', + enabled: true, + } as unknown as IAggConfig; + const serialize1 = () => bucket1; + + const bucket2 = { + id: 'some-id-1', + params: { type: 'some-type-1' }, + type: 'type2', + enabled: false, + } as unknown as IAggConfig; + const serialize2 = () => bucket2; + + const bucketWithSerialize1 = { ...bucket1, serialize: serialize1 } as unknown as IAggConfig; + const metric1: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + aggId: 'some-agg-id', + aggParams: { + customBucket: bucketWithSerialize1, + }, + }; + + const bucketWithSerialize2 = { ...bucket2, serialize: serialize2 } as unknown as IAggConfig; + const metric2: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + aggId: 'some-agg-id', + aggParams: { + customBucket: bucketWithSerialize2, + }, + }; + const bucket3 = { ...bucket1, id: 'other id' } as unknown as IAggConfig; + const serialize3 = () => bucket3; + + const bucketWithSerialize3 = { ...bucket3, serialize: serialize3 } as unknown as IAggConfig; + const metric3: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + aggId: 'some-agg-id', + aggParams: { + customBucket: bucketWithSerialize3, + }, + }; + + test("should filter out duplicated custom buckets, ignoring id's", () => { + expect(getCustomBucketsFromSiblingAggs([metric1, metric2, metric3])).toEqual([ + bucketWithSerialize1, + bucketWithSerialize2, + ]); + }); +}); + +const mockConvertToSchemaConfig = jest.fn(); + +jest.mock('../../vis_schemas', () => ({ + convertToSchemaConfig: jest.fn(() => mockConvertToSchemaConfig()), +})); + +describe('getMetricFromParentPipelineAgg', () => { + const metricAggId = 'agg-id-0'; + const aggId = 'agg-id-1'; + const plainAgg: SchemaConfig = { + accessor: 0, + label: 'some-label', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggId: metricAggId, + }; + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG_BUCKET, + aggParams: { customMetric: {} as IAggConfig }, + aggId, + }; + + const parentPipelineAgg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.CUMULATIVE_SUM, + aggParams: { metricAgg: 'custom' }, + aggId, + }; + + const metric = { aggType: METRIC_TYPES.CUMULATIVE_SUM }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(() => { + mockConvertToSchemaConfig.mockReturnValue(metric); + }); + + test('should return null if aggParams are undefined', () => { + expect(getMetricFromParentPipelineAgg({ ...agg, aggParams: undefined }, [])).toBeNull(); + expect(mockConvertToSchemaConfig).toBeCalledTimes(0); + }); + + test('should return null if is sibling pipeline agg and custom metric is not defined', () => { + expect( + getMetricFromParentPipelineAgg({ ...agg, aggParams: { customMetric: undefined } }, []) + ).toBeNull(); + expect(mockConvertToSchemaConfig).toBeCalledTimes(0); + }); + + test('should return null if is parent pipeline agg, metricAgg is custom and custom metric is not defined', () => { + expect(getMetricFromParentPipelineAgg(parentPipelineAgg, [])).toBeNull(); + expect(mockConvertToSchemaConfig).toBeCalledTimes(0); + }); + + test('should return metric if is parent pipeline agg, metricAgg is equal to aggId and custom metric is not defined', () => { + const parentPipelineAggWithLink = { + ...parentPipelineAgg, + aggParams: { + metricAgg: metricAggId, + }, + }; + expect( + getMetricFromParentPipelineAgg(parentPipelineAggWithLink, [ + parentPipelineAggWithLink, + plainAgg, + ]) + ).toEqual(plainAgg); + expect(mockConvertToSchemaConfig).toBeCalledTimes(0); + }); + + test('should return metric if sibling pipeline agg with custom metric', () => { + expect(getMetricFromParentPipelineAgg(agg, [agg])).toEqual(metric); + expect(mockConvertToSchemaConfig).toBeCalledTimes(1); + }); + + test('should return metric if parent pipeline agg with custom metric', () => { + expect( + getMetricFromParentPipelineAgg( + { + ...parentPipelineAgg, + aggParams: { ...parentPipelineAgg.aggParams, customMetric: {} as IAggConfig }, + }, + [agg] + ) + ).toEqual(metric); + expect(mockConvertToSchemaConfig).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts index 39920e525b791..c4e5c5474bf0c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts @@ -150,7 +150,7 @@ export const isStdDevAgg = (metric: SchemaConfig): metric is SchemaConfig { +export const getCustomBucketsFromSiblingAggs = (metrics: SchemaConfig[]) => { return metrics.reduce((acc, metric) => { if ( isSiblingPipeline(metric) && diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index dccd579d95dba..5b8b7832730b9 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -40,7 +40,7 @@ jest.mock('../../common/convert_to_lens/lib/buckets', () => ({ })); jest.mock('../../common/convert_to_lens/lib/utils', () => ({ - getCutomBucketsFromSiblingAggs: jest.fn(() => mockGetCutomBucketsFromSiblingAggs()), + getCustomBucketsFromSiblingAggs: jest.fn(() => mockGetCutomBucketsFromSiblingAggs()), })); jest.mock('../vis_schemas', () => ({ diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index e9b467074b6f1..56108b1a1d63f 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -11,7 +11,7 @@ import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; import { AggBasedColumn, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; -import { getCutomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; +import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; import type { Vis } from '../types'; import { getVisSchemas, Schemas } from '../vis_schemas'; import { @@ -57,7 +57,7 @@ export const getColumnsFromVis = ( return null; } - const customBuckets = getCutomBucketsFromSiblingAggs(visSchemas.metric); + const customBuckets = getCustomBucketsFromSiblingAggs(visSchemas.metric); // doesn't support sibbling pipeline aggs with different bucket aggs if (customBuckets.length > 1) { From 384ecc4b7ba272b9f890ba102277d0769a730067 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 29 Sep 2022 12:43:47 +0200 Subject: [PATCH 169/172] [Graph] Unskip a11y functional test (#141798) --- .../public/components/settings/url_template_form.tsx | 10 +++++++++- .../workspace_layout/workspace_top_nav_menu.tsx | 3 +++ x-pack/test/accessibility/apps/graph.ts | 7 +++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx index 57e69d0912a7f..4509a003e839d 100644 --- a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx @@ -261,7 +261,15 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { defaultMessage: 'Toolbar icon', })} > -
    +
    {urlTemplateIconChoices.map((icon) => ( { ownFocus: true, className: 'gphSettingsFlyout', maxWidth: 520, + 'aria-label': i18n.translate('xpack.graph.settings.ariaLabel', { + defaultMessage: 'Settings', + }), } ); }, diff --git a/x-pack/test/accessibility/apps/graph.ts b/x-pack/test/accessibility/apps/graph.ts index 85a77f4816273..03ca3b2afbfe4 100644 --- a/x-pack/test/accessibility/apps/graph.ts +++ b/x-pack/test/accessibility/apps/graph.ts @@ -79,12 +79,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.pressKeys(browser.keys.ESCAPE); }); - // https://github.com/elastic/kibana/issues/134693 - it.skip('Graph settings drilldown tab - add new drilldown', async function () { + it('Graph settings drilldown tab - add new drilldown', async function () { + await testSubjects.click('graphSettingsButton'); + await testSubjects.click('drillDowns'); await testSubjects.click('graphAddNewTemplate'); await a11y.testAppSnapshot(); - await testSubjects.click('graphRemoveUrlTemplate'); - await testSubjects.click('euiFlyoutCloseButton'); await browser.pressKeys(browser.keys.ESCAPE); }); From 57a3162402b54262bb5f730a36082b42535b1fbf Mon Sep 17 00:00:00 2001 From: Luke Gmys Date: Thu, 29 Sep 2022 13:35:59 +0200 Subject: [PATCH 170/172] [TIP] Fix broken setup instructions in the readme (#140519) --- x-pack/plugins/threat_intelligence/FAQ.md | 15 ---- x-pack/plugins/threat_intelligence/README.md | 73 +++++++++++++++----- 2 files changed, 56 insertions(+), 32 deletions(-) delete mode 100644 x-pack/plugins/threat_intelligence/FAQ.md diff --git a/x-pack/plugins/threat_intelligence/FAQ.md b/x-pack/plugins/threat_intelligence/FAQ.md deleted file mode 100644 index d3f1287713840..0000000000000 --- a/x-pack/plugins/threat_intelligence/FAQ.md +++ /dev/null @@ -1,15 +0,0 @@ -# FAQ - -### Where can I find the UI for the Threat Intelligence plugin? - -Kibana recommends working on a fork of the [elastic/kibana repository](https://github.com/elastic/kibana) (see [here](https://docs.github.com/en/get-started/quickstart/fork-a-repo) to learn about forks). - -### How is the Threat Intelligence code loaded in Kibana? - -The Threat Intelligence plugin is loaded within the [security_solution](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution) plugin. - -### I'm not seeing any data in the Indicators' table - -See this [documentation here](https://github.com/elastic/security-team/blob/main/docs/protections-team/threat-intelligence-services/protections-experience/development-setup.mdx) to get Threat Intelligence feed in Kibana. - -Once you have the feed running, go to `Management > Advanced Settings > Threat indices` and add `filebeat-*` to the list (comma separated). \ No newline at end of file diff --git a/x-pack/plugins/threat_intelligence/README.md b/x-pack/plugins/threat_intelligence/README.md index 945ab9b85a4f1..7395ca0df8a70 100755 --- a/x-pack/plugins/threat_intelligence/README.md +++ b/x-pack/plugins/threat_intelligence/README.md @@ -6,28 +6,55 @@ Elastic Threat Intelligence makes it easy to analyze and investigate potential s The Threat Intelligence UI is displayed in Kibana Security, under the Explore section. -## Quick Start +## Development setup -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. +### Kibana development in general -Verify your node version [here](https://github.com/elastic/kibana/blob/main/.node-version). +Best source - [internal Kibana docs](https://docs.elastic.dev/kibana-dev-docs/getting-started/welcome). If you have any issues with setting up your Kibana dev environment [#kibana](https://elastic.slack.com/archives/C0D8P2XK5) Slack channel is a good way to get help. -**Run ES:** +### Essential `kibana.yml` settings -`yarn es snapshot --license trial` +You can make a copy of `kibana.yml` file into `kibana.dev.yml` and make adjustments to the settings. External documentation on the flags available is [here](https://www.elastic.co/guide/en/kibana/current/settings.html) -**Run Kibana:** +It is recommended to set `server.basePath: "/kbn"` to make you local instance persist the base Kibana path. If you don't do it, the base path will be a random string every time you start Kibana. Any other value than `/kbn` will also work. -> **Important:** -> -> See here to get your `kibana.yaml` to enable the Threat Intelligence plugin. +### Getting Threat Intelligence feeds data into Kibana -``` -yarn kbn reset && yarn kbn bootstrap -yarn start --no-base-path -``` +There are many ways to get data for you local development. We first focus on getting Threat Intelligence data specifically. + +### Setting up filebeat threatintel integrations locally + +1. install [mage](https://github.com/magefile/mage). It is a Go build tool used to build `beats`. Installation from the sources requires Go lang set up. A simpler option might be to install it from a package manager available in your system (eg. `brew` on MacOs) or use their [binary distribution](https://github.com/magefile/mage/releases) +1. start Elasticsearch and Kibana +1. clone [beats](https://github.com/elastic/beats) repository +1. inside beats repository, update `x-pack/filebeat/filebeat.yml` with your local Elasticsearch and Kibana connection configs + + ``` + output.elasticsearch: + hosts: ["localhost:9200"] + username: "elastic" + password: "changeme" + + setup.kibana: + host: "localhost:5601" // make sure to run Kibana with --no-base-path option or specify server.basePath in Kibana config and use it here as a path, eg. localhost:5601/kbn + ``` + +1. go into `x-pack/filebeat` (that's where security related modules live) +1. build filebeat `mage build` +1. enable `threatintel` module by running `./filebeat modules enable threatintel` +1. enable specific Threat Intelligence integrations by updating `modules.d/threatintel.yml`. Update `enable` to `true` in every integration you want to enable and configs specific for these integrations. The bare minimum is to enable Abuse.CH feeds `abuseurl`, `abusemalware` and `malwarebazaar`. +1. run `./filebeat setup -E setup.dashboards.directory=build/kibana` to set up predefined dashboards +1. run `./filebeat -e` to start filebeat +1. to validate that the set up works, wait for some Threat Intel data to be ingested and then go in Analytics > Discover in your local Kibana to search `event.category : threat and event.type : indicator`. You should see some documents returned by this search. Abuse.CH feeds are up to date so you should see the results from the last 7 days. + +### More ways to get data -### Performance +There are many more tools available for getting the data for testing or local development, depending on the data type and usecase. + +- Kibana development docs > [Add data](https://docs.elastic.dev/kibana-dev-docs/getting-started/sample-data) +- [Dev/Design/Testing Environments and Frameworks](https://docs.google.com/document/d/1DGCcLMnVKQ_STlkbS4E0m4kbPivNtR8iMlg_IoCuCEw/edit#) gathered by Security Engineering Productivity team + +### Generate fixtures for local testing You can generate large volumes of threat indicators on demand with the following script: @@ -37,17 +64,29 @@ node scripts/generate_indicators.js see the file in order to adjust the amount of indicators generated. The default is one million. -### Useful hints +## Data for E2E tests -Export local instance data to es_archives (will be loaded in cypress tests). +Use es_archives to export data for e2e testing purposes, like so: ``` TEST_ES_PORT=9200 node scripts/es_archiver save x-pack/test/threat_intelligence_cypress/es_archives/threat_intelligence "logs-ti*" ``` +These can be loaded at will with `x-pack/plugins/threat_intelligence/cypress/tasks/es_archiver.ts` task. + +You can use this approach to load separate data dumps for every test case, to cover all critical scenarios. + ## FAQ -See [FAQ.md](https://github.com/elastic/kibana/blob/main/x-pack/plugins/threat_intelligence/FAQ.md) for questions you may have. +### How is the Threat Intelligence code loaded in Kibana? + +The Threat Intelligence plugin is loaded lazily within the [security_solution](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution) plugin, +from `x-pack/plugins/security_solution/public/threat_intelligence` owned by the Protections Experience Team. + +## QA and demo for implemented features + +One way to QA and demo the feature merged into `main` branch is to run the latest `main` locally. +Another option is to deploy a Staging instance. For Staging environment snapshots are being build every night with the latest state of the `main` branch. More documentation can be found [here](https://cloud.elastic.dev/environments/Staging/#automatic-termination-of-staging-deployments) ## Contributing From 16ca2d28957a833cf78abae97609a3f5a1dfd5c0 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 29 Sep 2022 07:53:50 -0400 Subject: [PATCH 171/172] [Event Log] Adding event log schema check to CI checks (#142104) * Adding event log check to CI check. * Adding event log check to CI check. * Can I check out ECS * Checking out specific ECS branch * Checking out specific ECS branch * Custom error message * Reverting event log mapping test changes * Pinning to 1.8 * Update .buildkite/scripts/steps/checks/event_log.sh Co-authored-by: Jonathan Budzenski Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jonathan Budzenski --- .buildkite/scripts/common/util.sh | 7 ++++++- .buildkite/scripts/steps/checks.sh | 1 + .buildkite/scripts/steps/checks/event_log.sh | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100755 .buildkite/scripts/steps/checks/event_log.sh diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index 1ce05856ec6b7..748babfc0650b 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -39,6 +39,7 @@ check_for_changed_files() { C_RESET='\033[0m' # Reset color SHOULD_AUTO_COMMIT_CHANGES="${2:-}" + CUSTOM_FIX_MESSAGE="${3:-}" GIT_CHANGES="$(git ls-files --modified -- . ':!:.bazelrc')" if [ "$GIT_CHANGES" ]; then @@ -75,7 +76,11 @@ check_for_changed_files() { else echo -e "\n${RED}ERROR: '$1' caused changes to the following files:${C_RESET}\n" echo -e "$GIT_CHANGES\n" - echo -e "\n${YELLOW}TO FIX: Run '$1' locally, commit the changes and push to your branch${C_RESET}\n" + if [ "$CUSTOM_FIX_MESSAGE" ]; then + echo "$CUSTOM_FIX_MESSAGE" + else + echo -e "\n${YELLOW}TO FIX: Run '$1' locally, commit the changes and push to your branch${C_RESET}\n" + fi exit 1 fi fi diff --git a/.buildkite/scripts/steps/checks.sh b/.buildkite/scripts/steps/checks.sh index 4af63d318c804..0e11ac04eea1d 100755 --- a/.buildkite/scripts/steps/checks.sh +++ b/.buildkite/scripts/steps/checks.sh @@ -8,6 +8,7 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/steps/checks/precommit_hook.sh .buildkite/scripts/steps/checks/ftr_configs.sh .buildkite/scripts/steps/checks/bazel_packages.sh +.buildkite/scripts/steps/checks/event_log.sh .buildkite/scripts/steps/checks/telemetry.sh .buildkite/scripts/steps/checks/ts_projects.sh .buildkite/scripts/steps/checks/jest_configs.sh diff --git a/.buildkite/scripts/steps/checks/event_log.sh b/.buildkite/scripts/steps/checks/event_log.sh new file mode 100755 index 0000000000000..dc9c01902c010 --- /dev/null +++ b/.buildkite/scripts/steps/checks/event_log.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +echo --- Check Event Log Schema + +# event log schema is pinned to a specific version of ECS +ECS_STABLE_VERSION=1.8 +git clone --depth 1 -b $ECS_STABLE_VERSION https://github.com/elastic/ecs.git ../ecs + +node x-pack/plugins/event_log/scripts/create_schemas.js + +check_for_changed_files 'node x-pack/plugins/event_log/scripts/create_schemas.js' false 'Follow the directions in x-pack/plugins/event_log/generated/README.md to make schema changes for the event log.' From 13824dd0ddbb88bcbd8995fab85ee325ffa6ae4f Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:57:16 +0200 Subject: [PATCH 172/172] added retry_on_conflict and report errors from bulk agent update (#142088) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/server/services/agents/crud.ts | 1 + .../server/services/agents/reassign.test.ts | 25 +++++++++++++++++++ .../services/agents/reassign_action_runner.ts | 8 +++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 305bf2b6bacb4..55a244664238b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -405,6 +405,7 @@ export async function bulkUpdateAgents( { update: { _id: agentId, + retry_on_conflict: 3, }, }, { diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index a54c2cb56c944..3fb9d2ee1f33b 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -96,4 +96,29 @@ describe('reassignAgents (plural)', () => { }); expect(calledWithActionResults.body?.[1] as any).toEqual(expectedObject); }); + + it('should report errors from ES agent update call', async () => { + const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO2 } = createClientMock(); + esClient.bulk.mockResponse({ + items: [ + { + update: { + _id: agentInRegularDoc._id, + error: new Error('version conflict'), + }, + }, + ], + } as any); + const idsToReassign = [agentInRegularDoc._id]; + await reassignAgents(soClient, esClient, { agentIds: idsToReassign }, regularAgentPolicySO2.id); + + const calledWithActionResults = esClient.bulk.mock.calls[1][0] as estypes.BulkRequest; + const expectedObject = expect.objectContaining({ + '@timestamp': expect.anything(), + action_id: expect.anything(), + agent_id: agentInRegularDoc._id, + error: 'version conflict', + }); + expect(calledWithActionResults.body?.[1] as any).toEqual(expectedObject); + }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index 55c0e00728d16..96405e464b358 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -72,7 +72,7 @@ export async function reassignBatch( throw new AgentReassignmentError('No agents to reassign, already assigned or hosted agents'); } - await bulkUpdateAgents( + const res = await bulkUpdateAgents( esClient, agentsToUpdate.map((agent) => ({ agentId: agent.id, @@ -83,6 +83,12 @@ export async function reassignBatch( })) ); + res.items + .filter((item) => !item.success) + .forEach((item) => { + errors[item.id] = item.error!; + }); + const actionId = options.actionId ?? uuid(); const errorCount = Object.keys(errors).length; const total = options.total ?? agentsToUpdate.length + errorCount;

    rk!lwQj$yx+= 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