diff --git a/x-pack/plugins/apm/common/service_inventory.ts b/x-pack/plugins/apm/common/service_inventory.ts new file mode 100644 index 0000000000000..b7c8c0ea90a58 --- /dev/null +++ b/x-pack/plugins/apm/common/service_inventory.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AgentName } from '../typings/es_schemas/ui/fields/agent'; +import { ServiceHealthStatus } from './service_health_status'; + +export interface ServiceListItem { + serviceName: string; + healthStatus?: ServiceHealthStatus; + transactionType?: string; + agentName?: AgentName; + throughput?: number; + latency?: number | null; + transactionErrorRate?: number | null; + environments?: string[]; +} diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 1e736409a9604..cc4cbd975f5cd 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -12,21 +12,20 @@ import uuid from 'uuid'; import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useLocalStorage } from '../../../hooks/use_local_storage'; -import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; import { SearchBar } from '../../shared/search_bar'; import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison'; import { ServiceList } from './service_list'; import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout'; +import { joinByKey } from '../../../../common/utils/join_by_key'; const initialData = { requestId: '', - mainStatisticsData: { - items: [], - hasHistoricalData: true, - hasLegacyData: false, - }, + items: [], + hasHistoricalData: true, + hasLegacyData: false, }; function useServicesFetcher() { @@ -36,7 +35,7 @@ function useServicesFetcher() { const { query: { rangeFrom, rangeTo, environment, kuery }, - } = useAnyOfApmParams('/services/{serviceName}', '/services'); + } = useApmParams('/services'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); @@ -47,7 +46,23 @@ function useServicesFetcher() { comparisonType, }); - const { data = initialData, status: mainStatisticsStatus } = useFetcher( + const sortedAndFilteredServicesFetch = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/sorted_and_filtered_services', { + params: { + query: { + start, + end, + environment, + kuery, + }, + }, + }); + }, + [start, end, environment, kuery] + ); + + const mainStatisticsFetch = useFetcher( (callApmApi) => { if (start && end) { return callApmApi('GET /internal/apm/services', { @@ -62,7 +77,7 @@ function useServicesFetcher() { }).then((mainStatisticsData) => { return { requestId: uuid(), - mainStatisticsData, + ...mainStatisticsData, }; }); } @@ -70,9 +85,9 @@ function useServicesFetcher() { [environment, kuery, start, end] ); - const { mainStatisticsData, requestId } = data; + const { data: mainStatisticsData = initialData } = mainStatisticsFetch; - const { data: comparisonData } = useFetcher( + const comparisonFetch = useFetcher( (callApmApi) => { if (start && end && mainStatisticsData.items.length) { return callApmApi('GET /internal/apm/services/detailed_statistics', { @@ -96,20 +111,23 @@ function useServicesFetcher() { }, // only fetches detailed statistics when requestId is invalidated by main statistics api call or offset is changed // eslint-disable-next-line react-hooks/exhaustive-deps - [requestId, offset], + [mainStatisticsData.requestId, offset], { preservePreviousData: false } ); return { - mainStatisticsData, - mainStatisticsStatus, - comparisonData, + sortedAndFilteredServicesFetch, + mainStatisticsFetch, + comparisonFetch, }; } export function ServiceInventory() { - const { mainStatisticsData, mainStatisticsStatus, comparisonData } = - useServicesFetcher(); + const { + sortedAndFilteredServicesFetch, + mainStatisticsFetch, + comparisonFetch, + } = useServicesFetcher(); const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext(); @@ -122,8 +140,13 @@ export function ServiceInventory() { !userHasDismissedCallout && shouldDisplayMlCallout(anomalyDetectionSetupState); - const isLoading = mainStatisticsStatus === FETCH_STATUS.LOADING; - const isFailure = mainStatisticsStatus === FETCH_STATUS.FAILURE; + const isLoading = + sortedAndFilteredServicesFetch.status === FETCH_STATUS.LOADING || + (sortedAndFilteredServicesFetch.status === FETCH_STATUS.SUCCESS && + sortedAndFilteredServicesFetch.data?.services.length === 0 && + mainStatisticsFetch.status === FETCH_STATUS.LOADING); + + const isFailure = mainStatisticsFetch.status === FETCH_STATUS.FAILURE; const noItemsMessage = ( ); + const items = joinByKey( + [ + ...(sortedAndFilteredServicesFetch.data?.services ?? []), + ...(mainStatisticsFetch.data?.items ?? []), + ], + 'serviceName' + ); + return ( <> @@ -154,8 +185,8 @@ export function ServiceInventory() { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx index bececfb545ba9..01430c93b4b5a 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx @@ -30,6 +30,10 @@ const stories: Meta<{}> = { switch (endpoint) { case '/internal/apm/services': return { items: [] }; + + case '/internal/apm/sorted_and_filtered_services': + return { services: [] }; + default: return {}; } diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index 760b849775429..2d01a11d92186 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -17,7 +17,6 @@ import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/typed-react-router-config'; import { orderBy } from 'lodash'; import React, { useMemo } from 'react'; -import { ValuesType } from 'utility-types'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { @@ -46,14 +45,11 @@ import { getTimeSeriesColor, } from '../../../shared/charts/helper/get_timeseries_color'; import { HealthBadge } from './health_badge'; +import { ServiceListItem } from '../../../../../common/service_inventory'; -type ServiceListAPIResponse = APIReturnType<'GET /internal/apm/services'>; -type Items = ServiceListAPIResponse['items']; type ServicesDetailedStatisticsAPIResponse = APIReturnType<'GET /internal/apm/services/detailed_statistics'>; -type ServiceListItem = ValuesType; - function formatString(value?: string | null) { return value || NOT_AVAILABLE_LABEL; } @@ -239,7 +235,7 @@ export function getServiceColumns({ } interface Props { - items: Items; + items: ServiceListItem[]; comparisonData?: ServicesDetailedStatisticsAPIResponse; noItemsMessage?: React.ReactNode; isLoading: boolean; @@ -287,9 +283,8 @@ export function ServiceList({ ] ); - const initialSortField = displayHealthStatus - ? 'healthStatus' - : 'transactionsPerMinute'; + const initialSortField = displayHealthStatus ? 'healthStatus' : 'serviceName'; + const initialSortDirection = displayHealthStatus ? 'desc' : 'asc'; return ( @@ -336,9 +331,9 @@ export function ServiceList({ items={items} noItemsMessage={noItemsMessage} initialSortField={initialSortField} - initialSortDirection="desc" + initialSortDirection={initialSortDirection} sortFn={(itemsToSort, sortField, sortDirection) => { - // For healthStatus, sort items by healthStatus first, then by TPM + // For healthStatus, sort items by healthStatus first, then by name return sortField === 'healthStatus' ? orderBy( itemsToSort, @@ -348,9 +343,9 @@ export function ServiceList({ ? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus) : -1; }, - (item) => item.throughput ?? 0, + (item) => item.serviceName.toLowerCase(), ], - [sortDirection, sortDirection] + [sortDirection, sortDirection === 'asc' ? 'desc' : 'asc'] ) : orderBy( itemsToSort, diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_health_statuses.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_health_statuses.ts index 65fb04b821ffa..4e8795aacc228 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_health_statuses.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_health_statuses.ts @@ -6,14 +6,16 @@ */ import { getSeverity } from '../../../../common/anomaly_detection'; -import { getServiceHealthStatus } from '../../../../common/service_health_status'; +import { + getServiceHealthStatus, + ServiceHealthStatus, +} from '../../../../common/service_health_status'; import { getServiceAnomalies } from '../../../routes/service_map/get_service_anomalies'; import { ServicesItemsSetup } from './get_services_items'; interface AggregationParams { environment: string; setup: ServicesItemsSetup; - searchAggregatedTransactions: boolean; start: number; end: number; } @@ -23,7 +25,9 @@ export const getHealthStatuses = async ({ setup, start, end, -}: AggregationParams) => { +}: AggregationParams): Promise< + Array<{ serviceName: string; healthStatus: ServiceHealthStatus }> +> => { if (!setup.ml) { return []; } diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts new file mode 100644 index 0000000000000..5a7df14fa3186 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/logging'; +import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; +import { Environment } from '../../../../common/environment_rt'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { joinByKey } from '../../../../common/utils/join_by_key'; +import { Setup } from '../../../lib/helpers/setup_request'; +import { getHealthStatuses } from './get_health_statuses'; + +export async function getSortedAndFilteredServices({ + setup, + start, + end, + environment, + logger, +}: { + setup: Setup; + start: number; + end: number; + environment: Environment; + logger: Logger; +}) { + const { apmEventClient } = setup; + + async function getServicesFromTermsEnum() { + if (environment !== ENVIRONMENT_ALL.value) { + return []; + } + const response = await apmEventClient.termsEnum( + 'get_services_from_terms_enum', + { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.span, + ProcessorEvent.metric, + ProcessorEvent.error, + ], + }, + body: { + size: 500, + field: SERVICE_NAME, + }, + } + ); + + return response.terms; + } + + const [servicesWithHealthStatuses, serviceNamesFromTermsEnum] = + await Promise.all([ + getHealthStatuses({ + setup, + start, + end, + environment, + }).catch((error) => { + logger.error(error); + return []; + }), + getServicesFromTermsEnum(), + ]); + + const services = joinByKey( + [ + ...servicesWithHealthStatuses, + ...serviceNamesFromTermsEnum.map((serviceName) => ({ serviceName })), + ], + 'serviceName' + ); + + return services; +} diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 55f7b4f14b7b6..7c69f3b1df802 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -52,6 +52,8 @@ import { ML_ERRORS } from '../../../common/anomaly_detection'; import { ScopedAnnotationsClient } from '../../../../observability/server'; import { Annotation } from './../../../../observability/common/annotations'; import { ConnectionStatsItemWithImpact } from './../../../common/connections'; +import { getSortedAndFilteredServices } from './get_services/get_sorted_and_filtered_services'; +import { ServiceHealthStatus } from './../../../common/service_health_status'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services', @@ -1224,6 +1226,45 @@ const serviceAnomalyChartsRoute = createApmServerRoute({ }, }); +const sortedAndFilteredServicesRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/sorted_and_filtered_services', + options: { + tags: ['access:apm'], + }, + params: t.type({ + query: t.intersection([rangeRt, environmentRt, kueryRt]), + }), + handler: async ( + resources + ): Promise<{ + services: Array<{ + serviceName: string; + healthStatus?: ServiceHealthStatus; + }>; + }> => { + const { + query: { start, end, environment, kuery }, + } = resources.params; + + if (kuery) { + return { + services: [], + }; + } + + const setup = await setupRequest(resources); + return { + services: await getSortedAndFilteredServices({ + setup, + start, + end, + environment, + logger: resources.logger, + }), + }; + }, +}); + export const serviceRouteRepository = { ...servicesRoute, ...servicesDetailedStatisticsRoute, @@ -1245,4 +1286,5 @@ export const serviceRouteRepository = { ...serviceAlertsRoute, ...serviceInfrastructureRoute, ...serviceAnomalyChartsRoute, + ...sortedAndFilteredServicesRoute, }; diff --git a/x-pack/test/apm_api_integration/common/utils/create_and_run_apm_ml_job.ts b/x-pack/test/apm_api_integration/common/utils/create_and_run_apm_ml_job.ts new file mode 100644 index 0000000000000..bd03493039b49 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/utils/create_and_run_apm_ml_job.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import job from '../../../../plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json'; +import datafeed from '../../../../plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_tx_metrics.json'; +import { MlApi } from '../../../functional/services/ml/api'; + +export function createAndRunApmMlJob({ ml, environment }: { ml: MlApi; environment: string }) { + return ml.createAndRunAnomalyDetectionLookbackJob( + // @ts-expect-error not entire job config + { + ...job, + job_id: `apm-tx-metrics-${environment}`, + allow_lazy_open: false, + custom_settings: { + job_tags: { + apm_ml_version: '3', + environment, + }, + }, + }, + { + ...datafeed, + job_id: `apm-tx-metrics-${environment}`, + indices: ['apm-*'], + datafeed_id: `apm-tx-metrics-${environment}-datafeed`, + query: { + bool: { + filter: [...datafeed.query.bool.filter, { term: { 'service.environment': environment } }], + }, + }, + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts index 00bdc258fb40f..e06c519a978ab 100644 --- a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts @@ -10,10 +10,9 @@ import { range, omit } from 'lodash'; import { apm, timerange } from '@elastic/apm-synthtrace'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { ApmApiError } from '../../common/apm_api_supertest'; -import job from '../../../../plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json'; -import datafeed from '../../../../plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_tx_metrics.json'; import { ServiceAnomalyTimeseries } from '../../../../plugins/apm/common/anomaly_detection/service_anomaly_timeseries'; import { ApmMlDetectorType } from '../../../../plugins/apm/common/anomaly_detection/apm_ml_detectors'; +import { createAndRunApmMlJob } from '../../common/utils/create_and_run_apm_ml_job'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -169,62 +168,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with ml jobs', () => { before(async () => { await Promise.all([ - ml.createAndRunAnomalyDetectionLookbackJob( - // @ts-expect-error not entire job config - { - ...job, - job_id: 'apm-tx-metrics-prod', - allow_lazy_open: false, - custom_settings: { - job_tags: { - apm_ml_version: '3', - environment: 'production', - }, - }, - }, - { - ...datafeed, - job_id: 'apm-tx-metrics-prod', - indices: ['apm-*'], - datafeed_id: 'apm-tx-metrics-prod-datafeed', - query: { - bool: { - filter: [ - ...datafeed.query.bool.filter, - { term: { 'service.environment': 'production' } }, - ], - }, - }, - } - ), - ml.createAndRunAnomalyDetectionLookbackJob( - // @ts-expect-error not entire job config - { - ...job, - job_id: 'apm-tx-metrics-development', - allow_lazy_open: false, - custom_settings: { - job_tags: { - apm_ml_version: '3', - environment: 'development', - }, - }, - }, - { - ...datafeed, - job_id: 'apm-tx-metrics-development', - indices: ['apm-*'], - datafeed_id: 'apm-tx-metrics-development-datafeed', - query: { - bool: { - filter: [ - ...datafeed.query.bool.filter, - { term: { 'service.environment': 'development' } }, - ], - }, - }, - } - ), + createAndRunApmMlJob({ environment: 'production', ml }), + createAndRunApmMlJob({ environment: 'development', ml }), ]); }); @@ -288,7 +233,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(omitTimeseriesData(latencySeries)).to.eql({ type: ApmMlDetectorType.txLatency, - jobId: 'apm-tx-metrics-prod', + jobId: 'apm-tx-metrics-production', serviceName: 'a', environment: 'production', transactionType: 'request', @@ -297,7 +242,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(omitTimeseriesData(throughputSeries)).to.eql({ type: ApmMlDetectorType.txThroughput, - jobId: 'apm-tx-metrics-prod', + jobId: 'apm-tx-metrics-production', serviceName: 'a', environment: 'production', transactionType: 'request', @@ -306,7 +251,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(omitTimeseriesData(failureRateSeries)).to.eql({ type: ApmMlDetectorType.txFailureRate, - jobId: 'apm-tx-metrics-prod', + jobId: 'apm-tx-metrics-production', serviceName: 'a', environment: 'production', transactionType: 'request', diff --git a/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts new file mode 100644 index 0000000000000..b529ab36c5637 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ValuesType } from 'utility-types'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { createAndRunApmMlJob } from '../../common/utils/create_and_run_apm_ml_job'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const synthtraceClient = getService('synthtraceEsClient'); + const apmApiClient = getService('apmApiClient'); + const ml = getService('ml'); + + const start = '2021-01-01T12:00:00.000Z'; + const end = '2021-08-01T12:00:00.000Z'; + + // the terms enum API will return names for deleted services, + // so we add a prefix to make sure we don't get data from other + // tests + const SERVICE_NAME_PREFIX = 'sorted_and_filtered_'; + + async function getSortedAndFilteredServices({ + environment = 'ENVIRONMENT_ALL', + kuery = '', + }: { environment?: string; kuery?: string } = {}) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/sorted_and_filtered_services', + params: { + query: { + start, + end, + environment, + kuery, + }, + }, + }); + + return response.body.services + .filter((service) => service.serviceName.startsWith(SERVICE_NAME_PREFIX)) + .map((service) => ({ + ...service, + serviceName: service.serviceName.replace(SERVICE_NAME_PREFIX, ''), + })); + } + + type ServiceListItem = ValuesType>>; + + registry.when( + 'Sorted and filtered services', + { config: 'trial', archives: ['apm_mappings_only_8.0.0'] }, + () => { + before(async () => { + const serviceA = apm.service(SERVICE_NAME_PREFIX + 'a', 'production', 'java').instance('a'); + + const serviceB = apm.service(SERVICE_NAME_PREFIX + 'b', 'development', 'go').instance('b'); + + const serviceC = apm.service(SERVICE_NAME_PREFIX + 'c', 'development', 'go').instance('c'); + + const spikeStart = new Date('2021-01-07T12:00:00.000Z').getTime(); + const spikeEnd = new Date('2021-01-07T14:00:00.000Z').getTime(); + + const eventsWithinTimerange = timerange(new Date(start).getTime(), new Date(end).getTime()) + .interval('15m') + .rate(1) + .spans((timestamp) => { + const isInSpike = spikeStart <= timestamp && spikeEnd >= timestamp; + return [ + ...serviceA + .transaction('GET /api') + .duration(isInSpike ? 1000 : 1100) + .timestamp(timestamp) + .serialize(), + ...serviceB + .transaction('GET /api') + .duration(isInSpike ? 1000 : 4000) + .timestamp(timestamp) + .serialize(), + ]; + }); + + const eventsOutsideOfTimerange = timerange( + new Date('2021-01-01T00:00:00.000Z').getTime(), + new Date(start).getTime() - 1 + ) + .interval('15m') + .rate(1) + .spans((timestamp) => { + return serviceC + .transaction('GET /api', 'custom') + .duration(1000) + .timestamp(timestamp) + .serialize(); + }); + + await synthtraceClient.index(eventsWithinTimerange.concat(eventsOutsideOfTimerange)); + + await Promise.all([ + createAndRunApmMlJob({ environment: 'production', ml }), + createAndRunApmMlJob({ environment: 'development', ml }), + ]); + }); + + after(() => { + return Promise.all([synthtraceClient.clean(), ml.cleanMlIndices()]); + }); + + describe('with no kuery or environment are set', () => { + let items: ServiceListItem[]; + + before(async () => { + items = await getSortedAndFilteredServices(); + }); + + it('returns services based on the terms enum API and ML data', () => { + const serviceNames = items.map((item) => item.serviceName); + + expect(serviceNames.sort()).to.eql(['a', 'b', 'c']); + }); + }); + + describe('with kuery set', () => { + let items: ServiceListItem[]; + + before(async () => { + items = await getSortedAndFilteredServices({ + kuery: 'service.name:*', + }); + }); + + it('does not return any services', () => { + expect(items.length).to.be(0); + }); + }); + + describe('with environment set to production', () => { + let items: ServiceListItem[]; + + before(async () => { + items = await getSortedAndFilteredServices({ + environment: 'production', + }); + }); + + it('returns services for production only', () => { + const serviceNames = items.map((item) => item.serviceName); + + expect(serviceNames.sort()).to.eql(['a']); + }); + }); + } + ); +} diff --git a/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts index d9a5ea35e5393..f70bd4736bd7e 100644 --- a/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts +++ b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts @@ -36,6 +36,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm'); await spacesService.delete('custom_space'); }); @@ -54,10 +55,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('apmMainContainer', { timeout: 10000, }); - - const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); - const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); - expect(apmMainContainerTextItems).to.contain('No services found'); }); }); diff --git a/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts b/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts index af3633798133b..200b3367b9723 100644 --- a/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts +++ b/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts @@ -37,6 +37,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm'); await spacesService.delete('custom_space'); }); @@ -55,10 +56,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('apmMainContainer', { timeout: 10000, }); - - const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); - const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); - expect(apmMainContainerTextItems).to.contain('No services found'); }); });