From 97848ca62a35f7ed409ea31269ba124a24a21bc3 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 14 Oct 2021 22:06:43 -0500 Subject: [PATCH 01/18] skip flaky suite. #115100 --- .../list/remove_trusted_app_from_policy_modal.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx index 0411751685a90..917ffe49c6090 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx @@ -25,7 +25,8 @@ import { import { Immutable } from '../../../../../../../common/endpoint/types'; import { HttpFetchOptionsWithPath } from 'kibana/public'; -describe('When using the RemoveTrustedAppFromPolicyModal component', () => { +// FLAKY https://github.com/elastic/kibana/issues/115100 +describe.skip('When using the RemoveTrustedAppFromPolicyModal component', () => { let appTestContext: AppContextTestRender; let renderResult: ReturnType; let render: (waitForLoadedState?: boolean) => Promise>; From 8aeaa5a2ffc871a7802a8d000501539e307a66ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Fri, 15 Oct 2021 09:25:30 +0200 Subject: [PATCH 02/18] [Security Solution][Endpoint] Remove refresh button from policy trusted apps flyout (#114974) * Hide refresh button by prop and refactor unit test * Add test cases for policies selector when enable/disable license * Remove unused code when adding mock --- .../search_exceptions.test.tsx | 112 +++++++++++++----- .../search_exceptions/search_exceptions.tsx | 18 +-- .../flyout/policy_trusted_apps_flyout.tsx | 1 + 3 files changed, 97 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx index ddee8e13f069d..084978d35d03a 100644 --- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx @@ -5,50 +5,77 @@ * 2.0. */ -import { mount } from 'enzyme'; import React from 'react'; +import { act, fireEvent } from '@testing-library/react'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { + EndpointPrivileges, + useEndpointPrivileges, +} from '../../../common/components/user_privileges/use_endpoint_privileges'; +import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { SearchExceptions } from '.'; +import { SearchExceptions, SearchExceptionsProps } from '.'; +jest.mock('../../../common/components/user_privileges/use_endpoint_privileges'); let onSearchMock: jest.Mock; - -interface EuiFieldSearchPropsFake { - onSearch(value: string): void; -} +const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock; describe('Search exceptions', () => { + let appTestContext: AppContextTestRender; + let renderResult: ReturnType; + let render: ( + props?: Partial + ) => ReturnType; + + const loadedUserEndpointPrivilegesState = ( + endpointOverrides: Partial = {} + ): EndpointPrivileges => ({ + loading: false, + canAccessFleet: true, + canAccessEndpointManagement: true, + isPlatinumPlus: false, + ...endpointOverrides, + }); + beforeEach(() => { onSearchMock = jest.fn(); + appTestContext = createAppRootMockRenderer(); + + render = (overrideProps = {}) => { + const props: SearchExceptionsProps = { + placeholder: 'search test', + onSearch: onSearchMock, + ...overrideProps, + }; + + renderResult = appTestContext.render(); + return renderResult; + }; + + mockUseEndpointPrivileges.mockReturnValue(loadedUserEndpointPrivilegesState()); }); - const getElement = (defaultValue: string = '') => ( - - ); + afterAll(() => { + mockUseEndpointPrivileges.mockReset(); + }); it('should have a default value', () => { const expectedDefaultValue = 'this is a default value'; - const element = mount(getElement(expectedDefaultValue)); - const defaultValue = element - .find('[data-test-subj="searchField"]') - .first() - .props().defaultValue; - expect(defaultValue).toBe(expectedDefaultValue); + const element = render({ defaultValue: expectedDefaultValue }); + + expect(element.getByDisplayValue(expectedDefaultValue)).not.toBeNull(); }); it('should dispatch search action when submit search field', () => { const expectedDefaultValue = 'this is a default value'; - const element = mount(getElement()); + const element = render(); expect(onSearchMock).toHaveBeenCalledTimes(0); - const searchFieldProps = element - .find('[data-test-subj="searchField"]') - .first() - .props() as EuiFieldSearchPropsFake; - searchFieldProps.onSearch(expectedDefaultValue); + act(() => { + fireEvent.change(element.getByTestId('searchField'), { + target: { value: expectedDefaultValue }, + }); + }); expect(onSearchMock).toHaveBeenCalledTimes(1); expect(onSearchMock).toHaveBeenCalledWith(expectedDefaultValue, '', ''); @@ -56,11 +83,42 @@ describe('Search exceptions', () => { it('should dispatch search action when click on button', () => { const expectedDefaultValue = 'this is a default value'; - const element = mount(getElement(expectedDefaultValue)); + const element = render({ defaultValue: expectedDefaultValue }); expect(onSearchMock).toHaveBeenCalledTimes(0); - element.find('[data-test-subj="searchButton"]').first().simulate('click'); + act(() => { + fireEvent.click(element.getByTestId('searchButton')); + }); + expect(onSearchMock).toHaveBeenCalledTimes(1); expect(onSearchMock).toHaveBeenCalledWith(expectedDefaultValue, '', ''); }); + + it('should hide refresh button', () => { + const element = render({ hideRefreshButton: true }); + + expect(element.queryByTestId('searchButton')).toBeNull(); + }); + + it('should hide policies selector when no license', () => { + const generator = new EndpointDocGenerator('policy-list'); + const policy = generator.generatePolicyPackagePolicy(); + mockUseEndpointPrivileges.mockReturnValue( + loadedUserEndpointPrivilegesState({ isPlatinumPlus: false }) + ); + const element = render({ policyList: [policy], hasPolicyFilter: true }); + + expect(element.queryByTestId('policiesSelectorButton')).toBeNull(); + }); + + it('should display policies selector when right license', () => { + const generator = new EndpointDocGenerator('policy-list'); + const policy = generator.generatePolicyPackagePolicy(); + mockUseEndpointPrivileges.mockReturnValue( + loadedUserEndpointPrivilegesState({ isPlatinumPlus: true }) + ); + const element = render({ policyList: [policy], hasPolicyFilter: true }); + + expect(element.queryByTestId('policiesSelectorButton')).not.toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx index 2b7b2e6b66884..1f3eab5db2947 100644 --- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx +++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx @@ -19,6 +19,7 @@ export interface SearchExceptionsProps { policyList?: ImmutableArray; defaultExcludedPolicies?: string; defaultIncludedPolicies?: string; + hideRefreshButton?: boolean; onSearch(query: string, includedPolicies?: string, excludedPolicies?: string): void; } @@ -31,6 +32,7 @@ export const SearchExceptions = memo( policyList, defaultIncludedPolicies, defaultExcludedPolicies, + hideRefreshButton = false, }) => { const { isPlatinumPlus } = useEndpointPrivileges(); const [query, setQuery] = useState(defaultValue); @@ -101,13 +103,15 @@ export const SearchExceptions = memo( ) : null} - - - {i18n.translate('xpack.securitySolution.management.search.button', { - defaultMessage: 'Refresh', - })} - - + {!hideRefreshButton ? ( + + + {i18n.translate('xpack.securitySolution.management.search.button', { + defaultMessage: 'Refresh', + })} + + + ) : null} ); } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx index f5880022383f9..bbf2f3b208754 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx @@ -183,6 +183,7 @@ export const PolicyTrustedAppsFlyout = React.memo(() => { defaultMessage: 'Search trusted applications', } )} + hideRefreshButton /> From 008421f170214648336bd9d5afb5cc794efbd82c Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Fri, 15 Oct 2021 09:33:06 +0200 Subject: [PATCH 03/18] [APM] Revert multi-metric ML job. (#114961) --- .../plugins/apm/common/anomaly_detection.ts | 2 - .../apm/common/utils/apm_ml_anomaly_query.ts | 25 ----- .../create_anomaly_detection_jobs.ts | 20 ++-- .../lib/service_map/get_service_anomalies.ts | 9 +- .../transactions/get_anomaly_data/fetcher.ts | 4 +- .../modules/apm_transaction/manifest.json | 18 ++-- .../apm_transaction/ml/apm_metrics.json | 53 ----------- .../ml/datafeed_apm_metrics.json | 95 ------------------- ...tafeed_high_mean_transaction_duration.json | 14 +++ .../ml/high_mean_transaction_duration.json | 35 +++++++ .../apis/ml/modules/recognize_module.ts | 2 +- .../apis/ml/modules/setup_module.ts | 10 +- 12 files changed, 76 insertions(+), 211 deletions(-) delete mode 100644 x-pack/plugins/apm/common/utils/apm_ml_anomaly_query.ts delete mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_metrics.json delete mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_metrics.json create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json diff --git a/x-pack/plugins/apm/common/anomaly_detection.ts b/x-pack/plugins/apm/common/anomaly_detection.ts index eb7c74a4540be..43a779407d2a4 100644 --- a/x-pack/plugins/apm/common/anomaly_detection.ts +++ b/x-pack/plugins/apm/common/anomaly_detection.ts @@ -33,8 +33,6 @@ export function getSeverityColor(score: number) { return mlGetSeverityColor(score); } -export const ML_TRANSACTION_LATENCY_DETECTOR_INDEX = 0; - export const ML_ERRORS = { INVALID_LICENSE: i18n.translate( 'xpack.apm.anomaly_detection.error.invalid_license', diff --git a/x-pack/plugins/apm/common/utils/apm_ml_anomaly_query.ts b/x-pack/plugins/apm/common/utils/apm_ml_anomaly_query.ts deleted file mode 100644 index 26b859d37cf7f..0000000000000 --- a/x-pack/plugins/apm/common/utils/apm_ml_anomaly_query.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export function apmMlAnomalyQuery(detectorIndex: 0 | 1 | 2) { - return [ - { - bool: { - filter: [ - { - terms: { - result_type: ['model_plot', 'record'], - }, - }, - { - term: { detector_index: detectorIndex }, - }, - ], - }, - }, - ]; -} diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 10758b6d90cdc..4d4bc8dc185ab 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -5,21 +5,21 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { Logger } from 'kibana/server'; +import uuid from 'uuid/v4'; import { snakeCase } from 'lodash'; +import Boom from '@hapi/boom'; import moment from 'moment'; -import uuid from 'uuid/v4'; import { ML_ERRORS } from '../../../common/anomaly_detection'; -import { - METRICSET_NAME, - PROCESSOR_EVENT, -} from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { withApmSpan } from '../../utils/with_apm_span'; import { Setup } from '../helpers/setup_request'; +import { + TRANSACTION_DURATION, + PROCESSOR_EVENT, +} from '../../../common/elasticsearch_fieldnames'; import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; +import { withApmSpan } from '../../utils/with_apm_span'; import { getAnomalyDetectionJobs } from './get_anomaly_detection_jobs'; export async function createAnomalyDetectionJobs( @@ -92,8 +92,8 @@ async function createAnomalyDetectionJob({ query: { bool: { filter: [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.metric } }, - { term: { [METRICSET_NAME]: 'transaction' } }, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + { exists: { field: TRANSACTION_DURATION } }, ...environmentQuery(environment), ], }, @@ -105,7 +105,7 @@ async function createAnomalyDetectionJob({ job_tags: { environment, // identifies this as an APM ML job & facilitates future migrations - apm_ml_version: 3, + apm_ml_version: 2, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 97c95e4e40045..9b2d79dc726ee 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -11,11 +11,7 @@ import { estypes } from '@elastic/elasticsearch'; import { ESSearchResponse } from '../../../../../../src/core/types/elasticsearch'; import { MlPluginSetup } from '../../../../ml/server'; import { PromiseReturnType } from '../../../../observability/typings/common'; -import { - getSeverity, - ML_ERRORS, - ML_TRANSACTION_LATENCY_DETECTOR_INDEX, -} from '../../../common/anomaly_detection'; +import { getSeverity, ML_ERRORS } from '../../../common/anomaly_detection'; import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { getServiceHealthStatus } from '../../../common/service_health_status'; import { @@ -26,7 +22,6 @@ import { rangeQuery } from '../../../../observability/server'; import { withApmSpan } from '../../utils/with_apm_span'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; import { Setup } from '../helpers/setup_request'; -import { apmMlAnomalyQuery } from '../../../common/utils/apm_ml_anomaly_query'; export const DEFAULT_ANOMALIES: ServiceAnomaliesResponse = { mlJobIds: [], @@ -61,7 +56,7 @@ export async function getServiceAnomalies({ query: { bool: { filter: [ - ...apmMlAnomalyQuery(ML_TRANSACTION_LATENCY_DETECTOR_INDEX), + { terms: { result_type: ['model_plot', 'record'] } }, ...rangeQuery( Math.min(end - 30 * 60 * 1000, start), end, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index a7357bbc1dd34..a61e0614f5b1a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -12,8 +12,6 @@ import { rangeQuery } from '../../../../../observability/server'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; -import { apmMlAnomalyQuery } from '../../../../common/utils/apm_ml_anomaly_query'; -import { ML_TRANSACTION_LATENCY_DETECTOR_INDEX } from '../../../../common/anomaly_detection'; export type ESResponse = Exclude< PromiseReturnType, @@ -42,7 +40,7 @@ export function anomalySeriesFetcher({ query: { bool: { filter: [ - ...apmMlAnomalyQuery(ML_TRANSACTION_LATENCY_DETECTOR_INDEX), + { terms: { result_type: ['model_plot', 'record'] } }, { term: { partition_field_value: serviceName } }, { term: { by_field_value: transactionType } }, ...rangeQuery(start, end, 'timestamp'), diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json index 123232935df05..f8feaef3be5f8 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json @@ -1,29 +1,29 @@ { "id": "apm_transaction", "title": "APM", - "description": "Detect anomalies in transactions from your APM services for metric data.", + "description": "Detect anomalies in transactions from your APM services.", "type": "Transaction data", "logoFile": "logo.json", - "defaultIndexPattern": "apm-*-metric,metrics-apm*", + "defaultIndexPattern": "apm-*-transaction", "query": { "bool": { "filter": [ - { "term": { "processor.event": "metric" } }, - { "term": { "metricset.name": "transaction" } } + { "term": { "processor.event": "transaction" } }, + { "exists": { "field": "transaction.duration" } } ] } }, "jobs": [ { - "id": "apm_metrics", - "file": "apm_metrics.json" + "id": "high_mean_transaction_duration", + "file": "high_mean_transaction_duration.json" } ], "datafeeds": [ { - "id": "datafeed-apm_metrics", - "file": "datafeed_apm_metrics.json", - "job_id": "apm_metrics" + "id": "datafeed-high_mean_transaction_duration", + "file": "datafeed_high_mean_transaction_duration.json", + "job_id": "high_mean_transaction_duration" } ] } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_metrics.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_metrics.json deleted file mode 100644 index d5092f3ffc553..0000000000000 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_metrics.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "job_type": "anomaly_detector", - "groups": [ - "apm" - ], - "description": "Detects anomalies in transaction duration, throughput and error percentage for metric data.", - "analysis_config": { - "bucket_span": "15m", - "summary_count_field_name" : "doc_count", - "detectors" : [ - { - "detector_description" : "high duration by transaction type for an APM service", - "function" : "high_mean", - "field_name" : "transaction_duration", - "by_field_name" : "transaction.type", - "partition_field_name" : "service.name" - }, - { - "detector_description" : "transactions per minute for an APM service", - "function" : "mean", - "field_name" : "transactions_per_min", - "by_field_name" : "transaction.type", - "partition_field_name" : "service.name" - }, - { - "detector_description" : "percent failed for an APM service", - "function" : "high_mean", - "field_name" : "transaction_failure_percentage", - "by_field_name" : "transaction.type", - "partition_field_name" : "service.name" - } - ], - "influencers" : [ - "transaction.type", - "service.name" - ] - }, - "analysis_limits": { - "model_memory_limit": "32mb" - }, - "data_description": { - "time_field" : "@timestamp", - "time_format" : "epoch_ms" - }, - "model_plot_config": { - "enabled" : true, - "annotations_enabled" : true - }, - "results_index_name" : "custom-apm", - "custom_settings": { - "created_by": "ml-module-apm-transaction" - } -} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_metrics.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_metrics.json deleted file mode 100644 index ba45582252cd7..0000000000000 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_metrics.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "job_id": "JOB_ID", - "indices": [ - "INDEX_PATTERN_NAME" - ], - "chunking_config" : { - "mode" : "off" - }, - "query": { - "bool": { - "filter": [ - { "term": { "processor.event": "metric" } }, - { "term": { "metricset.name": "transaction" } } - ] - } - }, - "aggregations" : { - "buckets" : { - "composite" : { - "size" : 5000, - "sources" : [ - { - "date" : { - "date_histogram" : { - "field" : "@timestamp", - "fixed_interval" : "90s" - } - } - }, - { - "transaction.type" : { - "terms" : { - "field" : "transaction.type" - } - } - }, - { - "service.name" : { - "terms" : { - "field" : "service.name" - } - } - } - ] - }, - "aggs" : { - "@timestamp" : { - "max" : { - "field" : "@timestamp" - } - }, - "transactions_per_min" : { - "rate" : { - "unit" : "minute" - } - }, - "transaction_duration" : { - "avg" : { - "field" : "transaction.duration.histogram" - } - }, - "error_count" : { - "filter" : { - "term" : { - "event.outcome" : "failure" - } - }, - "aggs" : { - "actual_error_count" : { - "value_count" : { - "field" : "event.outcome" - } - } - } - }, - "success_count" : { - "filter" : { - "term" : { - "event.outcome" : "success" - } - } - }, - "transaction_failure_percentage" : { - "bucket_script" : { - "buckets_path" : { - "failure_count" : "error_count>_count", - "success_count" : "success_count>_count" - }, - "script" : "if ((params.failure_count + params.success_count)==0){return 0;}else{return params.failure_count/(params.failure_count + params.success_count);}" - } - } - } - } - } -} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json new file mode 100644 index 0000000000000..d312577902f51 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json @@ -0,0 +1,14 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "processor.event": "transaction" } }, + { "exists": { "field": "transaction.duration.us" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json new file mode 100644 index 0000000000000..77284cb275cd8 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json @@ -0,0 +1,35 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "Detect transaction duration anomalies across transaction types for your APM services.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high duration by transaction type for an APM service", + "function": "high_mean", + "field_name": "transaction.duration.us", + "by_field_name": "transaction.type", + "partition_field_name": "service.name" + } + ], + "influencers": [ + "transaction.type", + "service.name" + ] + }, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-apm-transaction" + } +} diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index 00b820a025c8b..2742fbff294c0 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -44,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { user: USER.ML_POWERUSER, expected: { responseCode: 200, - moduleIds: ['apm_jsbase', 'apm_nodejs'], + moduleIds: ['apm_jsbase', 'apm_transaction', 'apm_nodejs'], }, }, { diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 6ff6b8113cb1a..c4dd529ac14f5 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -187,11 +187,9 @@ export default ({ getService }: FtrProviderContext) => { dashboards: [] as string[], }, }, - // Set startDatafeed and estimateModelMemory to false for the APM transaction test - // until there is a new data set available with metric data. { testTitleSuffix: - 'for apm_transaction with prefix, startDatafeed false and estimateModelMemory false', + 'for apm_transaction with prefix, startDatafeed true and estimateModelMemory true', sourceDataArchive: 'x-pack/test/functional/es_archives/ml/module_apm', indexPattern: { name: 'ft_module_apm', timeField: '@timestamp' }, module: 'apm_transaction', @@ -199,14 +197,14 @@ export default ({ getService }: FtrProviderContext) => { requestBody: { prefix: 'pf5_', indexPatternName: 'ft_module_apm', - startDatafeed: false, - estimateModelMemory: false, + startDatafeed: true, + end: Date.now(), }, expected: { responseCode: 200, jobs: [ { - jobId: 'pf5_apm_metrics', + jobId: 'pf5_high_mean_transaction_duration', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, }, From 402550c1654f8d6e0383440bba8e3db150499171 Mon Sep 17 00:00:00 2001 From: Giorgos Bamparopoulos Date: Fri, 15 Oct 2021 09:16:56 +0100 Subject: [PATCH 04/18] Update APM Plugin Routing and Linking (#115008) * Update client-side and server-side routing function names and files --- x-pack/plugins/apm/dev_docs/routing_and_linking.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/dev_docs/routing_and_linking.md b/x-pack/plugins/apm/dev_docs/routing_and_linking.md index 478de0081fca4..af22bcdbdfa11 100644 --- a/x-pack/plugins/apm/dev_docs/routing_and_linking.md +++ b/x-pack/plugins/apm/dev_docs/routing_and_linking.md @@ -6,15 +6,15 @@ This document describes routing in the APM plugin. ### Server-side -Route definitions for APM's server-side API are in the [server/routes directory](../server/routes). Routes are created with [the `createRoute` function](../server/routes/create_route.ts). Routes are added to the API in [the `createApmApi` function](../server/routes/create_apm_api.ts), which is initialized in the plugin `start` lifecycle method. +Route definitions for APM's server-side API are in the [server/routes directory](../server/routes). Routes are created with [the `createApmServerRoute` function](../server/routes/create_apm_server_route.ts). Routes are added to the API in [the `registerRoutes` function](../server/routes/register_routes.ts), which is initialized in the plugin `setup` lifecycle method. -The path and query string parameters are defined in the calls to `createRoute` with io-ts types, so that each route has its parameters type checked. +The path and query string parameters are defined in the calls to `createApmServerRoute` with io-ts types, so that each route has its parameters type checked. ### Client-side The client-side routing uses `@kbn/typed-react-router-config`, which is a wrapper around [React Router](https://reactrouter.com/) and [React Router Config](https://www.npmjs.com/package/react-router-config). Its goal is to provide a layer of high-fidelity types that allows us to parse and format URLs for routes while making sure the needed parameters are provided and/or available (typed and validated at runtime). The `history` object used by React Router is injected by the Kibana Platform. -Routes (and their parameters) are defined in [public/components/routing/apm_config.tsx](../public/components/routing/apm_config.tsx). +Routes (and their parameters) are defined in [public/components/routing/apm_route_config.tsx](../public/components/routing/apm_route_config.tsx). #### Parameter handling From 00db6023e6af7c69f1462738289f824a8dd67a64 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 15 Oct 2021 10:20:30 +0200 Subject: [PATCH 05/18] [deps] Renovate-bot default to draftPR and datavis reviewers (#114060) New renovate-bot PRs are created as draft PR for elastic-charts. The PR will now ping the whole datavis team. --- renovate.json5 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 76923f01daba0..ab33ba7b844ee 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -37,9 +37,10 @@ { groupName: '@elastic/charts', packageNames: ['@elastic/charts'], - reviewers: ['markov00', 'nickofthyme'], + reviewers: ['team:datavis'], matchBaseBranches: ['master'], labels: ['release_note:skip', 'v8.0.0', 'v7.16.0', 'auto-backport'], + draftPR: true, enabled: true, }, { From 0fa440abad2592be442d88e557044dbfcdfc168a Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 15 Oct 2021 12:01:42 +0300 Subject: [PATCH 06/18] [Vega] Replacing the 'interval' property should only happen for the date_histogram aggregation (#115001) --- .../public/data_model/es_query_parser.test.js | 16 ++++++++++++++-- .../vega/public/data_model/es_query_parser.ts | 8 ++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/plugins/vis_types/vega/public/data_model/es_query_parser.test.js b/src/plugins/vis_types/vega/public/data_model/es_query_parser.test.js index bb3c0276f4cf9..214a23d2ee935 100644 --- a/src/plugins/vis_types/vega/public/data_model/es_query_parser.test.js +++ b/src/plugins/vis_types/vega/public/data_model/es_query_parser.test.js @@ -178,11 +178,23 @@ describe(`EsQueryParser.injectQueryContextVars`, () => { ); test( `%autointerval% = true`, - check({ interval: { '%autointerval%': true } }, { calendar_interval: `1h` }, ctxObj) + check( + { date_histogram: { interval: { '%autointerval%': true } } }, + { date_histogram: { calendar_interval: `1h` } }, + ctxObj + ) ); test( `%autointerval% = 10`, - check({ interval: { '%autointerval%': 10 } }, { fixed_interval: `3h` }, ctxObj) + check( + { date_histogram: { interval: { '%autointerval%': 10 } } }, + { date_histogram: { fixed_interval: `3h` } }, + ctxObj + ) + ); + test( + `histogram with interval`, + check({ histogram: { interval: 1 } }, { histogram: { interval: 1 } }, ctxObj) ); test(`%timefilter% = min`, check({ a: { '%timefilter%': 'min' } }, { a: rangeStart })); test(`%timefilter% = max`, check({ a: { '%timefilter%': 'max' } }, { a: rangeEnd })); diff --git a/src/plugins/vis_types/vega/public/data_model/es_query_parser.ts b/src/plugins/vis_types/vega/public/data_model/es_query_parser.ts index 134e82d676763..7f6ca05df3d7a 100644 --- a/src/plugins/vis_types/vega/public/data_model/es_query_parser.ts +++ b/src/plugins/vis_types/vega/public/data_model/es_query_parser.ts @@ -235,7 +235,8 @@ export class EsQueryParser { interval?: { '%autointerval%': true | number } | string; } >, - isQuery: boolean + isQuery: boolean, + key?: string ) { if (obj && typeof obj === 'object') { if (Array.isArray(obj)) { @@ -281,9 +282,8 @@ export class EsQueryParser { if (!subObj || typeof obj !== 'object') continue; // replace "interval" with ES acceptable fixed_interval / calendar_interval - if (prop === 'interval') { + if (prop === 'interval' && key === 'date_histogram') { let intervalString: string; - if (typeof subObj === 'string') { intervalString = subObj; } else if (subObj[AUTOINTERVAL]) { @@ -322,7 +322,7 @@ export class EsQueryParser { this._createRangeFilter(subObj); continue; case undefined: - this._injectContextVars(subObj, isQuery); + this._injectContextVars(subObj, isQuery, prop); continue; default: throw new Error( From c11b38de7bba8a805a7979ea9095858d580b22cf Mon Sep 17 00:00:00 2001 From: mgiota Date: Fri, 15 Oct 2021 11:07:47 +0200 Subject: [PATCH 07/18] [RAC] create functional tests for add to case (#114075) * [RAC] create functional tests for add to case * use observability test helpers for user creation * basic tests for add to case options * add two more cases * test case for clicking on add to new case button * remove unused expect statement * clicking on add to existing case should open a modal * move add to case functionality in a separate file * address comments in the PR review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../cases/add_to_existing_case_button.tsx | 2 +- .../timeline/cases/add_to_new_case_button.tsx | 2 +- .../observability/alerts/add_to_case.ts | 75 +++++++++++++++ .../services/observability/alerts/common.ts | 1 + .../services/observability/alerts/index.ts | 4 +- .../apps/observability/alerts/add_to_case.ts | 92 +++++++++++++++++++ .../apps/observability/index.ts | 1 + 7 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 x-pack/test/functional/services/observability/alerts/add_to_case.ts create mode 100644 x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx index af19a6b7cdb74..30181a96aa70b 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx @@ -32,7 +32,7 @@ const AddToCaseActionComponent: React.FC = ({ {userCanCrud && ( = ({ {userCanCrud && ( { + return await testSubjects.find(ADD_TO_EXISTING_CASE_SELECTOR); + }; + + const getAddToExistingCaseSelectorOrFail = async () => { + return await testSubjects.existOrFail(ADD_TO_EXISTING_CASE_SELECTOR); + }; + + const missingAddToExistingCaseSelectorOrFail = async () => { + return await testSubjects.missingOrFail(ADD_TO_EXISTING_CASE_SELECTOR); + }; + + const getAddToNewCaseSelector = async () => { + return await testSubjects.find(ADD_TO_NEW_CASE_SELECTOR); + }; + + const getAddToNewCaseSelectorOrFail = async () => { + return await testSubjects.existOrFail(ADD_TO_NEW_CASE_SELECTOR); + }; + + const missingAddToNewCaseSelectorOrFail = async () => { + return await testSubjects.missingOrFail(ADD_TO_NEW_CASE_SELECTOR); + }; + + const addToNewCaseButtonClick = async () => { + return await (await getAddToNewCaseSelector()).click(); + }; + + const addToExistingCaseButtonClick = async () => { + return await (await getAddToExistingCaseSelector()).click(); + }; + + const getCreateCaseFlyoutOrFail = async () => { + return await testSubjects.existOrFail(CREATE_CASE_FLYOUT); + }; + + const closeFlyout = async () => { + return await (await testSubjects.find('euiFlyoutCloseButton')).click(); + }; + + const getAddtoExistingCaseModalOrFail = async () => { + return await testSubjects.existOrFail(SELECT_CASE_MODAL); + }; + + return { + getAddToExistingCaseSelector, + getAddToExistingCaseSelectorOrFail, + missingAddToExistingCaseSelectorOrFail, + getAddToNewCaseSelector, + getAddToNewCaseSelectorOrFail, + missingAddToNewCaseSelectorOrFail, + getCreateCaseFlyoutOrFail, + closeFlyout, + addToNewCaseButtonClick, + addToExistingCaseButtonClick, + getAddtoExistingCaseModalOrFail, + }; +} diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index 7098fdec2a9d4..d5a2ce2a18c41 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -204,5 +204,6 @@ export function ObservabilityAlertsCommonProvider({ setWorkflowStatusFilter, submitQuery, typeInQueryBar, + openActionsMenuForRow, }; } diff --git a/x-pack/test/functional/services/observability/alerts/index.ts b/x-pack/test/functional/services/observability/alerts/index.ts index f373b0d75c543..f2b5173dfe5b0 100644 --- a/x-pack/test/functional/services/observability/alerts/index.ts +++ b/x-pack/test/functional/services/observability/alerts/index.ts @@ -7,15 +7,17 @@ import { ObservabilityAlertsPaginationProvider } from './pagination'; import { ObservabilityAlertsCommonProvider } from './common'; +import { ObservabilityAlertsAddToCaseProvider } from './add_to_case'; import { FtrProviderContext } from '../../../ftr_provider_context'; export function ObservabilityAlertsProvider(context: FtrProviderContext) { const common = ObservabilityAlertsCommonProvider(context); const pagination = ObservabilityAlertsPaginationProvider(context); - + const addToCase = ObservabilityAlertsAddToCaseProvider(context); return { common, pagination, + addToCase, }; } diff --git a/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts b/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts new file mode 100644 index 0000000000000..f29111f2cb66b --- /dev/null +++ b/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, getPageObjects }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const observability = getService('observability'); + const retry = getService('retry'); + + describe('Observability alerts / Add to case', function () { + this.tags('includeFirefox'); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + }); + + describe('When user has all priviledges for cases', () => { + before(async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + observabilityCases: ['all'], + logs: ['all'], + }) + ); + await observability.alerts.common.navigateToTimeWithData(); + }); + + after(async () => { + await observability.users.restoreDefaultTestUserRole(); + }); + + it('renders case options in the overflow menu', async () => { + await observability.alerts.common.openActionsMenuForRow(0); + await retry.try(async () => { + await observability.alerts.addToCase.getAddToExistingCaseSelectorOrFail(); + await observability.alerts.addToCase.getAddToNewCaseSelectorOrFail(); + }); + }); + + it('opens a flyout when Add to new case is clicked', async () => { + await observability.alerts.addToCase.addToNewCaseButtonClick(); + + await retry.try(async () => { + await observability.alerts.addToCase.getCreateCaseFlyoutOrFail(); + await observability.alerts.addToCase.closeFlyout(); + }); + }); + + it('opens a modal when Add to existing case is clicked', async () => { + await observability.alerts.common.openActionsMenuForRow(0); + + await retry.try(async () => { + await observability.alerts.addToCase.addToExistingCaseButtonClick(); + await observability.alerts.addToCase.getAddtoExistingCaseModalOrFail(); + }); + }); + }); + + describe('When user has read permissions for cases', () => { + before(async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + observabilityCases: ['read'], + logs: ['all'], + }) + ); + await observability.alerts.common.navigateToTimeWithData(); + }); + + after(async () => { + await observability.users.restoreDefaultTestUserRole(); + }); + + it('does not render case options in the overflow menu', async () => { + await observability.alerts.common.openActionsMenuForRow(0); + await retry.try(async () => { + await observability.alerts.addToCase.missingAddToExistingCaseSelectorOrFail(); + await observability.alerts.addToCase.missingAddToNewCaseSelectorOrFail(); + }); + }); + }); + }); +}; diff --git a/x-pack/test/observability_functional/apps/observability/index.ts b/x-pack/test/observability_functional/apps/observability/index.ts index b163d4d6bb8d5..43e056bae65c0 100644 --- a/x-pack/test/observability_functional/apps/observability/index.ts +++ b/x-pack/test/observability_functional/apps/observability/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./alerts')); loadTestFile(require.resolve('./alerts/workflow_status')); loadTestFile(require.resolve('./alerts/pagination')); + loadTestFile(require.resolve('./alerts/add_to_case')); }); } From d19510535a4774a58ac37384ad0b09b99f821399 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Fri, 15 Oct 2021 12:09:19 +0300 Subject: [PATCH 08/18] [i18n] remove angular i18n and move the remains to monitoring plugin (#115003) --- .../external-plugin-localization.asciidoc | 21 --- package.json | 2 - packages/kbn-i18n/BUILD.bazel | 2 - packages/kbn-i18n/GUIDELINE.md | 57 ++----- packages/kbn-i18n/README.md | 92 ----------- packages/kbn-i18n/angular/package.json | 5 - .../__snapshots__/directive.test.ts.snap | 69 -------- .../kbn-i18n/src/angular/directive.test.ts | 150 ------------------ packages/kbn-i18n/src/angular/filter.test.ts | 46 ------ .../kbn-i18n/src/angular/provider.test.ts | 47 ------ packages/kbn-ui-shared-deps-src/src/entry.js | 1 - packages/kbn-ui-shared-deps-src/src/index.js | 1 - src/dev/i18n/README.md | 28 ---- x-pack/.i18nrc.json | 2 +- .../public/angular/angular_i18n}/directive.ts | 5 +- .../public/angular/angular_i18n}/filter.ts | 5 +- .../public/angular/angular_i18n}/index.ts | 5 +- .../public/angular/angular_i18n}/provider.ts | 7 +- .../monitoring/public/angular/app_modules.ts | 2 +- yarn.lock | 14 +- 20 files changed, 23 insertions(+), 538 deletions(-) delete mode 100644 packages/kbn-i18n/angular/package.json delete mode 100644 packages/kbn-i18n/src/angular/__snapshots__/directive.test.ts.snap delete mode 100644 packages/kbn-i18n/src/angular/directive.test.ts delete mode 100644 packages/kbn-i18n/src/angular/filter.test.ts delete mode 100644 packages/kbn-i18n/src/angular/provider.test.ts rename {packages/kbn-i18n/src/angular => x-pack/plugins/monitoring/public/angular/angular_i18n}/directive.ts (94%) rename {packages/kbn-i18n/src/angular => x-pack/plugins/monitoring/public/angular/angular_i18n}/filter.ts (71%) rename {packages/kbn-i18n/src/angular => x-pack/plugins/monitoring/public/angular/angular_i18n}/index.ts (71%) rename {packages/kbn-i18n/src/angular => x-pack/plugins/monitoring/public/angular/angular_i18n}/provider.ts (78%) diff --git a/docs/developer/plugin/external-plugin-localization.asciidoc b/docs/developer/plugin/external-plugin-localization.asciidoc index d30dec1a8f46b..656dff90fe0de 100644 --- a/docs/developer/plugin/external-plugin-localization.asciidoc +++ b/docs/developer/plugin/external-plugin-localization.asciidoc @@ -135,27 +135,6 @@ export const Component = () => { Full details are {kib-repo}tree/master/packages/kbn-i18n#react[here]. - - -[discrete] -==== i18n for Angular - -You are encouraged to use `i18n.translate()` by statically importing `i18n` from `@kbn/i18n` wherever possible in your Angular code. Angular wrappers use the translation `service` with the i18n engine under the hood. - -The translation directive has the following syntax: -["source","js"] ------------ - ------------ - -Full details are {kib-repo}tree/master/packages/kbn-i18n#angularjs[here]. - - [discrete] === Resources diff --git a/package.json b/package.json index f526f357ff347..9341ecd0bae35 100644 --- a/package.json +++ b/package.json @@ -489,7 +489,6 @@ "@testing-library/react-hooks": "^5.1.1", "@testing-library/user-event": "^13.1.1", "@types/angular": "^1.6.56", - "@types/angular-mocks": "^1.7.0", "@types/apidoc": "^0.22.3", "@types/archiver": "^5.1.0", "@types/babel__core": "^7.1.16", @@ -644,7 +643,6 @@ "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", "aggregate-error": "^3.1.0", - "angular-mocks": "^1.7.9", "antlr4ts-cli": "^0.5.0-alpha.3", "apidoc": "^0.29.0", "apidoc-markdown": "^6.0.0", diff --git a/packages/kbn-i18n/BUILD.bazel b/packages/kbn-i18n/BUILD.bazel index 49d5603b2c516..256262bb8783b 100644 --- a/packages/kbn-i18n/BUILD.bazel +++ b/packages/kbn-i18n/BUILD.bazel @@ -27,7 +27,6 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "angular/package.json", "react/package.json", "package.json", "GUIDELINE.md", @@ -47,7 +46,6 @@ TYPES_DEPS = [ "//packages/kbn-babel-preset", "@npm//intl-messageformat", "@npm//tslib", - "@npm//@types/angular", "@npm//@types/intl-relativeformat", "@npm//@types/jest", "@npm//@types/prop-types", diff --git a/packages/kbn-i18n/GUIDELINE.md b/packages/kbn-i18n/GUIDELINE.md index 806e799bd1106..7ffc4b078c79b 100644 --- a/packages/kbn-i18n/GUIDELINE.md +++ b/packages/kbn-i18n/GUIDELINE.md @@ -93,17 +93,6 @@ The long term plan is to rely on using `FormattedMessage` and `i18n.translate()` Currently, we support the following ReactJS `i18n` tools, but they will be removed in future releases: - Usage of `props.intl.formatmessage()` (where `intl` is passed to `props` by `injectI18n` HOC). -#### In AngularJS - -The long term plan is to rely on using `i18n.translate()` by statically importing `i18n` from the `@kbn/i18n` package. **Avoid using the `i18n` filter and the `i18n` service injected in controllers, directives, services.** - -- Call JS function `i18n.translate()` from the `@kbn/i18n` package. -- Use `i18nId` directive in template. - -Currently, we support the following AngluarJS `i18n` tools, but they will be removed in future releases: -- Usage of `i18n` service in controllers, directives, services by injecting it. -- Usage of `i18n` filter in template for attribute translation. Note: Use one-time binding ("{{:: ... }}") in filters wherever it's possible to prevent unnecessary expression re-evaluation. - #### In JavaScript - Use `i18n.translate()` in NodeJS or any other framework agnostic code, where `i18n` is the I18n engine from `@kbn/i18n` package. @@ -223,7 +212,6 @@ For example: - for button: ```js - @@ -232,11 +220,11 @@ For example: - for dropDown: ```js - +