From 010ee2e112ac80833bf5cab06c16d4457e91b55f Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Mon, 20 Mar 2023 14:31:02 +0100 Subject: [PATCH] ESLint Telemetry Rule (#153108) Resolves https://github.com/elastic/kibana/issues/144887 ## Summary This PR adds an ESLint Plugin which checks specific `Eui` elements for the existence of a `data-test-subj` prop. This rule will make having one for these elements required. This rule is currently only enabled for Observability apps (APM, Infra, Observability, Synthetics, Uptime). The plugin is also able to generate a suggestion based on the context in which the element is used. In the IDE this suggestion can be applied by using the autofix capability (see video below). When opening a PR, the CI will automatically apply the suggestion to qualifying Eui elements in the branch. https://user-images.githubusercontent.com/535564/225449622-bbfccb40-fdd2-4f69-9d5a-7d5a97bf62e6.mov ## Why do this? There is an increased push to move towards data driven feature development. In order to facilitate this, we need to have an increased focus on instrumenting user event generating elements in the Kibana codebase. This linting rule is an attempt to nudge Kibana engineers to not forget to add this property when writing frontend code. It also saves a bit of work for engineers by suggesting a value for the `data-test-subj` based on the location of the file in the codebase and any potential default values that might be present in the JSX node tree. Finally, because the suggestion is always of the same form, it can increase the consistency in the values given to these elements. ## Shape of the suggestion The suggestion for the value of data-test-subj is of the form: `[app][componentName][intent][euiElementName]`. For example, when working in a component in the location: `x-pack/plugins/observability/public/pages/overview/containers/overview_page/header_actions.tsx`, and having the code: ``` function HeaderActions() { return ( {i18n.translate('id', { defaultMessage: 'Submit Form' })} ) } ``` the suggestion becomes: `data-test-subj=o11yHeaderActionsSubmitFormButton`. For elements that don't take a `defaultMessage` prop / translation, the suggestion takes the form: `[app][componentName][euiElementName]` ## Which elements are checked by the ESLint rule? In its current iteration the rule checks these Eui elements: * `EuiButton` * `EuiButtonEmpty` * `EuiLink` * `EuiFieldText` * `EuiFieldSearch` * `EuiFieldNumber` * `EuiSelect` * `EuiRadioGroup` * 'EuiTextArea` ## What types of prop setting does this rule support? * `` (direct prop) * `` (via spreaded object; rule checks for `data-test-subj` key in object) ## What types of function declarations does this rule support? * `function Foo(){}` (Named function) * `const Foo = () => {}` (Arrow function assigned to variable) * `const Foo = memo(() => {})` (Arrow function assigned to variable wrapped in function) * `const Foo = hoc(uponHoc(uponHoc(() => {})))` (Arrow function assigned to variable wrapped in infinite levels of functions) ## Things to note * If an element already has a value for `data-test-subj` the rule will not kick in as any existing instrumentation might depend on the value. * the auto suggestion is just a suggestion: the engineer can always adjust the value for a `data-test-subj` before or after committing. Once a value is present (autofixed or manually set) the rule will not kick in. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dario Gieselaar Co-authored-by: Katerina Patticha Co-authored-by: Tiago Costa --- .eslintrc.js | 12 + .github/CODEOWNERS | 1 + package.json | 1 + packages/kbn-eslint-config/.eslintrc.js | 299 ++++++++---------- .../kbn-eslint-plugin-telemetry/README.mdx | 13 + ...k_node_for_existing_data_test_subj_prop.ts | 47 +++ .../helpers/get_app_name.test.ts | 28 ++ .../helpers/get_app_name.ts | 49 +++ .../helpers/get_function_name.ts | 34 ++ .../helpers/get_intent_from_node.ts | 133 ++++++++ packages/kbn-eslint-plugin-telemetry/index.ts | 17 + .../jest.config.js | 13 + .../kbn-eslint-plugin-telemetry/kibana.jsonc | 6 + .../kbn-eslint-plugin-telemetry/package.json | 6 + ...ng_elements_should_be_instrumented.test.ts | 71 +++++ ...erating_elements_should_be_instrumented.ts | 92 ++++++ .../kbn-eslint-plugin-telemetry/tsconfig.json | 11 + tsconfig.base.json | 2 + .../transaction_duration_rule_type/index.tsx | 1 + .../components/alerting/utils/fields.tsx | 1 + .../app/correlations/progress_controls.tsx | 12 +- .../dependency_operation_detail_link.tsx | 6 +- .../detail_view_header/index.tsx | 2 +- .../error_sampler/error_sample_detail.tsx | 5 +- .../app/help_popover/help_popover.tsx | 1 + .../serverless_metrics/serverless_summary.tsx | 6 +- .../service_node_metrics/index.tsx | 5 +- .../mobile/service_overview/filters/index.tsx | 1 + .../app/mobile/service_overview/index.tsx | 10 +- .../service_group_save/edit_button.tsx | 1 + .../service_group_save/group_details.tsx | 8 +- .../service_group_save/select_services.tsx | 9 +- .../service_groups_list/index.tsx | 2 + .../service_groups_list/sort.tsx | 1 + .../service_groups/service_groups_tour.tsx | 7 +- .../cytoscape_example_data.stories.tsx | 4 + .../app/service_map/empty_banner.tsx | 5 +- .../popover/dependency_contents.tsx | 1 + .../app/service_map/popover/edge_contents.tsx | 1 + .../service_map/popover/service_contents.tsx | 13 +- .../app/service_map/timeout_prompt.tsx | 5 +- .../components/app/service_overview/index.tsx | 5 +- .../service_page/service_page.tsx | 6 +- .../settings_page/setting_form_row.tsx | 3 + .../settings_page/settings_page.tsx | 6 +- .../settings/agent_configurations/index.tsx | 1 + .../agent_configurations/list/index.tsx | 2 + .../agent_instances_details/index.tsx | 1 + .../settings/agent_keys/create_agent_key.tsx | 7 +- .../create_agent_key/agent_key_callout.tsx | 1 + .../app/settings/agent_keys/index.tsx | 2 + .../prompts/api_keys_not_enabled.tsx | 1 + .../anomaly_detection/add_environments.tsx | 7 +- .../settings/anomaly_detection/jobs_list.tsx | 13 +- .../app/settings/apm_indices/index.tsx | 7 +- .../app/settings/bottom_bar_actions/index.tsx | 7 +- .../delete_button.tsx | 1 + .../documentation.tsx | 9 +- .../filters_section.tsx | 2 + .../flyout_footer.tsx | 8 +- .../custom_link/custom_link_table.tsx | 1 + .../app/settings/custom_link/empty_prompt.tsx | 1 + .../app/settings/general_settings/index.tsx | 1 + .../schema/migrated/card_footer_content.tsx | 5 +- .../migrated/upgrade_available_card.tsx | 5 +- .../app/settings/schema/schema_overview.tsx | 7 +- .../components/app/storage_explorer/index.tsx | 7 +- .../resources/tips_and_resources.tsx | 6 +- .../storage_details_per_service.tsx | 5 +- .../app/storage_explorer/summary_stats.tsx | 10 +- .../trace_explorer/trace_search_box/index.tsx | 2 + .../maybe_view_trace_link.tsx | 1 + .../waterfall_container/waterfall/index.tsx | 1 + .../span_flyout/truncate_height_section.tsx | 1 + .../dropped_spans_warning.tsx | 5 +- .../edit_discovery_rule.tsx | 10 +- .../java_agent_version_input.tsx | 1 + .../runtime_attachment/runtime_attachment.tsx | 1 + .../apm_enrollment_flyout_extension.tsx | 6 +- .../tail_sampling_settings.tsx | 6 +- .../apm_policy_form/settings_form/index.tsx | 1 + .../apm_header_action_menu/labs/index.tsx | 6 +- .../labs/labs_flyout.tsx | 12 +- .../routing/home/storage_explorer.tsx | 6 +- .../analyze_data_button.tsx | 6 +- .../shared/charts/latency_chart/index.tsx | 1 + .../dependencies_table_service_map_link.tsx | 5 +- .../shared/license_prompt/index.tsx | 6 +- .../components/shared/links/apm/apm_link.tsx | 4 +- .../shared/links/apm/error_overview_link.tsx | 8 +- .../shared/links/apm/service_map_link.tsx | 4 +- .../apm/service_node_metric_overview_link.tsx | 8 +- .../service_transactions_overview_link.tsx | 8 +- .../apm/transaction_detail_link/index.tsx | 8 +- .../links/apm/transaction_overview_link.tsx | 8 +- .../links/discover_links/discover_link.tsx | 2 +- .../shared/links/elastic_docs_link.tsx | 2 +- .../components/shared/links/infra_link.tsx | 2 +- .../mlexplorer_link.tsx | 1 + .../mlmanage_jobs_link.tsx | 1 + .../mlsingle_metric_link.tsx | 1 + .../shared/links/setup_instructions_link.tsx | 20 +- .../shared/metadata_table/index.tsx | 7 +- .../components/shared/ml_callout/index.tsx | 15 +- .../shared/select_with_placeholder/index.tsx | 1 + .../shared/span_links/span_links_callout.tsx | 1 + .../shared/span_links/span_links_table.tsx | 5 + .../transaction_action_menu.test.tsx.snap | 1 + .../custom_link_toolbar.tsx | 1 + .../custom_link_menu_section/index.tsx | 2 + .../transaction_action_menu.tsx | 1 + .../license/invalid_license_notification.tsx | 5 +- .../opentelemetry_instructions.tsx | 4 + .../tutorial/config_agent/policy_selector.tsx | 7 +- .../tutorial_fleet_instructions/index.tsx | 2 + .../inventory/components/expression.tsx | 3 + .../alerting/inventory/components/metric.tsx | 2 + .../components/expression_editor/criteria.tsx | 1 + .../expression_editor/criterion.tsx | 3 + .../components/expression_editor/editor.tsx | 6 +- .../expression_editor/threshold.tsx | 2 + .../expression_editor/type_switcher.tsx | 1 + .../components/influencer_filter.tsx | 1 + .../expression_row.test.tsx.snap | 1 + .../custom_equation_editor.tsx | 3 + .../custom_equation/metric_row_with_agg.tsx | 1 + .../custom_equation/metric_row_with_count.tsx | 8 +- .../components/expression.tsx | 3 + .../components/expression_row.tsx | 2 + .../components/data_search_error_callout.tsx | 7 +- .../components/empty_states/no_data.tsx | 8 +- .../infra/public/components/error_page.tsx | 6 +- .../components/metrics_node_details_link.tsx | 6 +- .../shared/components/no_data_content.tsx | 7 +- .../analyze_in_ml_button.tsx | 7 +- .../log_analysis_setup/create_job_button.tsx | 6 +- .../log_analysis_setup/manage_jobs_button.tsx | 2 +- .../ml_unavailable_prompt.tsx | 14 +- .../process_step/create_ml_jobs_button.tsx | 7 +- .../process_step/process_step.tsx | 4 +- .../process_step/recreate_ml_jobs_button.tsx | 7 +- .../setup_flyout/module_list_card.tsx | 5 +- .../setup_flyout/setup_flyout.tsx | 1 + .../setup_status_unknown_prompt.tsx | 7 +- .../user_management_link.tsx | 8 +- .../logging/log_customization_menu.tsx | 8 +- .../components/logging/log_datepicker.tsx | 16 +- .../log_entry_examples_empty_indicator.tsx | 6 +- .../log_entry_examples_failure_indicator.tsx | 6 +- .../logging/log_highlights_menu.tsx | 9 +- .../log_search_buttons.tsx | 2 + .../logging/log_text_scale_controls.tsx | 1 + .../logging/log_text_stream/jump_to_tail.tsx | 7 +- .../log_text_stream/loading_item_view.tsx | 3 +- .../log_entry_context_menu.tsx | 1 + .../components/saved_views/create_modal.tsx | 2 +- .../saved_views/manage_views_flyout.tsx | 11 +- .../components/saved_views/update_modal.tsx | 2 +- .../subscription_splash_content.tsx | 8 +- .../page_setup_content.tsx | 6 +- .../log_entry_rate/page_setup_content.tsx | 6 +- .../index_pattern_configuration_panel.tsx | 5 +- .../source_configuration_settings.tsx | 7 +- .../pages/logs/shared/page_log_view_error.tsx | 13 +- .../stream/components/stream_live_button.tsx | 16 +- .../components/hosts_table_entry_title.tsx | 6 +- .../public/pages/metrics/hosts/index.tsx | 1 + .../anomalies_table/anomalies_table.tsx | 1 + .../ml/anomaly_detection/flyout_home.tsx | 26 +- .../ml/anomaly_detection/job_setup_screen.tsx | 14 +- .../components/node_details/overlay.tsx | 2 + .../components/node_details/tabs/logs.tsx | 2 + .../tabs/metrics/time_dropdown.tsx | 1 + .../node_details/tabs/processes/index.tsx | 7 +- .../tabs/processes/process_row.tsx | 3 +- .../tabs/processes/processes_table.tsx | 6 +- .../node_details/tabs/properties/table.tsx | 4 +- .../inventory_view/components/table_view.tsx | 8 +- .../components/timeline/timeline.tsx | 2 +- .../components/waffle/legend_controls.tsx | 9 +- .../metric_control/custom_metric_form.tsx | 5 + .../waffle/metric_control/mode_switcher.tsx | 4 + .../waffle/waffle_time_controls.tsx | 15 +- .../metric_detail/components/invalid_node.tsx | 7 +- .../components/aggregation.tsx | 1 + .../components/chart_context_menu.tsx | 1 + .../components/chart_options.tsx | 2 + .../metrics_explorer/components/charts.tsx | 1 + .../source_configuration_settings.tsx | 7 +- .../alerts_table/render_cell_value.tsx | 1 + .../observability_status_box.tsx | 28 +- .../observability_status_progress.tsx | 12 +- .../public/components/app/section/index.tsx | 1 + .../core_web_vitals/web_core_vitals_title.tsx | 8 +- .../components/action_menu/action_menu.tsx | 2 + .../components/series_color_picker.tsx | 7 +- .../url_search/selectable_url_list.tsx | 1 + .../exploratory_view/exploratory_view.tsx | 1 + .../header/add_to_case_action.tsx | 3 +- .../exploratory_view/header/embed_action.tsx | 1 + .../header/refresh_button.tsx | 6 +- .../hooks/use_add_to_case.test.tsx | 12 +- .../columns/chart_type_select.tsx | 1 + .../columns/data_type_select.tsx | 1 + .../columns/selected_filters.tsx | 1 + .../columns/text_report_definition_field.tsx | 1 + .../components/filter_values_list.tsx | 1 + .../components/labels_filter.tsx | 1 + .../series_editor/report_metric_options.tsx | 1 + .../views/add_series_button.tsx | 1 + .../field_value_selection.tsx | 1 + .../pages/alerts/components/rule_stats.tsx | 15 +- .../overview/components/header_actions.tsx | 8 +- .../pages/overview/components/news_feed.tsx | 7 +- .../slo_details/components/header_control.tsx | 1 + .../pages/slos/components/slo_list_item.tsx | 4 +- .../slo_list_search_filter_sort_bar.tsx | 1 + .../components/slo_list_welcome_prompt.tsx | 13 +- .../components/monitor_details_panel.tsx | 1 + .../components/monitor_location_select.tsx | 6 +- .../common/components/refresh_button.tsx | 6 +- .../common/components/stderr_logs.tsx | 3 +- .../components/common/links/add_monitor.tsx | 5 +- .../common/links/error_details_link.tsx | 14 +- .../common/links/manage_rules_link.tsx | 2 +- .../common/links/step_details_link.tsx | 1 + .../common/links/test_details_link.tsx | 1 + .../react_router_helpers/link_for_eui.tsx | 6 +- .../step_field_trend/step_field_trend.tsx | 8 +- .../wrappers/service_allowed_wrapper.tsx | 8 +- .../getting_started/getting_started_page.tsx | 8 +- .../fields/monitor_type_radio_group.tsx | 6 +- .../fields/script_recorder_fields.tsx | 2 + .../monitor_add_edit/form/field_config.tsx | 3 +- .../monitor_add_edit/form/field_wrappers.tsx | 5 +- .../monitor_add_edit/form/submit.tsx | 8 +- .../monitor_details_portal.tsx | 2 +- .../monitor_add_edit/steps/step_config.tsx | 22 +- .../monitor_not_found_page.tsx | 1 + .../monitor_searchable_list.tsx | 1 + .../monitor_summary/alert_actions.tsx | 7 +- .../monitor_summary/edit_monitor_link.tsx | 8 +- .../test_runs_table_header.tsx | 1 + .../monitor_details/run_test_manually.tsx | 1 + .../common/no_monitors_found.tsx | 6 +- .../monitor_errors/monitor_async_error.tsx | 6 +- .../monitor_list_table/delete_monitor.tsx | 1 + .../monitor_details_link.tsx | 6 +- .../synthetics_enablement.tsx | 1 + .../monitors_page/monitors_page.tsx | 7 +- .../overview/grid_by_group/group_menu.tsx | 8 +- .../overview/overview/metric_item_icon.tsx | 7 +- .../overview/monitor_detail_flyout.tsx | 8 +- .../overview/overview/overview_grid.tsx | 1 + .../overview/overview/sort_menu.tsx | 8 +- .../alerting_defaults/alert_defaults_form.tsx | 2 + .../components/settings/data_retention.tsx | 5 +- .../global_params/add_param_flyout.tsx | 15 +- .../settings/global_params/add_param_form.tsx | 9 +- .../settings/global_params/params_list.tsx | 2 + .../private_locations/add_location_flyout.tsx | 8 +- .../private_locations/agent_policy_needed.tsx | 2 + .../private_locations/empty_locations.tsx | 3 + .../private_locations/location_form.tsx | 3 + .../private_locations/policy_name.tsx | 5 +- .../view_location_monitors.tsx | 8 +- .../project_api_keys/project_api_keys.tsx | 14 +- .../step_metrics/definitions_popover.tsx | 7 +- .../step_details_page/step_number_nav.tsx | 1 + .../step_details_page/step_page_nav.tsx | 3 + .../waterfall/middle_truncated_text.tsx | 7 +- .../waterfall_header/waterfall_legend.tsx | 6 +- .../waterfall_header/waterfall_search.tsx | 1 + .../test_now_mode/test_now_mode_flyout.tsx | 7 +- .../test_now_mode/test_result_header.tsx | 1 + .../components/step_number_nav.tsx | 2 + .../public/apps/synthetics/routes.tsx | 7 +- .../__snapshots__/cert_monitors.test.tsx.snap | 3 + .../__snapshots__/location_link.test.tsx.snap | 1 + .../monitor_page_link.test.tsx.snap | 8 +- .../components/common/location_link.tsx | 6 +- .../components/common/monitor_page_link.tsx | 2 +- .../components/common/monitor_tags.tsx | 1 + .../react_router_helpers/link_for_eui.tsx | 6 +- .../browser/script_recorder_fields.tsx | 8 +- .../fleet_package/browser/source_field.tsx | 1 + .../fleet_package/common/common_fields.tsx | 1 + .../fleet_package/custom_fields.tsx | 1 + .../fleet_package/deprecate_notice_modal.tsx | 1 + .../fleet_package/http/simple_fields.tsx | 1 + .../fleet_package/icmp/simple_fields.tsx | 1 + .../components/monitor/ml/license_info.tsx | 1 + .../components/monitor/ml/ml_flyout.tsx | 10 +- .../components/monitor/ml/ml_job_link.tsx | 10 +- .../components/monitor/monitor_title.tsx | 6 +- .../__snapshots__/expanded_row.test.tsx.snap | 1 + .../ping_timestamp/step_image_caption.tsx | 2 + .../monitor/ping_list/doc_link_body.tsx | 2 +- .../monitor/ping_list/location_name.tsx | 1 + .../monitor_status.bar.test.tsx.snap | 1 + .../status_details/status_bar/status_bar.tsx | 1 + .../synthetics/step_detail/step_page_nav.tsx | 2 + .../step_detail/step_page_title.tsx | 2 + .../waterfall/waterfall_filter.tsx | 1 + .../components/middle_truncated_text.tsx | 7 +- .../action_bar/action_bar.tsx | 2 + .../manage_locations/add_location_flyout.tsx | 13 +- .../manage_locations/agent_policy_needed.tsx | 8 +- .../manage_locations/empty_locations.tsx | 2 + .../manage_locations/location_form.tsx | 2 + .../manage_locations_flyout.tsx | 14 +- .../manage_locations/policy_name.tsx | 5 +- .../monitor_advanced_fields.tsx | 2 + .../monitor_config/monitor_config.tsx | 7 +- .../monitor_list/enablement_empty_state.tsx | 2 + .../monitor_list/list_tabs.tsx | 1 + .../monitor_list/management_settings.tsx | 14 +- .../monitor_list/monitor_async_error.tsx | 6 +- .../monitor_list/monitor_list.tsx | 1 + .../test_now_mode/test_result_header.tsx | 6 +- .../availability_expression_select.tsx | 1 + .../alerts/toggle_alert_flyout_button.tsx | 6 +- .../integration_deprecation_callout.tsx | 7 +- .../monitor_list/columns/monitor_name_col.tsx | 1 + .../overview/monitor_list/monitor_list.tsx | 8 +- .../integration_link.test.tsx.snap | 1 + .../most_recent_error.test.tsx.snap | 1 + .../actions_popover/integration_link.tsx | 2 +- .../monitor_list_drawer/monitor_url.tsx | 7 +- .../monitor_list/troubleshoot_popover.tsx | 10 +- .../synthetics/check_steps/stderr_logs.tsx | 3 +- .../synthetics/check_steps/step_duration.tsx | 1 + .../check_steps/step_field_trend.tsx | 8 +- .../lib/alert_types/alert_messages.tsx | 2 +- .../legacy_uptime/pages/mapping_error.tsx | 1 + .../monitor_management/disabled_callout.tsx | 3 +- .../invalid_api_key_callout.tsx | 7 +- .../service_allowed_wrapper.tsx | 8 +- .../public/legacy_uptime/pages/not_found.tsx | 5 +- .../pages/synthetics/checks_navigation.tsx | 2 + .../environment_filter/index.tsx | 1 + .../impactful_metrics/js_errors.tsx | 1 + .../local_uifilters/selected_filters.tsx | 1 + .../reset_percentile_zoom.tsx | 7 +- .../url_filter/service_name_filter/index.tsx | 1 + yarn.lock | 4 + 346 files changed, 2029 insertions(+), 391 deletions(-) create mode 100644 packages/kbn-eslint-plugin-telemetry/README.mdx create mode 100644 packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.test.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/helpers/get_function_name.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/helpers/get_intent_from_node.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/index.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/jest.config.js create mode 100644 packages/kbn-eslint-plugin-telemetry/kibana.jsonc create mode 100644 packages/kbn-eslint-plugin-telemetry/package.json create mode 100644 packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts create mode 100644 packages/kbn-eslint-plugin-telemetry/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index f32b6498d998..51cd3440b87d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -897,6 +897,18 @@ module.exports = { ], }, }, + { + files: [ + 'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/observability/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/ux/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/synthetics/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/infra/**/*.{js,mjs,ts,tsx}', + ], + rules: { + '@kbn/telemetry/event_generating_elements_should_be_instrumented': 'error', + }, + }, { // require explicit return types in route handlers for performance reasons files: ['x-pack/plugins/apm/server/**/route.ts'], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 914d4e78e1d0..2d003e7d696b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -334,6 +334,7 @@ packages/kbn-eslint-config @elastic/kibana-operations packages/kbn-eslint-plugin-disable @elastic/kibana-operations packages/kbn-eslint-plugin-eslint @elastic/kibana-operations packages/kbn-eslint-plugin-imports @elastic/kibana-operations +packages/kbn-eslint-plugin-telemetry @elastic/actionable-observability x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security src/plugins/event_annotation @elastic/kibana-visualizations x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops diff --git a/package.json b/package.json index 967ffe0dd015..9f85e93bf8b4 100644 --- a/package.json +++ b/package.json @@ -1059,6 +1059,7 @@ "@kbn/eslint-plugin-disable": "link:packages/kbn-eslint-plugin-disable", "@kbn/eslint-plugin-eslint": "link:packages/kbn-eslint-plugin-eslint", "@kbn/eslint-plugin-imports": "link:packages/kbn-eslint-plugin-imports", + "@kbn/eslint-plugin-telemetry": "link:packages/kbn-eslint-plugin-telemetry", "@kbn/expect": "link:packages/kbn-expect", "@kbn/failed-test-reporter-cli": "link:packages/kbn-failed-test-reporter-cli", "@kbn/find-used-node-modules": "link:packages/kbn-find-used-node-modules", diff --git a/packages/kbn-eslint-config/.eslintrc.js b/packages/kbn-eslint-config/.eslintrc.js index 79369e3ed2ca..36504cb8b835 100644 --- a/packages/kbn-eslint-config/.eslintrc.js +++ b/packages/kbn-eslint-config/.eslintrc.js @@ -1,22 +1,18 @@ const { USES_STYLED_COMPONENTS } = require('@kbn/babel-preset/styled_components_files'); module.exports = { - extends: [ - './javascript.js', - './typescript.js', - './jest.js', - './react.js', - ], + extends: ['./javascript.js', './typescript.js', './jest.js', './react.js'], plugins: [ '@kbn/eslint-plugin-disable', '@kbn/eslint-plugin-eslint', '@kbn/eslint-plugin-imports', + '@kbn/eslint-plugin-telemetry', 'prettier', ], parserOptions: { - ecmaVersion: 2018 + ecmaVersion: 2018, }, env: { @@ -41,7 +37,7 @@ module.exports = { { from: 'mkdirp', to: false, - disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` + disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead`, }, { from: 'numeral', @@ -50,7 +46,7 @@ module.exports = { { from: '@kbn/elastic-idx', to: false, - disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain`, }, { from: 'x-pack', @@ -67,46 +63,45 @@ module.exports = { { from: 'monaco-editor', to: false, - disallowedMessage: `Don't import monaco directly, use or add exports to @kbn/monaco` + disallowedMessage: `Don't import monaco directly, use or add exports to @kbn/monaco`, }, { from: 'tinymath', to: '@kbn/tinymath', - disallowedMessage: `Don't use 'tinymath', use '@kbn/tinymath'` + disallowedMessage: `Don't use 'tinymath', use '@kbn/tinymath'`, }, { from: '@kbn/test/types/ftr', to: '@kbn/test', - disallowedMessage: `import from the root of @kbn/test instead` + disallowedMessage: `import from the root of @kbn/test instead`, }, { from: 'react-intl', to: '@kbn/i18n-react', - disallowedMessage: `import from @kbn/i18n-react instead` + disallowedMessage: `import from @kbn/i18n-react instead`, }, { from: 'styled-components', to: false, exclude: USES_STYLED_COMPONENTS, - disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in packages/kbn-babel-preset/styled_components_files.js.` + disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in packages/kbn-babel-preset/styled_components_files.js.`, }, - ...[ - '@elastic/eui/dist/eui_theme_light.json', - '@elastic/eui/dist/eui_theme_dark.json', - ].map(from => ({ - from, - to: false, - disallowedMessage: `Use "@kbn/ui-theme" to access theme vars.` - })), + ...['@elastic/eui/dist/eui_theme_light.json', '@elastic/eui/dist/eui_theme_dark.json'].map( + (from) => ({ + from, + to: false, + disallowedMessage: `Use "@kbn/ui-theme" to access theme vars.`, + }) + ), { from: '@kbn/test/jest', to: '@kbn/test-jest-helpers', - disallowedMessage: `import from @kbn/test-jest-helpers instead` + disallowedMessage: `import from @kbn/test-jest-helpers instead`, }, { from: '@kbn/utility-types/jest', to: '@kbn/utility-types-jest', - disallowedMessage: `import from @kbn/utility-types-jest instead` + disallowedMessage: `import from @kbn/utility-types-jest instead`, }, { from: '@kbn/inspector-plugin', @@ -149,142 +144,126 @@ module.exports = { * of the file being linted so that we could re-route imports from `plugin-client` types to a different package * than `plugin-server` types. */ - '@kbn/imports/exports_moved_packages': ['error', [ - { - from: '@kbn/dev-utils', - to: '@kbn/tooling-log', - exportNames: [ - 'DEFAULT_LOG_LEVEL', - 'getLogLevelFlagsHelp', - 'LOG_LEVEL_FLAGS', - 'LogLevel', - 'Message', - 'ParsedLogLevel', - 'parseLogLevel', - 'pickLevelFromFlags', - 'ToolingLog', - 'ToolingLogCollectingWriter', - 'ToolingLogOptions', - 'ToolingLogTextWriter', - 'ToolingLogTextWriterConfig', - 'Writer', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/ci-stats-reporter', - exportNames: [ - 'CiStatsMetric', - 'CiStatsReporter', - 'CiStatsReportTestsOptions', - 'CiStatsTestGroupInfo', - 'CiStatsTestResult', - 'CiStatsTestRun', - 'CiStatsTestType', - 'CiStatsTiming', - 'getTimeReporter', - 'MetricsOptions', - 'TimingsOptions', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/ci-stats-core', - exportNames: [ - 'Config', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/jest-serializers', - exportNames: [ - 'createAbsolutePathSerializer', - 'createStripAnsiSerializer', - 'createRecursiveSerializer', - 'createAnyInstanceSerializer', - 'createReplaceSerializer', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/stdio-dev-helpers', - exportNames: [ - 'observeReadable', - 'observeLines', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/sort-package-json', - exportNames: [ - 'sortPackageJson', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/dev-cli-runner', - exportNames: [ - 'run', - 'Command', - 'RunWithCommands', - 'CleanupTask', - 'Command', - 'CommandRunFn', - 'FlagOptions', - 'Flags', - 'RunContext', - 'RunFn', - 'RunOptions', - 'RunWithCommands', - 'RunWithCommandsOptions', - 'getFlags', - 'mergeFlagOptions' - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/dev-cli-errors', - exportNames: [ - 'createFailError', - 'createFlagError', - 'isFailError', - ] - }, - { - from: '@kbn/dev-utils', - to: '@kbn/dev-proc-runner', - exportNames: [ - 'withProcRunner', - 'ProcRunner', - ] - }, - { - from: '@kbn/utils', - to: '@kbn/repo-info', - exportNames: [ - 'REPO_ROOT', - 'UPSTREAM_BRANCH', - 'kibanaPackageJson', - 'isKibanaDistributable', - 'fromRoot', - ] - }, - { - from: '@kbn/presentation-util-plugin/common', - to: '@kbn/presentation-util-plugin/test_helpers', - exportNames: [ - 'functionWrapper', - 'fontStyle' - ] - }, - { - from: '@kbn/fleet-plugin/common', - to: '@kbn/fleet-plugin/common/mocks', - exportNames: [ - 'createFleetAuthzMock' - ] - } - ]], + '@kbn/imports/exports_moved_packages': [ + 'error', + [ + { + from: '@kbn/dev-utils', + to: '@kbn/tooling-log', + exportNames: [ + 'DEFAULT_LOG_LEVEL', + 'getLogLevelFlagsHelp', + 'LOG_LEVEL_FLAGS', + 'LogLevel', + 'Message', + 'ParsedLogLevel', + 'parseLogLevel', + 'pickLevelFromFlags', + 'ToolingLog', + 'ToolingLogCollectingWriter', + 'ToolingLogOptions', + 'ToolingLogTextWriter', + 'ToolingLogTextWriterConfig', + 'Writer', + ], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/ci-stats-reporter', + exportNames: [ + 'CiStatsMetric', + 'CiStatsReporter', + 'CiStatsReportTestsOptions', + 'CiStatsTestGroupInfo', + 'CiStatsTestResult', + 'CiStatsTestRun', + 'CiStatsTestType', + 'CiStatsTiming', + 'getTimeReporter', + 'MetricsOptions', + 'TimingsOptions', + ], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/ci-stats-core', + exportNames: ['Config'], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/jest-serializers', + exportNames: [ + 'createAbsolutePathSerializer', + 'createStripAnsiSerializer', + 'createRecursiveSerializer', + 'createAnyInstanceSerializer', + 'createReplaceSerializer', + ], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/stdio-dev-helpers', + exportNames: ['observeReadable', 'observeLines'], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/sort-package-json', + exportNames: ['sortPackageJson'], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/dev-cli-runner', + exportNames: [ + 'run', + 'Command', + 'RunWithCommands', + 'CleanupTask', + 'Command', + 'CommandRunFn', + 'FlagOptions', + 'Flags', + 'RunContext', + 'RunFn', + 'RunOptions', + 'RunWithCommands', + 'RunWithCommandsOptions', + 'getFlags', + 'mergeFlagOptions', + ], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/dev-cli-errors', + exportNames: ['createFailError', 'createFlagError', 'isFailError'], + }, + { + from: '@kbn/dev-utils', + to: '@kbn/dev-proc-runner', + exportNames: ['withProcRunner', 'ProcRunner'], + }, + { + from: '@kbn/utils', + to: '@kbn/repo-info', + exportNames: [ + 'REPO_ROOT', + 'UPSTREAM_BRANCH', + 'kibanaPackageJson', + 'isKibanaDistributable', + 'fromRoot', + ], + }, + { + from: '@kbn/presentation-util-plugin/common', + to: '@kbn/presentation-util-plugin/test_helpers', + exportNames: ['functionWrapper', 'fontStyle'], + }, + { + from: '@kbn/fleet-plugin/common', + to: '@kbn/fleet-plugin/common/mocks', + exportNames: ['createFleetAuthzMock'], + }, + ], + ], '@kbn/disable/no_protected_eslint_disable': 'error', '@kbn/disable/no_naked_eslint_disable': 'error', diff --git a/packages/kbn-eslint-plugin-telemetry/README.mdx b/packages/kbn-eslint-plugin-telemetry/README.mdx new file mode 100644 index 000000000000..11127b2492a5 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/README.mdx @@ -0,0 +1,13 @@ +--- +id: kibDevDocsOpsEslintPluginTelemetry +slug: /kibana-dev-docs/ops/kbn-eslint-plugin-telemetry +title: '@kbn/eslint-plugin-telemetry' +description: Custom ESLint rules to support telemetry in the Kibana repository +tags: ['kibana', 'dev', 'contributor', 'operations', 'eslint', 'telemetry'] +--- + +`@kbn/eslint-plugin-telemetry` is an ESLint plugin providing custom rules for validating JSXCode in the Kibana repo to make sure it can be instrumented for the purposes of telemetry. + +## `@kbn/telemetry/instrumentable_elements_should_be_instrumented` + +This rule warns engineers to add `data-test-subj` to instrumentable components. It currently suggests the most widely used EUI components (`EuiButton`, `EuiFieldText`, etc), but can be expanded with often-used specific components used in the Kibana repo. diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts b/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts new file mode 100644 index 000000000000..b739dc5116c1 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts @@ -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 type { Scope } from 'eslint'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; + +export function checkNodeForExistingDataTestSubjProp( + node: TSESTree.JSXOpeningElement, + getScope: () => Scope.Scope +): boolean { + const hasJsxDataTestSubjProp = node.attributes.find( + (attr) => attr.type === AST_NODE_TYPES.JSXAttribute && attr.name.name === 'data-test-subj' + ); + + if (hasJsxDataTestSubjProp) { + return true; + } + + const spreadedVariable = node.attributes.find( + (attr) => attr.type === AST_NODE_TYPES.JSXSpreadAttribute + ); + + if ( + !spreadedVariable || + !('argument' in spreadedVariable) || + !('name' in spreadedVariable.argument) + ) { + return false; + } + + const { name } = spreadedVariable.argument; // The name of the spreaded variable + + const variable = getScope().variables.find((v) => v.name === name); // the variable definition of the spreaded variable + + return variable && variable.defs.length > 0 + ? variable.defs[0].node.init.properties.find((property: TSESTree.Property) => { + if ('value' in property.key) { + return property.key.value === 'data-test-subj'; + } + return false; + }) + : false; +} diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.test.ts b/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.test.ts new file mode 100644 index 000000000000..36790e883cce --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.test.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 { getAppName } from './get_app_name'; + +const SYSTEMPATH = 'systemPath'; + +const testMap = [ + ['x-pack/plugins/observability/foo/bar/baz/header_actions.tsx', 'o11y'], + ['x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx', 'apm'], + ['x-pack/plugins/cases/public/components/foo.tsx', 'cases'], + ['packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx', 'kbnAlertsUiShared'], +]; + +describe('Get App Name', () => { + test.each(testMap)( + 'should get the responsible app name from a file path', + (path, expectedValue) => { + const appName = getAppName(`${SYSTEMPATH}/${path}`, SYSTEMPATH); + expect(appName).toBe(expectedValue); + } + ); +}); diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.ts b/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.ts new file mode 100644 index 000000000000..e483a572be89 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/helpers/get_app_name.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { camelCase } from 'lodash'; +import path from 'path'; +import { getPkgDirMap } from '@kbn/repo-packages'; +import { REPO_ROOT } from '@kbn/repo-info'; + +export function getAppName(fileName: string, cwd: string) { + const { dir } = path.parse(fileName); + const relativePathToFile = dir.replace(cwd, ''); + + const packageDirs = Array.from( + Array.from(getPkgDirMap(REPO_ROOT).values()).reduce((acc, currentDir) => { + const topDirectory = currentDir.normalizedRepoRelativeDir.split('/')[0]; + + if (topDirectory) { + acc.add(topDirectory); + } + + return acc; + }, new Set()) + ); + + const relativePathArray = relativePathToFile.split('/'); + + const appName = camelCase( + packageDirs.reduce((acc, repoPath) => { + if (!relativePathArray[1]) return ''; + + if (relativePathArray[1] === 'x-pack') { + return relativePathArray[3]; + } + + if (relativePathArray[1].includes(repoPath)) { + return relativePathArray[2]; + } + + return acc; + }, '') + ); + + return appName === 'observability' ? 'o11y' : appName; +} diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/get_function_name.ts b/packages/kbn-eslint-plugin-telemetry/helpers/get_function_name.ts new file mode 100644 index 000000000000..40bb3e8a17e9 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/helpers/get_function_name.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 { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; + +export function getFunctionName(func: TSESTree.FunctionDeclaration | TSESTree.Node): string { + if ( + 'id' in func && + func.id && + func.type === AST_NODE_TYPES.FunctionDeclaration && + func.id.type === AST_NODE_TYPES.Identifier + ) { + return func.id.name; + } + + if ( + func.parent && + (func.parent.type !== AST_NODE_TYPES.VariableDeclarator || + func.parent.id.type !== AST_NODE_TYPES.Identifier) + ) { + return getFunctionName(func.parent); + } + + if (func.parent?.id && 'name' in func.parent.id) { + return func.parent.id.name; + } + + return ''; +} diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/get_intent_from_node.ts b/packages/kbn-eslint-plugin-telemetry/helpers/get_intent_from_node.ts new file mode 100644 index 000000000000..5df44e5dd0f7 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/helpers/get_intent_from_node.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 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 { TSESTree } from '@typescript-eslint/typescript-estree'; +import camelCase from 'lodash/camelCase'; + +/* + Attempts to get a string representation of the intent + out of an array of nodes. + + Currently supported node types in the array: + * String literal text (JSXText) + * Translated text via component -> uses prop `defaultMessage` + * Translated text via {i18n.translate} call -> uses passed options object key `defaultMessage` +*/ +export function getIntentFromNode(originalNode: TSESTree.JSXOpeningElement): string { + const parent = originalNode.parent as TSESTree.JSXElement; + + const node = Array.isArray(parent.children) ? parent.children : []; + + if (node.length === 0) { + return ''; + } + + /* + In order to satisfy TS we need to do quite a bit of defensive programming. + This is my best attempt at providing the minimum amount of typeguards and + keeping the code readable. In the cases where types are explicitly set to + variables, it was done to help the compiler when it couldn't infer the type. + */ + return node.reduce((acc: string, currentNode) => { + switch (currentNode.type) { + case 'JSXText': + // When node is a string primitive + return `${acc}${strip(currentNode.value)}`; + + case 'JSXElement': + // Determining whether node is of form `` + const name: TSESTree.JSXTagNameExpression = currentNode.openingElement.name; + const attributes: Array = + currentNode.openingElement.attributes; + + if (!('name' in name) || name.name !== 'FormattedMessage') { + return ''; + } + + const defaultMessageProp = attributes.find( + (attribute) => 'name' in attribute && attribute.name.name === 'defaultMessage' + ); + + if ( + !defaultMessageProp || + !('value' in defaultMessageProp) || + !('type' in defaultMessageProp.value!) || + defaultMessageProp.value.type !== 'Literal' || + typeof defaultMessageProp.value.value !== 'string' + ) { + return ''; + } + + return `${acc}${strip(defaultMessageProp.value.value)}`; + + case 'JSXExpressionContainer': + // Determining whether node is of form `{i18n.translate('foo', { defaultMessage: 'message'})}` + const expression: TSESTree.JSXEmptyExpression | TSESTree.Expression = + currentNode.expression; + + if (!('arguments' in expression)) { + return ''; + } + + const args: TSESTree.CallExpressionArgument[] = expression.arguments; + const callee: TSESTree.LeftHandSideExpression = expression.callee; + + if (!('object' in callee)) { + return ''; + } + + const object: TSESTree.LeftHandSideExpression = callee.object; + const property: TSESTree.Expression | TSESTree.PrivateIdentifier = callee.property; + + if (!('name' in object) || !('name' in property)) { + return ''; + } + + if (object.name !== 'i18n' || property.name !== 'translate') { + return ''; + } + + const callExpressionArgument: TSESTree.CallExpressionArgument | undefined = args.find( + (arg) => arg.type === 'ObjectExpression' + ); + + if (!callExpressionArgument || callExpressionArgument.type !== 'ObjectExpression') { + return ''; + } + + const defaultMessageValue: TSESTree.ObjectLiteralElement | undefined = + callExpressionArgument.properties.find( + (prop) => + prop.type === 'Property' && 'name' in prop.key && prop.key.name === 'defaultMessage' + ); + + if ( + !defaultMessageValue || + !('value' in defaultMessageValue) || + defaultMessageValue.value.type !== 'Literal' || + typeof defaultMessageValue.value.value !== 'string' + ) { + return ''; + } + + return `${acc}${strip(defaultMessageValue.value.value)}`; + + default: + break; + } + + return acc; + }, ''); +} + +function strip(input: string): string { + if (!input) return ''; + + const cleanedString = camelCase(input); + + return `${cleanedString.charAt(0).toUpperCase()}${cleanedString.slice(1)}`; +} diff --git a/packages/kbn-eslint-plugin-telemetry/index.ts b/packages/kbn-eslint-plugin-telemetry/index.ts new file mode 100644 index 000000000000..ea68eae7fdfa --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/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 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 { EventGeneratingElementsShouldBeInstrumented } from './rules/event_generating_elements_should_be_instrumented'; + +/** + * Custom ESLint rules, add `'@kbn/eslint-plugin-telemetry'` to your eslint config to use them + * @internal + */ +export const rules = { + event_generating_elements_should_be_instrumented: EventGeneratingElementsShouldBeInstrumented, +}; diff --git a/packages/kbn-eslint-plugin-telemetry/jest.config.js b/packages/kbn-eslint-plugin-telemetry/jest.config.js new file mode 100644 index 000000000000..f2778eaf7dda --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/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/kbn-eslint-plugin-telemetry'], +}; diff --git a/packages/kbn-eslint-plugin-telemetry/kibana.jsonc b/packages/kbn-eslint-plugin-telemetry/kibana.jsonc new file mode 100644 index 000000000000..79c8fbf8adb2 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "type": "shared-common", + "id": "@kbn/eslint-plugin-telemetry", + "owner": "@elastic/actionable-observability", + "devOnly": true +} diff --git a/packages/kbn-eslint-plugin-telemetry/package.json b/packages/kbn-eslint-plugin-telemetry/package.json new file mode 100644 index 000000000000..9632bf6d0f5c --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/eslint-plugin-telemetry", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts new file mode 100644 index 000000000000..c8829f05efd2 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts @@ -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 { RuleTester } from 'eslint'; +import { + EventGeneratingElementsShouldBeInstrumented, + EVENT_GENERATING_ELEMENTS, +} from './event_generating_elements_should_be_instrumented'; + +const tsTester = [ + '@typescript-eslint/parser', + new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + }, + }, + }), +] as const; + +const babelTester = [ + '@babel/eslint-parser', + new RuleTester({ + parser: require.resolve('@babel/eslint-parser'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, + requireConfigFile: false, + babelOptions: { + presets: ['@kbn/babel-preset/node_preset'], + }, + }, + }), +] as const; + +for (const [name, tester] of [tsTester, babelTester]) { + describe(name, () => { + tester.run( + '@kbn/event_generating_elements_should_be_instrumented', + EventGeneratingElementsShouldBeInstrumented, + { + valid: EVENT_GENERATING_ELEMENTS.map((element) => ({ + filename: 'foo.tsx', + code: `<${element} data-test-subj="foo" />`, + })), + + invalid: EVENT_GENERATING_ELEMENTS.map((element) => ({ + filename: 'foo.tsx', + code: `<${element}>Value`, + errors: [ + { + line: 1, + message: `<${element}> should have a \`data-test-subj\` for telemetry purposes. Use the autofix suggestion or add your own.`, + }, + ], + output: `<${element} data-test-subj="Value${element + .replace('Eui', '') + .replace('Empty', '')}">Value`, + })), + } + ); + }); +} diff --git a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts new file mode 100644 index 000000000000..d2069d2845e5 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.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 type { Rule } from 'eslint'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; + +import { checkNodeForExistingDataTestSubjProp } from '../helpers/check_node_for_existing_data_test_subj_prop'; +import { getIntentFromNode } from '../helpers/get_intent_from_node'; +import { getAppName } from '../helpers/get_app_name'; +import { getFunctionName } from '../helpers/get_function_name'; + +export const EVENT_GENERATING_ELEMENTS = [ + 'EuiButton', + 'EuiButtonEmpty', + 'EuiLink', + 'EuiFieldText', + 'EuiFieldSearch', + 'EuiFieldNumber', + 'EuiSelect', + 'EuiRadioGroup', + 'EuiTextArea', +]; + +export const EventGeneratingElementsShouldBeInstrumented: Rule.RuleModule = { + meta: { + type: 'suggestion', + fixable: 'code', + }, + create(context) { + const { getCwd, getFilename, getScope, report } = context; + + return { + JSXIdentifier: (node: TSESTree.Node) => { + if (!('name' in node)) { + return; + } + + const name = String(node.name); + const range = node.range; + const parent = node.parent; + + if ( + parent?.type !== AST_NODE_TYPES.JSXOpeningElement || + !EVENT_GENERATING_ELEMENTS.includes(name) + ) { + return; + } + + const hasDataTestSubjProp = checkNodeForExistingDataTestSubjProp(parent, getScope); + + if (hasDataTestSubjProp) { + // JSXOpeningElement already has a prop for data-test-subj. Bail. + return; + } + + // Start building the suggestion. + + // 1. The app name + const cwd = getCwd(); + const fileName = getFilename(); + const appName = getAppName(fileName, cwd); + + // 2. Component name + const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; + const functionName = getFunctionName(functionDeclaration); + const componentName = `${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`; + + // 3. The intention of the element (i.e. "Select date", "Submit", "Cancel") + const intent = getIntentFromNode(parent); + + // 4. The element name that generates the events + const element = name.replace('Eui', '').replace('Empty', ''); + + const suggestion = `${appName}${componentName}${intent}${element}`; // 'o11yHeaderActionsSubmitButton' + + // 6. Report feedback to engineer + report({ + node: node as any, + message: `<${name}> should have a \`data-test-subj\` for telemetry purposes. Use the autofix suggestion or add your own.`, + fix(fixer) { + return fixer.insertTextAfterRange(range, ` data-test-subj="${suggestion}"`); + }, + }); + }, + } as Rule.RuleListener; + }, +}; diff --git a/packages/kbn-eslint-plugin-telemetry/tsconfig.json b/packages/kbn-eslint-plugin-telemetry/tsconfig.json new file mode 100644 index 000000000000..75014884aa31 --- /dev/null +++ b/packages/kbn-eslint-plugin-telemetry/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"], + "lib": ["es2021"] + }, + "include": ["**/*.ts"], + "exclude": ["target/**/*"], + "kbn_references": ["@kbn/repo-packages", "@kbn/repo-info"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e4ad01b21f5a..c697ddcb1a1a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -662,6 +662,8 @@ "@kbn/eslint-plugin-eslint/*": ["packages/kbn-eslint-plugin-eslint/*"], "@kbn/eslint-plugin-imports": ["packages/kbn-eslint-plugin-imports"], "@kbn/eslint-plugin-imports/*": ["packages/kbn-eslint-plugin-imports/*"], + "@kbn/eslint-plugin-telemetry": ["packages/kbn-eslint-plugin-telemetry"], + "@kbn/eslint-plugin-telemetry/*": ["packages/kbn-eslint-plugin-telemetry/*"], "@kbn/eso-plugin": ["x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin"], "@kbn/eso-plugin/*": ["x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/*"], "@kbn/event-annotation-plugin": ["src/plugins/event_annotation"], diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx index 1045eff2cc6c..a94cad876736 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx @@ -171,6 +171,7 @@ export function TransactionDurationRuleType(props: Props) { })} > { return { diff --git a/x-pack/plugins/apm/public/components/alerting/utils/fields.tsx b/x-pack/plugins/apm/public/components/alerting/utils/fields.tsx index 3ff3275f8dc8..a385bfaa4d95 100644 --- a/x-pack/plugins/apm/public/components/alerting/utils/fields.tsx +++ b/x-pack/plugins/apm/public/components/alerting/utils/fields.tsx @@ -156,6 +156,7 @@ export function IsAboveField({ })} > onChange(parseInt(e.target.value, 10))} diff --git a/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx b/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx index 101713c34597..8dc8669c9ecd 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx @@ -56,7 +56,11 @@ export function CorrelationsProgressControls({ {!isRunning && ( - + )} {isRunning && ( - + {spanName}; + return ( + + {spanName} + + ); } diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/detail_view_header/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/detail_view_header/index.tsx index 8e8ed13b9b3f..47becdf9b306 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/detail_view_header/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/detail_view_header/index.tsx @@ -26,7 +26,7 @@ export function DetailViewHeader({ return ( - + diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx index 5366f467826c..4251d777602b 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx @@ -187,7 +187,10 @@ export function ErrorSampleDetails({ {isTraceExplorerEnabled && ( - + diff --git a/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx index 57f419747624..3a972b44f9a4 100644 --- a/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx +++ b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx @@ -37,6 +37,7 @@ export function HelpPopoverButton({ if (buttonTextEnabled) { return ( - + {i18n.translate('xpack.apm.serverlessMetrics.summary.feedback', { defaultMessage: 'Give feedback', })} diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx index 1db9c8690891..28865a8ad5f1 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx @@ -127,7 +127,10 @@ export function ServiceNodeMetrics({ serviceNodeName }: Props) { defaultMessage="We could not identify which JVMs these metrics belong to. This is likely caused by running a version of APM Server that is older than 7.5. Upgrading to APM Server 7.5 or higher should resolve this issue. For more information on upgrading, see the {link}. As an alternative, you can use the Kibana Query bar to filter by hostname, container ID or other fields." values={{ link: ( - + {i18n.translate( 'xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningDocumentationLink', { defaultMessage: 'documentation of APM Server' } diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx index 0fbab1465226..9dcad10a9fa7 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx @@ -133,6 +133,7 @@ export function MobileFilters() { style={isLarge ? {} : { width: '225px' }} > + {i18n.translate( 'xpack.apm.serviceOverview.mobileCallOutLink', { @@ -320,7 +323,10 @@ export function MobileServiceOverview() { fixedHeight={true} showPerPageOptions={false} link={ - + {i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableTabLink', { defaultMessage: 'View dependencies' } diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/edit_button.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/edit_button.tsx index 8325ffd40195..07b8d8c87f3d 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/edit_button.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/edit_button.tsx @@ -32,6 +32,7 @@ export function EditButton({ onClick }: Props) { )} > { dismissTour(); diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/group_details.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/group_details.tsx index 36f429b63409..8bad458d5e0e 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/group_details.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_group_save/group_details.tsx @@ -149,6 +149,7 @@ export function GroupDetails({ } > { @@ -181,7 +182,11 @@ export function GroupDetails({ )} - + {i18n.translate( 'xpack.apm.serviceGroups.groupDetailsForm.cancel', { defaultMessage: 'Cancel' } @@ -190,6 +195,7 @@ export function GroupDetails({ { setKuery(stagedKuery); }} @@ -244,6 +245,7 @@ export function SelectServices({
- + {i18n.translate( 'xpack.apm.serviceGroups.selectServicesForm.cancel', { @@ -268,6 +274,7 @@ export function SelectServices({ { onSaveClick({ ...serviceGroup, kuery }); 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 cad9cdb6c7db..cde5fa6733ce 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 @@ -106,6 +106,7 @@ export function ServiceGroupsList() { } > diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/sort.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/sort.tsx index f87a7b767c93..6b635798c135 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/sort.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_list/sort.tsx @@ -35,6 +35,7 @@ const options: Array<{ export function Sort({ type, onChange }: Props) { return ( onChange(e.target.value as ServiceGroupsSortType)} diff --git a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_tour.tsx b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_tour.tsx index 4f7a457be49b..21cdc434a205 100644 --- a/x-pack/plugins/apm/public/components/app/service_groups/service_groups_tour.tsx +++ b/x-pack/plugins/apm/public/components/app/service_groups/service_groups_tour.tsx @@ -71,7 +71,12 @@ export function ServiceGroupsTour({ title={title} anchorPosition={anchorPosition} footerAction={ - + {i18n.translate('xpack.apm.serviceGroups.tour.dismiss', { defaultMessage: 'Dismiss', })} diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx index 2b3daac15dc0..995b82d960e6 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx @@ -67,6 +67,7 @@ export const GenerateMap: Story<{}> = () => { { setElements( generateServiceMapElements({ size, hasAnomalies: true }) @@ -80,6 +81,7 @@ export const GenerateMap: Story<{}> = () => { setSize(e.target.valueAsNumber)} @@ -88,6 +90,7 @@ export const GenerateMap: Story<{}> = () => { { setJson(JSON.stringify({ elements }, null, 2)); }} @@ -183,6 +186,7 @@ export const MapFromJSON: Story<{}> = () => { /> { updateRenderedElements(); }} diff --git a/x-pack/plugins/apm/public/components/app/service_map/empty_banner.tsx b/x-pack/plugins/apm/public/components/app/service_map/empty_banner.tsx index 84b0bcbd0dbc..2e582551c8a0 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/empty_banner.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/empty_banner.tsx @@ -68,7 +68,10 @@ export function EmptyBanner() { defaultMessage: "We will map out connected services and external requests if we can detect them. Please make sure you're running the latest version of the APM agent.", })}{' '} - + {i18n.translate('xpack.apm.serviceMap.emptyBanner.docsLink', { defaultMessage: 'Learn more in the docs', })} diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/dependency_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/dependency_contents.tsx index 7141c856a36f..9ec7773f91c8 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/dependency_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/dependency_contents.tsx @@ -92,6 +92,7 @@ export function DependencyContents({ {/* eslint-disable-next-line @elastic/eui/href-or-on-click*/} { diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx index aeb9a771bf31..e5e3de188de4 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx @@ -67,6 +67,7 @@ export function EdgeContents({ elementData }: ContentsProps) { {/* eslint-disable-next-line @elastic/eui/href-or-on-click*/} { diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/service_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/service_contents.tsx index f5310fad5b22..917fdf651a78 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/service_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/service_contents.tsx @@ -129,14 +129,23 @@ export function ServiceContents({ - + {i18n.translate('xpack.apm.serviceMap.serviceDetailsButtonText', { defaultMessage: 'Service Details', })} - + {i18n.translate('xpack.apm.serviceMap.focusMapButtonText', { defaultMessage: 'Focus map', })} diff --git a/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx b/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx index 31c51f773ab0..cec31e599bb2 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx @@ -46,7 +46,10 @@ export function TimeoutPrompt({ function ApmSettingsDocLink() { const { docLinks } = useApmPluginContext().core; return ( - + {i18n.translate('xpack.apm.serviceMap.timeoutPrompt.docsLink', { defaultMessage: 'Learn more about APM settings in the docs', })} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 3e2d178357a8..40580ef70014 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -174,7 +174,10 @@ export function ServiceOverview() { fixedHeight={true} showPerPageOptions={false} link={ - + {i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableTabLink', { defaultMessage: 'View dependencies' } diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx index a7603163f371..27e942ba49a9 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx @@ -188,7 +188,10 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { {/* Cancel button */} - + {i18n.translate( 'xpack.apm.agentConfig.servicePage.cancelButton', { defaultMessage: 'Cancel' } @@ -200,6 +203,7 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { {/* Next button */} onChange(setting.key, e.target.value)} @@ -51,6 +52,7 @@ function FormRow({ case 'integer': { return ( diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx index 60ea88028509..0130e8051057 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx @@ -160,7 +160,11 @@ export function SettingsPage({ {!isEditMode && ( - + {i18n.translate( 'xpack.apm.agentConfig.chooseService.editButton', { defaultMessage: 'Edit' } diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/index.tsx index 48b421ca0461..74359d03e1a2 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/index.tsx @@ -96,6 +96,7 @@ function CreateConfigurationButton() { } > ( - + {i18n.translate( 'xpack.apm.settings.agentKeys.createKeyFlyout.cancelButton', { @@ -219,6 +223,7 @@ export function CreateAgentKeyFlyout({ onCancel, onSuccess, onError }: Props) { setIsFlyoutVisible(true)} fill={true} iconType="plusInCircle" @@ -238,6 +239,7 @@ function AgentKeysContent({ } actions={ diff --git a/x-pack/plugins/apm/public/components/app/settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/settings/anomaly_detection/add_environments.tsx index f3afd70cfbba..7fbbedfeca27 100644 --- a/x-pack/plugins/apm/public/components/app/settings/anomaly_detection/add_environments.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/anomaly_detection/add_environments.tsx @@ -136,7 +136,11 @@ export function AddEnvironments({ - + {i18n.translate( 'xpack.apm.settings.anomalyDetection.addEnvironments.cancelButtonText', { @@ -147,6 +151,7 @@ export function AddEnvironments({ - + {i18n.translate( 'xpack.apm.settings.anomalyDetection.jobList.manageMlJobsButtonText', { @@ -241,7 +245,12 @@ export function JobsList({ - + {i18n.translate( 'xpack.apm.settings.anomalyDetection.jobList.addEnvironments', { diff --git a/x-pack/plugins/apm/public/components/app/settings/apm_indices/index.tsx b/x-pack/plugins/apm/public/components/app/settings/apm_indices/index.tsx index f551a9b6c51f..1ea865b533ab 100644 --- a/x-pack/plugins/apm/public/components/app/settings/apm_indices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/apm_indices/index.tsx @@ -242,6 +242,7 @@ export function ApmIndices() { fullWidth > - + {i18n.translate( 'xpack.apm.settings.apmIndices.cancelButton', { defaultMessage: 'Cancel' } @@ -276,6 +280,7 @@ export function ApmIndices() { } > - + {i18n.translate( 'xpack.apm.bottomBarActions.discardChangesButton', { @@ -64,6 +68,7 @@ export function BottomBarActions({ {label}; + return ( + + {label} + + ); } diff --git a/x-pack/plugins/apm/public/components/app/settings/custom_link/create_edit_custom_link_flyout/filters_section.tsx b/x-pack/plugins/apm/public/components/app/settings/custom_link/create_edit_custom_link_flyout/filters_section.tsx index 5b2ceb5f353d..901e25f52eb8 100644 --- a/x-pack/plugins/apm/public/components/app/settings/custom_link/create_edit_custom_link_flyout/filters_section.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/custom_link/create_edit_custom_link_flyout/filters_section.tsx @@ -137,6 +137,7 @@ export function FiltersSection({ onRemoveFilter(idx)} disabled={!value && !key && filters.length === 1} @@ -166,6 +167,7 @@ function AddFilterButton({ }) { return ( - + {i18n.translate('xpack.apm.settings.customLink.flyout.close', { defaultMessage: 'Close', })} @@ -44,6 +49,7 @@ export function FlyoutFooter({ )} setSearchTerm(e.target.value)} placeholder={i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/settings/custom_link/empty_prompt.tsx b/x-pack/plugins/apm/public/components/app/settings/custom_link/empty_prompt.tsx index fd7a3d258719..791a26dda7bb 100644 --- a/x-pack/plugins/apm/public/components/app/settings/custom_link/empty_prompt.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/custom_link/empty_prompt.tsx @@ -39,6 +39,7 @@ export function EmptyPrompt({ values={{ customLinkDocLinkText: ( diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx index 670826e43ccc..a578dd585cb0 100644 --- a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx @@ -84,6 +84,7 @@ export function GeneralSettings() { values={{ link: ( - + {i18n.translate( 'xpack.apm.settings.schema.success.viewIntegrationInFleet.buttonText', { defaultMessage: 'View the APM integration in Fleet' } diff --git a/x-pack/plugins/apm/public/components/app/settings/schema/migrated/upgrade_available_card.tsx b/x-pack/plugins/apm/public/components/app/settings/schema/migrated/upgrade_available_card.tsx index 8e7444c1a776..dcdf55508a72 100644 --- a/x-pack/plugins/apm/public/components/app/settings/schema/migrated/upgrade_available_card.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/schema/migrated/upgrade_available_card.tsx @@ -35,7 +35,10 @@ export function UpgradeAvailableCard({ defaultMessage="Even though your APM integration is setup, a new version of the APM integration is available for upgrade with your package policy. {upgradePackagePolicyLink} to get the most out of your setup." values={{ upgradePackagePolicyLink: ( - + {i18n.translate( 'xpack.apm.settings.schema.upgradeAvailable.upgradePackagePolicyLink', { defaultMessage: 'Upgrade your APM integration' } diff --git a/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx b/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx index 1f17b9f63c0a..be52301aaaa4 100644 --- a/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx @@ -162,6 +162,7 @@ export function SchemaOverview({ })} > ), elasticAgentDocLink: ( - + {i18n.translate( 'xpack.apm.settings.schema.descriptionText.elasticAgentDocLinkText', { defaultMessage: 'Elastic Agent' } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx index 98992e97988f..084f11a85f62 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx @@ -123,7 +123,10 @@ export function StorageExplorer() { defaultMessage="Enable progressive loading of data and optimized sorting for services list in {kibanaAdvancedSettingsLink}." values={{ kibanaAdvancedSettingsLink: ( - + {i18n.translate( 'xpack.apm.storageExplorer.longLoadingTimeCalloutLink', { @@ -136,6 +139,7 @@ export function StorageExplorer() { />

setCalloutDismissed({ ...calloutDismissed, @@ -171,6 +175,7 @@ export function StorageExplorer() { )}

setCalloutDismissed({ ...calloutDismissed, diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx index 29553bf1ecca..4c1b283f0562 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx @@ -170,7 +170,11 @@ export function TipsAndResources() { title={title} description={description} footer={ - + {i18n.translate( 'xpack.apm.storageExplorer.resources.learnMoreButton', { 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 ba8005f12759..0e47d657da30 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 @@ -169,7 +169,10 @@ export function StorageDetailsPerService({
- + {i18n.translate( 'xpack.apm.storageExplorer.serviceDetails.serviceOverviewLink', { diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx index 506b966f0d3c..37b6fb872ed1 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx @@ -186,7 +186,10 @@ export function SummaryStats() { - + {i18n.translate( 'xpack.apm.storageExplorer.summary.serviceInventoryLink', { @@ -196,7 +199,10 @@ export function SummaryStats() { - + {i18n.translate( 'xpack.apm.storageExplorer.summary.indexManagementLink', { diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx index 782197828b09..84d72000c7b8 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -162,6 +162,7 @@ export function TraceSearchBox({ { @@ -189,6 +190,7 @@ export function TraceSearchBox({ { onQueryCommit(); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx index af9365b14be0..72b06bdef41f 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx @@ -25,6 +25,7 @@ function FullTraceButton({ }) { return (
{ diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/truncate_height_section.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/truncate_height_section.tsx index aac15e48d884..a8e483963934 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/truncate_height_section.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/truncate_height_section.tsx @@ -48,6 +48,7 @@ export function TruncateHeightSection({ children, previewHeight }: Props) { {showToggle ? ( { setIsOpen(!isOpen); }} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/transaction_flyout/dropped_spans_warning.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/transaction_flyout/dropped_spans_warning.tsx index 2c6dbe99b606..61bfa995a069 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/transaction_flyout/dropped_spans_warning.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/transaction_flyout/dropped_spans_warning.tsx @@ -33,7 +33,10 @@ export function DroppedSpansWarning({ values: { dropped }, } )}{' '} - + {i18n.translate( 'xpack.apm.transactionDetails.transFlyout.callout.learnMoreAboutDroppedSpansLinkText', { diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx index 5059bbabfce9..f5a8498b62a0 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx @@ -64,6 +64,7 @@ export function EditDiscoveryRule({ }} > ({ text: item.operation.label, value: item.operation.value, @@ -145,6 +146,7 @@ export function EditDiscoveryRule({ )} > onChangeProbe(e.target.value)} @@ -156,10 +158,16 @@ export function EditDiscoveryRule({ )} - Cancel + + Cancel + diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx index 8ae285a95268..8c04ce3464d6 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx @@ -147,6 +147,7 @@ export function RuntimeAttachment({ - + {i18n.translate( 'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText', { defaultMessage: 'Install APM Agent' } diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/tail_sampling_settings.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/tail_sampling_settings.tsx index 7af79bb1d6c9..9d98277a63b7 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/tail_sampling_settings.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/tail_sampling_settings.tsx @@ -77,7 +77,11 @@ export function getTailSamplingSettings(docsLinks?: string): SettingsRow[] { defaultMessage="Learn more about tail sampling policies in our {link}." values={{ link: ( - + {i18n.translate( 'xpack.apm.fleet_integration.settings.tailSamplingDocsHelpTextLink', { diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/index.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/index.tsx index d32ace193342..098dbc988c6a 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/index.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/index.tsx @@ -167,6 +167,7 @@ function AdvancedOptions({ children }: { children: React.ReactNode }) { { setIsOpen((state) => !state); diff --git a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/index.tsx b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/index.tsx index c2049f1db623..ac506495ceff 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/index.tsx @@ -27,7 +27,11 @@ export function Labs() { return ( <> - + {i18n.translate('xpack.apm.labs', { defaultMessage: 'Labs' })} {isOpen && } diff --git a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx index cda57400b599..a7dfc481e70b 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx @@ -133,14 +133,22 @@ export function LabsFlyout({ onClose }: Props) { - + {i18n.translate('xpack.apm.labs.cancel', { defaultMessage: 'Cancel', })} - + {i18n.translate('xpack.apm.labs.reload', { defaultMessage: 'Reload to apply changes', })} diff --git a/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx b/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx index e971002dfbcd..807c8249985c 100644 --- a/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx @@ -53,7 +53,11 @@ export const storageExplorer = { ), rightSideItems: [ - + {i18n.translate( 'xpack.apm.views.storageExplorer.giveFeedback', { diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx index d1c1cfba9f73..0c1e86aa5fa2 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx @@ -85,7 +85,11 @@ export function AnalyzeDataButton() { 'Explore Data allows you to select and filter result data in any dimension, and look for the cause or impact of performance problems', })} > - + {i18n.translate('xpack.apm.analyzeDataButton.label', { defaultMessage: 'Explore data', })} diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx index 65285ea4fbe6..e2c1def6e0df 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx @@ -102,6 +102,7 @@ export function LatencyChart({ height, kuery }: Props) { + {i18n.translate('xpack.apm.dependenciesTable.serviceMapLinkText', { defaultMessage: 'View service map', })} diff --git a/x-pack/plugins/apm/public/components/shared/license_prompt/index.tsx b/x-pack/plugins/apm/public/components/shared/license_prompt/index.tsx index a76743d6ff4a..38f348dab53d 100644 --- a/x-pack/plugins/apm/public/components/shared/license_prompt/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/license_prompt/index.tsx @@ -49,7 +49,11 @@ export function LicensePrompt({ titleElement="h2" description={{text}} footer={ - + {i18n.translate('xpack.apm.license.button', { defaultMessage: 'Start trial', })} diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/apm_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/apm_link.tsx index a0ba4f907cbc..9cd7e65ab6b8 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/apm_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/apm_link.tsx @@ -100,5 +100,7 @@ export function LegacyAPMLink({ const href = getLegacyApmHref({ basePath, path, search, query: mergedQuery }); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/error_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/error_overview_link.tsx index bfa2067e9e7e..a1f9892b79e3 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/error_overview_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/error_overview_link.tsx @@ -27,5 +27,11 @@ export function ErrorOverviewLink({ serviceName, query, ...rest }: Props) { query, }); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/service_map_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/service_map_link.tsx index 84eff7eb444b..066dd0c02e6e 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/service_map_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/service_map_link.tsx @@ -22,5 +22,7 @@ interface ServiceMapLinkProps extends APMLinkExtendProps { export function ServiceMapLink({ serviceName, ...rest }: ServiceMapLinkProps) { const href = useServiceMapHref(serviceName); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/service_node_metric_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/service_node_metric_overview_link.tsx index 8cb3bce94fc9..3a988750ab57 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/service_node_metric_overview_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/service_node_metric_overview_link.tsx @@ -46,5 +46,11 @@ export function ServiceNodeMetricOverviewLink({ serviceName, serviceNodeName, }); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/service_transactions_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/service_transactions_overview_link.tsx index b33f22cd9faf..6fb1cc998ab2 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/service_transactions_overview_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/service_transactions_overview_link.tsx @@ -50,5 +50,11 @@ export function ServiceOrTransactionsOverviewLink({ environment, transactionType, }); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/transaction_detail_link/index.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/transaction_detail_link/index.tsx index 517b97ac7797..21b18e673099 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/transaction_detail_link/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/transaction_detail_link/index.tsx @@ -81,7 +81,13 @@ export function TransactionDetailLink({ return ( } + content={ + + } /> ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/transaction_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/transaction_overview_link.tsx index bd0ac78b855f..87d43236787f 100644 --- a/x-pack/plugins/apm/public/components/shared/links/apm/transaction_overview_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/apm/transaction_overview_link.tsx @@ -48,5 +48,11 @@ export function TransactionOverviewLink({ latencyAggregationType, transactionType, }); - return ; + return ( + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx index 1455e53730a7..dccce740b62d 100644 --- a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx @@ -69,5 +69,5 @@ export function DiscoverLink({ query = {}, ...rest }: Props) { location, }); - return ; + return ; } diff --git a/x-pack/plugins/apm/public/components/shared/links/elastic_docs_link.tsx b/x-pack/plugins/apm/public/components/shared/links/elastic_docs_link.tsx index 5a7cc4623ea7..4b97e6653ac7 100644 --- a/x-pack/plugins/apm/public/components/shared/links/elastic_docs_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/elastic_docs_link.tsx @@ -32,7 +32,7 @@ export function ElasticDocsLink({ section, path, children, ...rest }: Props) { return typeof children === 'function' ? ( children(href) ) : ( - + {children} ); diff --git a/x-pack/plugins/apm/public/components/shared/links/infra_link.tsx b/x-pack/plugins/apm/public/components/shared/links/infra_link.tsx index 1e5c3a6748a1..fcc3f2ce9d72 100644 --- a/x-pack/plugins/apm/public/components/shared/links/infra_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/infra_link.tsx @@ -48,5 +48,5 @@ export const getInfraHref = ({ export function InfraLink({ app, path, query = {}, ...rest }: Props) { const { core } = useApmPluginContext(); const href = getInfraHref({ app, basePath: core.http.basePath, query, path }); - return ; + return ; } diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx index 48bdfc69150a..af5ec49635db 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx @@ -23,6 +23,7 @@ export function MLExplorerLink({ jobId, external, children }: Props) { return ( + {buttonFill ? ( - + {SETUP_INSTRUCTIONS_LABEL} ) : ( - + {ADD_DATA_LABEL} )} diff --git a/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx b/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx index f78bc13ca026..3126a89ed774 100644 --- a/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx @@ -62,6 +62,7 @@ export function MetadataTable({ sections, isLoading }: Props) { - + {i18n.translate('xpack.apm.metadata.help', { defaultMessage: 'How to add labels and other data', diff --git a/x-pack/plugins/apm/public/components/shared/ml_callout/index.tsx b/x-pack/plugins/apm/public/components/shared/ml_callout/index.tsx index 12b4e021e230..474ef3fc327e 100644 --- a/x-pack/plugins/apm/public/components/shared/ml_callout/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ml_callout/index.tsx @@ -57,7 +57,7 @@ export function MLCallout({ | undefined; const getLearnMoreLink = (color: 'primary' | 'success') => ( - + { onCreateJobClick?.(); @@ -113,6 +114,7 @@ export function MLCallout({ icon: 'wrench', primaryAction: isOnSettingsPage ? ( { @@ -147,7 +149,10 @@ export function MLCallout({ icon: 'iInCircle', color: 'primary', primaryAction: ( - + {i18n.translate( 'xpack.apm.settings.anomaly_detection.legacy_jobs.button', { defaultMessage: 'Review jobs' } @@ -173,7 +178,11 @@ export function MLCallout({ )} {dismissable && ( - + {i18n.translate('xpack.apm.mlCallout.dismissButton', { defaultMessage: `Dismiss`, })} diff --git a/x-pack/plugins/apm/public/components/shared/select_with_placeholder/index.tsx b/x-pack/plugins/apm/public/components/shared/select_with_placeholder/index.tsx index 4255ca98d4ea..18f59ac4b731 100644 --- a/x-pack/plugins/apm/public/components/shared/select_with_placeholder/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/select_with_placeholder/index.tsx @@ -25,6 +25,7 @@ export const SelectWithPlaceholder: typeof EuiSelect = (props) => { const placeholder = props.placeholder || DEFAULT_PLACEHOLDER; return (

{ dismissCallout(); }} diff --git a/x-pack/plugins/apm/public/components/shared/span_links/span_links_table.tsx b/x-pack/plugins/apm/public/components/shared/span_links/span_links_table.tsx index 6753c0fd9f2f..f223cf003611 100644 --- a/x-pack/plugins/apm/public/components/shared/span_links/span_links_table.tsx +++ b/x-pack/plugins/apm/public/components/shared/span_links/span_links_table.tsx @@ -102,6 +102,7 @@ export function SpanLinksTable({ items }: Props) {
{(copy) => ( { copy(); setIdActionMenuOpen(undefined); @@ -189,6 +192,7 @@ export function SpanLinksTable({ items }: Props) { {details?.transactionId && ( {(copy) => ( { copy(); setIdActionMenuOpen(undefined); diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap index 2b5427b321ce..05ac11ba496e 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap @@ -11,6 +11,7 @@ exports[`TransactionActionMenu component matches the snapshot 1`] = ` >
- + setDurationPopoverOpenIndex(step.synthetics.step?.index ?? null)} iconType={compactView ? undefined : 'visArea'} > diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx index e03656ed562d..f337a9ba0bed 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx @@ -73,7 +73,13 @@ export function StepFieldTrend({ + {EXPLORE_LABEL} } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx index c5e3f301ed21..847de4412a10 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx @@ -42,7 +42,7 @@ export const simpleAlertEnabled = ( /> - + {i18n.translate('xpack.synthetics.enableAlert.editAlert', { defaultMessage: 'Edit alert', })} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/mapping_error.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/mapping_error.tsx index 9923e102d530..db403eb0dcd9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/mapping_error.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/mapping_error.tsx @@ -61,6 +61,7 @@ export const MappingErrorPage = () => { values={{ docsLink: ( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx index 48e5564621a1..fd0a0feba5a1 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx @@ -28,6 +28,7 @@ export const DisabledCallout = () => {

{CALLOUT_MANAGEMENT_DESCRIPTION}

{enablement.canEnable ? ( { @@ -39,7 +40,7 @@ export const DisabledCallout = () => { ) : (

{CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - + {LEARN_MORE_LABEL}

diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx index aa8b3112955c..f19c2e2af70a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx @@ -23,6 +23,7 @@ export const InvalidApiKeyCalloutCallout = () => {

{CALLOUT_MANAGEMENT_DESCRIPTION}

{enablement.canEnable ? ( { @@ -34,7 +35,11 @@ export const InvalidApiKeyCalloutCallout = () => { ) : (

{CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - + {LEARN_MORE_LABEL}

diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx index ff74fd97f0b3..1418380bfd6a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx @@ -20,7 +20,13 @@ export const ServiceAllowedWrapper: React.FC = ({ children }) => { title={

{MONITOR_MANAGEMENT_LABEL}

} body={

{PUBLIC_BETA_DESCRIPTION}

} actions={[ - + {REQUEST_ACCESS_LABEL} , ]} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/not_found.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/not_found.tsx index c94a7a7a06b6..1d6738210379 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/not_found.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/not_found.tsx @@ -37,7 +37,10 @@ export const NotFoundPage = () => { } body={ - + { { ( {errorMessage} diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx index 776830fd10a7..c1c5503eb1dc 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx @@ -79,6 +79,7 @@ export function SelectedFilters({ - + {I18LABELS.resetZoom} diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx index 674b9593610a..57bd4a51729e 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx @@ -66,6 +66,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { return (