From 07cfa90e7c15732dafdd5046cfef7bcb4161bba4 Mon Sep 17 00:00:00 2001 From: Katerina Date: Mon, 11 Nov 2024 15:49:54 +0200 Subject: [PATCH] [APM] Migrate `/mobile` API tests to deployment agnostic folder (#199021) closes https://github.com/elastic/kibana/issues/198980 In addition to migrating the mobile api tests, the PR includes - Fixing mapping issue with `error.grouping_name` which causing to drop documents - Fix and unskip mobile tests ### How to test - Serverless ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM" ``` It's recommended to be run against [MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki) - Stateful ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM" ``` TODO - [x] flaky runner - [x] locally pass - [x] mki run --------- Co-authored-by: Carlos Crespo Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine (cherry picked from commit f77a8052f7a3e39cb3b61d1b61dbef8b6c254525) --- .../src/lib/apm/instance.ts | 1 - .../src/lib/apm/mobile_device.ts | 1 - .../latest_agent_versions.spec.ts | 2 - .../apis/observability/apm/index.ts | 1 + .../mobile/crashes/crash_group_list.spec.ts | 160 ++++++++++++++ .../apm/mobile/crashes/distribution.spec.ts | 209 ++++++++++++++++++ .../apm}/mobile/crashes/generate_data.ts | 0 .../apm}/mobile/errors/generate_data.ts | 0 .../mobile/errors/group_id_samples.spec.ts | 191 ++++++++++++++++ .../apm}/mobile/generate_mobile_data.ts | 0 .../apis/observability/apm/mobile/index.ts | 25 +++ ...obile_detailed_statistics_by_field.spec.ts | 53 +++-- .../apm}/mobile/mobile_filters.spec.ts | 40 ++-- .../mobile_http_requests_timeseries.spec.ts | 88 ++++---- .../apm}/mobile/mobile_location_stats.spec.ts | 184 +++++++-------- .../mobile_main_statistics_by_field.spec.ts | 139 ++++++------ .../apm/mobile/mobile_most_used_chart.spec.ts | 104 +++++++++ .../mobile/mobile_sessions_timeseries.spec.ts | 133 +++++++++++ .../apm}/mobile/mobile_stats.spec.ts | 188 ++++++++-------- .../apm}/mobile/mobile_terms_by_field.spec.ts | 118 +++++----- .../mobile/crashes/crash_group_list.spec.ts | 157 ------------- .../tests/mobile/crashes/distribution.spec.ts | 203 ----------------- .../mobile/errors/group_id_samples.spec.ts | 189 ---------------- .../mobile/mobile_most_used_chart.spec.ts | 105 --------- .../mobile/mobile_sessions_timeseries.spec.ts | 128 ----------- x-pack/test/tsconfig.json | 2 +- 26 files changed, 1223 insertions(+), 1198 deletions(-) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/crash_group_list.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/distribution.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/crashes/generate_data.ts (100%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/errors/generate_data.ts (100%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/group_id_samples.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/generate_mobile_data.ts (100%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_detailed_statistics_by_field.spec.ts (78%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_filters.spec.ts (88%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_http_requests_timeseries.spec.ts (58%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_location_stats.spec.ts (60%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_main_statistics_by_field.spec.ts (59%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_most_used_chart.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_sessions_timeseries.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_stats.spec.ts (56%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/mobile/mobile_terms_by_field.spec.ts (70%) delete mode 100644 x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts index 31c301591ae4e..d3e393b68639e 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts @@ -70,7 +70,6 @@ export class Instance extends Entity { ...this.fields, 'error.type': 'crash', 'error.exception': [{ message, ...(type ? { type } : {}) }], - 'error.grouping_name': getErrorGroupingKey(message), }); } error({ diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts index b4dfafe22fc44..c0947f75531a3 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts @@ -262,7 +262,6 @@ export class MobileDevice extends Entity { 'error.type': 'crash', 'error.id': generateLongIdWithSeed(message), 'error.exception': [{ message, ...{ type: 'crash' } }], - 'error.grouping_name': groupingName || message, }); } } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts index 7d40993885255..acc31a3743da8 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts @@ -23,9 +23,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon it('returns a version when agent is listed in the file', async () => { const { status, body } = await callApi(); expect(status).to.be(200); - const agents = body.data; - const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion; expect(nodeAgent?.latest_version).not.to.be(undefined); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts index abc7f72945e2e..93ef6ed2764fe 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -12,6 +12,7 @@ export default function apmApiIntegrationTests({ }: DeploymentAgnosticFtrProviderContext) { describe('APM', function () { loadTestFile(require.resolve('./agent_explorer')); + loadTestFile(require.resolve('./mobile')); loadTestFile(require.resolve('./custom_dashboards')); loadTestFile(require.resolve('./dependencies')); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/crash_group_list.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/crash_group_list.spec.ts new file mode 100644 index 0000000000000..1a053055617af --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/crash_group_list.spec.ts @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; + +type ErrorGroups = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['errorGroups']; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const serviceName = 'synth-swift'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics', + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + describe('Crash group list', () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.errorGroups).to.empty(); + }); + + describe('when data is loaded', () => { + describe('errors group', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + const appleTransaction = { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }; + + const bananaTransaction = { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }; + + before(async () => { + const serviceInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'swift' }) + .instance('instance-a'); + + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await apmSynthtraceEsClient.index([ + timerange(start, end) + .interval('1m') + .rate(appleTransaction.successRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: appleTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval('1m') + .rate(appleTransaction.failureRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: appleTransaction.name }) + .errors( + serviceInstance + .crash({ + message: 'crash 1', + }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + timerange(start, end) + .interval('1m') + .rate(bananaTransaction.successRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: bananaTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval('1m') + .rate(bananaTransaction.failureRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: bananaTransaction.name }) + .errors( + serviceInstance + .crash({ + message: 'crash 2', + }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + ]); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('returns the correct data', () => { + let errorGroups: ErrorGroups; + before(async () => { + const response = await callApi(); + errorGroups = response.body.errorGroups; + }); + it('returns correct number of crashes', () => { + expect(errorGroups.length).to.equal(2); + expect(errorGroups.map((error) => error.name).sort()).to.eql(['crash 1', 'crash 2']); + }); + + it('returns correct occurrences', () => { + const numberOfBuckets = 15; + expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([ + appleTransaction.failureRate * numberOfBuckets, + bananaTransaction.failureRate * numberOfBuckets, + ]); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/distribution.spec.ts new file mode 100644 index 0000000000000..b726f9df3349c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/distribution.spec.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { first, last, sumBy } from 'lodash'; +import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { config, generateData } from './generate_data'; + +type ErrorsDistribution = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const serviceName = 'synth-swift'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution', + params: { + path: { + serviceName, + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + return response; + } + + describe('Distribution', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + it('handles the empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod.length).to.be(0); + expect(response.body.previousPeriod.length).to.be(0); + }); + + describe('when data is loaded', () => { + describe('errors distribution', () => { + const { appleTransaction, bananaTransaction } = config; + before(async () => { + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('without comparison', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi(); + errorsDistribution = response.body; + }); + + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal( + (appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets + ); + }); + + describe('displays correct start in errors distribution chart', () => { + let errorsDistributionWithComparison: ErrorsDistribution; + before(async () => { + const responseWithComparison = await callApi({ + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + offset: '15m', + }, + }); + errorsDistributionWithComparison = responseWithComparison.body; + }); + it('has same start time when comparison is enabled', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistributionWithComparison.currentPeriod)?.x + ); + }); + }); + }); + + describe('displays occurrences for type "apple transaction" only', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { kuery: `error.exception.type:"${appleTransaction.name}"` }, + }); + errorsDistribution = response.body; + }); + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets); + }); + }); + + describe('with comparison', () => { + describe('when data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const fiveMinutes = 5 * 60 * 1000; + const response = await callApi({ + query: { + start: new Date(end - fiveMinutes).toISOString(), + end: new Date(end).toISOString(), + offset: '5m', + }, + }); + errorsDistribution = response.body; + }); + it('returns some data', () => { + const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + + describe('when no data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { + start: '2021-01-03T00:00:00.000Z', + end: '2021-01-03T00:15:00.000Z', + offset: '1d', + }, + }); + errorsDistribution = response.body; + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/crashes/generate_data.ts diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/generate_data.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/group_id_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/group_id_samples.spec.ts new file mode 100644 index 0000000000000..6e5a19327c24f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/errors/group_id_samples.spec.ts @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { timerange } from '@kbn/apm-synthtrace-client'; +import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; +import { orderBy } from 'lodash'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { config, generateData } from './generate_data'; + +type ErrorGroupSamples = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>; + +type ErrorSampleDetails = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}'>; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callErrorGroupSamplesApi({ groupId }: { groupId: string }) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples', + params: { + path: { + serviceName, + groupId, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + return response; + } + + async function callErrorSampleDetailsApi(errorId: string) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}', + params: { + path: { + serviceName, + groupId: 'foo', + errorId, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + return response; + } + + describe('Group id samples', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + it('handles the empty state', async () => { + const response = await callErrorGroupSamplesApi({ groupId: 'foo' }); + expect(response.status).to.be(200); + expect(response.body.occurrencesCount).to.be(0); + }); + + describe('when samples data is loaded', () => { + let errorsSamplesResponse: ErrorGroupSamples; + const { bananaTransaction } = config; + describe('error group id', () => { + before(async () => { + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + const response = await callErrorGroupSamplesApi({ + groupId: '0000000000000000000000000Error 1', + }); + errorsSamplesResponse = response.body; + }); + + after(() => apmSynthtraceEsClient.clean()); + + it('displays correct number of occurrences', () => { + const numberOfBuckets = 15; + expect(errorsSamplesResponse.occurrencesCount).to.equal( + bananaTransaction.failureRate * numberOfBuckets + ); + }); + }); + }); + + // github.com/elastic/kibana/issues/177665 + describe('when error sample data is loaded', () => { + describe('error sample id', () => { + before(async () => { + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('return correct data', () => { + let errorSampleDetailsResponse: ErrorSampleDetails; + before(async () => { + const errorsSamplesResponse = await callErrorGroupSamplesApi({ + groupId: '0000000000000000000000000Error 1', + }); + + const errorId = errorsSamplesResponse.body.errorSampleIds[0]; + + const response = await callErrorSampleDetailsApi(errorId); + errorSampleDetailsResponse = response.body; + }); + + it('displays correct error grouping_key', () => { + expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal( + '0000000000000000000000000Error 1' + ); + }); + + it('displays correct error message', () => { + expect(errorSampleDetailsResponse.error.error.exception?.[0].message).to.equal( + 'Error 1' + ); + }); + }); + }); + + describe('with sampled and unsampled transactions', () => { + let errorGroupSamplesResponse: ErrorGroupSamples; + + before(async () => { + const instance = service(serviceName, 'production', 'go').instance('a'); + const errorMessage = 'Error 1'; + const groupId = '0000000000000000000000000Error 1'; + + await apmSynthtraceEsClient.index([ + timerange(start, end) + .interval('15m') + .rate(1) + .generator((timestamp) => { + return [ + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(false) + .errors( + instance.error({ message: errorMessage }).timestamp(timestamp), + instance.error({ message: errorMessage }).timestamp(timestamp + 1) + ), + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(true) + .errors(instance.error({ message: errorMessage }).timestamp(timestamp)), + ]; + }), + ]); + + errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body; + }); + + after(() => apmSynthtraceEsClient.clean()); + + it('returns the errors in the correct order (sampled first, then unsampled)', () => { + const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => + parseInt(id, 10) + ); + + // this checks whether the order of indexing is different from the order that is returned + // if it is not, scoring/sorting is broken + expect(errorGroupSamplesResponse.errorSampleIds.length).to.be(3); + expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors)); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts new file mode 100644 index 0000000000000..97d8e13256d60 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Mobile', () => { + loadTestFile(require.resolve('./crashes/crash_group_list.spec.ts')); + loadTestFile(require.resolve('./crashes/distribution.spec.ts')); + loadTestFile(require.resolve('./errors/group_id_samples.spec.ts')); + loadTestFile(require.resolve('./mobile_detailed_statistics_by_field.spec.ts')); + loadTestFile(require.resolve('./mobile_filters.spec.ts')); + loadTestFile(require.resolve('./mobile_http_requests_timeseries.spec.ts')); + loadTestFile(require.resolve('./mobile_location_stats.spec.ts')); + loadTestFile(require.resolve('./mobile_main_statistics_by_field.spec.ts')); + loadTestFile(require.resolve('./mobile_most_used_chart.spec.ts')); + loadTestFile(require.resolve('./mobile_sessions_timeseries.spec.ts')); + loadTestFile(require.resolve('./mobile_stats.spec.ts')); + loadTestFile(require.resolve('./mobile_terms_by_field.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_detailed_statistics_by_field.spec.ts similarity index 78% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_detailed_statistics_by_field.spec.ts index a8912989e295b..134577dff9204 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_detailed_statistics_by_field.spec.ts @@ -10,16 +10,18 @@ import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_value import { isEmpty } from 'lodash'; import moment from 'moment'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { generateMobileData, SERVICE_VERSIONS } from './generate_mobile_data'; type MobileDetailedStatisticsResponse = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/detailed_statistics'>; -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -56,27 +58,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when( - 'Mobile detailed statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - describe('when no data', () => { - it('handles empty state', async () => { - const response = await getMobileDetailedStatisticsByField({ - serviceName: 'foo', - field: 'service.version', - }); - expect(response).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); + describe('Mobile detailed statistics ', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await getMobileDetailedStatisticsByField({ + serviceName: 'foo', + field: 'service.version', }); + expect(response).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177388 - registry.when.skip( - 'Mobile detailed statistics when data is loaded', - { config: 'basic', archives: [] }, - () => { + }); + + describe('when data is loaded', () => { before(async () => { await generateMobileData({ apmSynthtraceEsClient, @@ -85,8 +84,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - after(() => apmSynthtraceEsClient.clean()); - describe('when comparison is disable', () => { it('returns current period data only', async () => { const response = await getMobileDetailedStatisticsByField({ @@ -133,6 +130,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); - } - ); + }); + }); } diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_filters.spec.ts similarity index 88% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_filters.spec.ts index edebde9f0d439..5c8d8499d3295 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_filters.spec.ts @@ -7,10 +7,10 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; type MobileFilters = APIReturnType<'GET /internal/apm/services/{serviceName}/mobile/filters'>; @@ -133,10 +133,11 @@ async function generateData({ ]); } -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -166,7 +167,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when('Mobile filters when data is not loaded', { config: 'basic', archives: [] }, () => { + describe('Mobile filters', () => { describe('when no data', () => { it('handles empty state', async () => { const response = await getMobileFilters({ serviceName: 'foo' }); @@ -175,30 +176,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177389 - registry.when.skip('Mobile filters', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateData({ - apmSynthtraceEsClient, - start, - end, - }); - }); - - after(() => apmSynthtraceEsClient.clean()); describe('when data is loaded', () => { - let response: MobileFilters; - before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateData({ + apmSynthtraceEsClient, + start, + end, + }); response = await getMobileFilters({ serviceName: 'synth-android', environment: 'production', }); }); + after(() => apmSynthtraceEsClient.clean()); + let response: MobileFilters; + it('returns correct filters for device', () => { response.mobileFilters.map(({ key, options }) => { if (key === 'device') { diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_http_requests_timeseries.spec.ts similarity index 58% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_http_requests_timeseries.spec.ts index ccd4ddd23ca53..6a5a92a29b331 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_http_requests_timeseries.spec.ts @@ -7,13 +7,15 @@ import expect from '@kbn/expect'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { generateMobileData } from './generate_mobile_data'; -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T02:00:00.000Z').getTime(); @@ -47,27 +49,20 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - registry.when.skip( - 'Mobile HTTP requests without data loaded', - { config: 'basic', archives: [] }, - () => { - describe('when no data', () => { - it('handles empty state', async () => { - const response = await getHttpRequestsChart({ serviceName: 'foo' }); - expect(response.body.currentPeriod.timeseries).to.eql([]); - expect(response.body.previousPeriod.timeseries).to.eql([]); - expect(response.status).to.be(200); - }); + describe('Mobile HTTP requests ', () => { + describe('when no data', () => { + it('handles empty state', async () => { + const response = await getHttpRequestsChart({ serviceName: 'foo' }); + expect(response.body.currentPeriod.timeseries).to.eql([]); + expect(response.body.previousPeriod.timeseries).to.eql([]); + expect(response.status).to.be(200); }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177390 - registry.when.skip( - 'Mobile HTTP requests with data loaded', - { config: 'basic', archives: [] }, - () => { + }); + + describe('when data is loaded', () => { before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateMobileData({ apmSynthtraceEsClient, start, @@ -76,32 +71,29 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - it('returns timeseries for http requests chart', async () => { - const response = await getHttpRequestsChart({ - serviceName: 'synth-android', - offset: '1d', - }); - - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( - true - ); - expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); + it('returns timeseries for http requests chart', async () => { + const response = await getHttpRequestsChart({ + serviceName: 'synth-android', + offset: '1d', }); - it('returns only current period timeseries when offset is not available', async () => { - const response = await getHttpRequestsChart({ serviceName: 'synth-android' }); + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( + true + ); + expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); + }); - expect(response.status).to.be(200); - expect( - response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x) - ).to.eql(true); + it('returns only current period timeseries when offset is not available', async () => { + const response = await getHttpRequestsChart({ serviceName: 'synth-android' }); - expect(response.body.currentPeriod.timeseries[0].y).to.eql(7); - expect(response.body.previousPeriod.timeseries).to.eql([]); - }); + expect(response.status).to.be(200); + expect( + response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x) + ).to.eql(true); + + expect(response.body.currentPeriod.timeseries[0].y).to.eql(7); + expect(response.body.previousPeriod.timeseries).to.eql([]); }); describe('when filters are applied', () => { @@ -138,6 +130,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(ntcCell.body.currentPeriod.timeseries[0].y).to.eql(2); }); }); - } - ); + }); + }); } diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_location_stats.spec.ts similarity index 60% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_location_stats.spec.ts index ec82de406e0e0..20098e9a671ef 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_location_stats.spec.ts @@ -7,10 +7,10 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; type MobileLocationStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/location/stats'>; @@ -176,10 +176,11 @@ async function generateData({ ]); } -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -212,7 +213,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when('Location stats when data is not loaded', { config: 'basic', archives: [] }, () => { + describe('Location stats', () => { describe('when no data', () => { it('handles empty state', async () => { const response = await getMobileLocationStats({ serviceName: 'foo' }); @@ -230,111 +231,112 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177396 - registry.when.skip('Location stats', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateData({ - apmSynthtraceEsClient, - start, - end, - }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - let response: MobileLocationStats; + describe('Location stats with data', () => { before(async () => { - response = await getMobileLocationStats({ - serviceName: 'synth-android', - environment: 'production', + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateData({ + apmSynthtraceEsClient, + start, + end, }); }); - it('returns location for most sessions', () => { - const { location } = response.currentPeriod.mostSessions; - expect(location).to.be('China'); - }); + after(() => apmSynthtraceEsClient.clean()); - it('returns location for most requests', () => { - const { location } = response.currentPeriod.mostRequests; - expect(location).to.be('China'); - }); + describe('when data is loaded', () => { + let response: MobileLocationStats; - it('returns location for most crashes', () => { - const { location } = response.currentPeriod.mostCrashes; - expect(location).to.be('China'); - }); + before(async () => { + response = await getMobileLocationStats({ + serviceName: 'synth-android', + environment: 'production', + }); + }); - it('returns location for most launches', () => { - const { location } = response.currentPeriod.mostLaunches; - expect(location).to.be('China'); - }); - }); + it('returns location for most sessions', () => { + const { location } = response.currentPeriod.mostSessions; + expect(location).to.be('China'); + }); - describe('when filters are applied', () => { - it('returns empty state for filters with no results', async () => { - const response = await getMobileLocationStats({ - serviceName: 'synth-android', - environment: 'production', - kuery: `app.version:"none"`, + it('returns location for most requests', () => { + const { location } = response.currentPeriod.mostRequests; + expect(location).to.be('China'); }); - expect(response.currentPeriod.mostSessions.value).to.eql(0); - expect(response.currentPeriod.mostRequests.value).to.eql(0); - expect(response.currentPeriod.mostCrashes.value).to.eql(0); - expect(response.currentPeriod.mostLaunches.value).to.eql(0); + it('returns location for most crashes', () => { + const { location } = response.currentPeriod.mostCrashes; + expect(location).to.be('China'); + }); - expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect(response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0)).to.eql( - true - ); + it('returns location for most launches', () => { + const { location } = response.currentPeriod.mostLaunches; + expect(location).to.be('China'); + }); }); - it('returns the correct values when single filter is applied', async () => { - const response = await getMobileLocationStats({ - serviceName: 'synth-android', - environment: 'production', - kuery: `service.version:"1.1"`, + describe('when filters are applied', () => { + it('returns empty state for filters with no results', async () => { + const response = await getMobileLocationStats({ + serviceName: 'synth-android', + environment: 'production', + kuery: `app.version:"none"`, + }); + + expect(response.currentPeriod.mostSessions.value).to.eql(0); + expect(response.currentPeriod.mostRequests.value).to.eql(0); + expect(response.currentPeriod.mostCrashes.value).to.eql(0); + expect(response.currentPeriod.mostLaunches.value).to.eql(0); + + expect( + response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0) + ).to.eql(true); + expect( + response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0) + ).to.eql(true); + expect( + response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0) + ).to.eql(true); + expect( + response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0) + ).to.eql(true); }); - expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); + it('returns the correct values when single filter is applied', async () => { + const response = await getMobileLocationStats({ + serviceName: 'synth-android', + environment: 'production', + kuery: `service.version:"1.1"`, + }); - expect(response.currentPeriod.mostSessions.value).to.eql(3); - expect(response.currentPeriod.mostRequests.value).to.eql(3); - expect(response.currentPeriod.mostCrashes.value).to.eql(3); - expect(response.currentPeriod.mostLaunches.value).to.eql(3); - }); + expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); - it('returns the correct values when multiple filters are applied', async () => { - const response = await getMobileLocationStats({ - serviceName: 'synth-android', - kuery: `service.version:"1.1" and service.environment: "production"`, + expect(response.currentPeriod.mostSessions.value).to.eql(3); + expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); + expect(response.currentPeriod.mostLaunches.value).to.eql(3); }); - expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); - expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); + it('returns the correct values when multiple filters are applied', async () => { + const response = await getMobileLocationStats({ + serviceName: 'synth-android', + kuery: `service.version:"1.1" and service.environment: "production"`, + }); - expect(response.currentPeriod.mostSessions.value).to.eql(3); - expect(response.currentPeriod.mostRequests.value).to.eql(3); - expect(response.currentPeriod.mostCrashes.value).to.eql(3); - expect(response.currentPeriod.mostLaunches.value).to.eql(3); + expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); + + expect(response.currentPeriod.mostSessions.value).to.eql(3); + expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); + expect(response.currentPeriod.mostLaunches.value).to.eql(3); + }); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_main_statistics_by_field.spec.ts similarity index 59% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_main_statistics_by_field.spec.ts index 945ed5970e000..0e49624be2a9a 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_main_statistics_by_field.spec.ts @@ -4,12 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; const GALAXY_DURATION = 500; const HUAWEI_DURATION = 20; @@ -126,10 +125,11 @@ async function generateData({ ]); } -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -162,91 +162,88 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when( - 'Mobile main statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - describe('when no data', () => { - it('handles empty state', async () => { - const response = await getMobileMainStatisticsByField({ - serviceName: 'foo', - field: 'service.version', - }); - expect(response.mainStatistics.length).to.be(0); + describe('Mobile main statistics', () => { + describe('when no data', () => { + it('handles empty state', async () => { + const response = await getMobileMainStatisticsByField({ + serviceName: 'foo', + field: 'service.version', }); - }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177395 - registry.when.skip('Mobile main statistics', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateData({ - apmSynthtraceEsClient, - start, - end, + expect(response.mainStatistics.length).to.be(0); }); }); - after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - const huaweiLatency = calculateLatency(HUAWEI_DURATION); - const galaxyLatency = calculateLatency(GALAXY_DURATION); - const huaweiThroughput = calculateThroughput({ start, end }); - const galaxyThroughput = calculateThroughput({ start, end }); + describe('Mobile main statistics', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - it('returns the correct data for App version', async () => { - const response = await getMobileMainStatisticsByField({ - serviceName: 'synth-android', - environment: 'production', - field: 'service.version', + await generateData({ + apmSynthtraceEsClient, + start, + end, }); - const fieldValues = response.mainStatistics.map((item) => item.name); + }); - expect(fieldValues).to.be.eql(SERVICE_VERSIONS); + after(() => apmSynthtraceEsClient.clean()); - const latencyValues = response.mainStatistics.map((item) => item.latency); + describe('when data is loaded', () => { + const huaweiLatency = calculateLatency(HUAWEI_DURATION); + const galaxyLatency = calculateLatency(GALAXY_DURATION); + const huaweiThroughput = calculateThroughput({ start, end }); + const galaxyThroughput = calculateThroughput({ start, end }); - expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]); + it('returns the correct data for App version', async () => { + const response = await getMobileMainStatisticsByField({ + serviceName: 'synth-android', + environment: 'production', + field: 'service.version', + }); + const fieldValues = response.mainStatistics.map((item) => item.name); - const throughputValues = response.mainStatistics.map((item) => item.throughput); - expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]); - }); - it('returns the correct data for Os version', async () => { - const response = await getMobileMainStatisticsByField({ - serviceName: 'synth-android', - environment: 'production', - field: 'host.os.version', + expect(fieldValues).to.be.eql(SERVICE_VERSIONS); + + const latencyValues = response.mainStatistics.map((item) => item.latency); + + expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]); + + const throughputValues = response.mainStatistics.map((item) => item.throughput); + expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]); }); + it('returns the correct data for Os version', async () => { + const response = await getMobileMainStatisticsByField({ + serviceName: 'synth-android', + environment: 'production', + field: 'host.os.version', + }); - const fieldValues = response.mainStatistics.map((item) => item.name); + const fieldValues = response.mainStatistics.map((item) => item.name); - expect(fieldValues).to.be.eql(OS_VERSIONS); + expect(fieldValues).to.be.eql(OS_VERSIONS); - const latencyValues = response.mainStatistics.map((item) => item.latency); + const latencyValues = response.mainStatistics.map((item) => item.latency); - expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]); + expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]); - const throughputValues = response.mainStatistics.map((item) => item.throughput); - expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]); - }); - it('returns the correct data for Devices', async () => { - const response = await getMobileMainStatisticsByField({ - serviceName: 'synth-android', - environment: 'production', - field: 'device.model.identifier', + const throughputValues = response.mainStatistics.map((item) => item.throughput); + expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]); }); - const fieldValues = response.mainStatistics.map((item) => item.name); + it('returns the correct data for Devices', async () => { + const response = await getMobileMainStatisticsByField({ + serviceName: 'synth-android', + environment: 'production', + field: 'device.model.identifier', + }); + const fieldValues = response.mainStatistics.map((item) => item.name); - expect(fieldValues).to.be.eql(['HUAWEI P2-0000', 'SM-G973F']); + expect(fieldValues).to.be.eql(['HUAWEI P2-0000', 'SM-G973F']); - const latencyValues = response.mainStatistics.map((item) => item.latency); + const latencyValues = response.mainStatistics.map((item) => item.latency); - expect(latencyValues).to.be.eql([huaweiLatency, galaxyLatency]); + expect(latencyValues).to.be.eql([huaweiLatency, galaxyLatency]); - const throughputValues = response.mainStatistics.map((item) => item.throughput); - expect(throughputValues).to.be.eql([huaweiThroughput, galaxyThroughput]); + const throughputValues = response.mainStatistics.map((item) => item.throughput); + expect(throughputValues).to.be.eql([huaweiThroughput, galaxyThroughput]); + }); }); }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_most_used_chart.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_most_used_chart.spec.ts new file mode 100644 index 0000000000000..205b5125d944b --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_most_used_chart.spec.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import expect from '@kbn/expect'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { generateMobileData } from './generate_mobile_data'; + +type MostUsedCharts = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/most_used_charts'>; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + const start = new Date('2023-01-01T00:00:00.000Z').getTime(); + const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; + + async function getMobileMostUsedCharts({ + environment = ENVIRONMENT_ALL.value, + kuery = '', + serviceName, + transactionType = 'mobile', + }: { + environment?: string; + kuery?: string; + serviceName: string; + transactionType?: string; + }) { + return await apmApiClient + .readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/most_used_charts', + params: { + path: { serviceName }, + query: { + environment, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + kuery, + transactionType, + }, + }, + }) + .then(({ body }) => body); + } + + describe('Most used charts', () => { + describe('when no data', () => { + it('handles empty state', async () => { + const response: MostUsedCharts = await getMobileMostUsedCharts({ serviceName: 'foo' }); + expect(response.mostUsedCharts.length).to.eql(4); + expect(response.mostUsedCharts.every((chart) => chart.options.length === 0)).to.eql(true); + }); + }); + + describe('Mobile stats', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateMobileData({ + apmSynthtraceEsClient, + start, + end, + }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('when data is loaded', () => { + let response: MostUsedCharts; + + before(async () => { + response = await getMobileMostUsedCharts({ + serviceName: 'synth-android', + environment: 'production', + }); + }); + + it('should get the top 5 and the other option only', () => { + const deviceOptions = response.mostUsedCharts.find( + (chart) => chart.key === 'device' + )?.options; + expect(deviceOptions?.length).to.eql(6); + expect(deviceOptions?.find((option) => option.key === 'other')).to.not.be(undefined); + }); + + it('should get network connection type object from span events', () => { + const nctOptions = response.mostUsedCharts.find( + (chart) => chart.key === 'netConnectionType' + )?.options; + expect(nctOptions?.length).to.eql(2); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_sessions_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_sessions_timeseries.spec.ts new file mode 100644 index 0000000000000..c43284767b7b4 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_sessions_timeseries.spec.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import expect from '@kbn/expect'; +import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { generateMobileData } from './generate_mobile_data'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + const start = new Date('2023-01-01T00:00:00.000Z').getTime(); + const end = new Date('2023-01-01T02:00:00.000Z').getTime(); + + async function getSessionsChart({ + environment = ENVIRONMENT_ALL.value, + kuery = '', + serviceName, + transactionType = 'mobile', + offset, + }: { + environment?: string; + kuery?: string; + serviceName: string; + transactionType?: string; + offset?: string; + }) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions', + params: { + path: { serviceName }, + query: { + environment, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + offset, + kuery, + transactionType, + }, + }, + }); + } + + describe('Sessions charts', () => { + describe('when no data', () => { + it('handles empty state', async () => { + const response = await getSessionsChart({ serviceName: 'foo' }); + expect(response.body.currentPeriod.timeseries).to.eql([]); + expect(response.body.previousPeriod.timeseries).to.eql([]); + expect(response.status).to.be(200); + }); + }); + + describe('with data loaded', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateMobileData({ + apmSynthtraceEsClient, + start, + end, + }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('when data is loaded', () => { + it('returns timeseries for sessions chart', async () => { + const response = await getSessionsChart({ serviceName: 'synth-android', offset: '1d' }); + + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( + true + ); + + expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); + expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); + }); + + it('returns only current period timeseries when offset is not available', async () => { + const response = await getSessionsChart({ serviceName: 'synth-android' }); + + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( + true + ); + + expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); + expect(response.body.previousPeriod.timeseries).to.eql([]); + }); + }); + + describe('when filters are applied', () => { + it('returns empty state for filters', async () => { + const response = await getSessionsChart({ + serviceName: 'synth-android', + environment: 'production', + kuery: `app.version:"none"`, + }); + + expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); + expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql( + true + ); + }); + + it('returns the correct values filter is applied', async () => { + const response = await getSessionsChart({ + serviceName: 'synth-android', + environment: 'production', + kuery: `transaction.name : "Start View - View Appearing"`, + }); + + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( + true + ); + + expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); + expect(response.body.previousPeriod.timeseries).to.eql([]); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_stats.spec.ts similarity index 56% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_stats.spec.ts index 0b1e71471a2b4..22281cd392951 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_stats.spec.ts @@ -7,11 +7,11 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; import { meanBy, sumBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>; @@ -134,10 +134,11 @@ async function generateData({ ]); } -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -170,7 +171,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when('Mobile stats when data is not loaded', { config: 'basic', archives: [] }, () => { + describe('Mobile stats', () => { describe('when no data', () => { it('handles empty state', async () => { const response = await getMobileStats({ serviceName: 'foo' }); @@ -182,109 +183,110 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177392 - registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateData({ - apmSynthtraceEsClient, - start, - end, - }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - let response: MobileStats; + describe('Mobile stats', () => { before(async () => { - response = await getMobileStats({ - serviceName: 'synth-android', - environment: 'production', + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateData({ + apmSynthtraceEsClient, + start, + end, }); }); - it('returns same sessions', () => { - const { value, timeseries } = response.currentPeriod.sessions; - const timeseriesTotal = sumBy(timeseries, 'y'); - expect(value).to.be(timeseriesTotal); - }); + after(() => apmSynthtraceEsClient.clean()); - it('returns same requests', () => { - const { value, timeseries } = response.currentPeriod.requests; - const timeseriesTotal = sumBy(timeseries, 'y'); - expect(value).to.be(timeseriesTotal); - }); + describe('when data is loaded', () => { + let response: MobileStats; - it('returns same crashes', () => { - const { value, timeseries } = response.currentPeriod.crashRate; - const timeseriesMean = meanBy( - timeseries.filter((bucket) => bucket.y !== 0), - 'y' - ); - expect(value).to.be(timeseriesMean); - }); - it('returns same launch times', () => { - const { value, timeseries } = response.currentPeriod.launchTimes; - const timeseriesMean = meanBy( - timeseries.filter((bucket) => bucket.y !== null), - 'y' - ); - expect(value).to.be(timeseriesMean); - }); - }); + before(async () => { + response = await getMobileStats({ + serviceName: 'synth-android', + environment: 'production', + }); + }); - describe('when filters are applied', () => { - it('returns empty state for filters', async () => { - const response = await getMobileStats({ - serviceName: 'synth-android', - environment: 'production', - kuery: `app.version:"none"`, + it('returns same sessions', () => { + const { value, timeseries } = response.currentPeriod.sessions; + const timeseriesTotal = sumBy(timeseries, 'y'); + expect(value).to.be(timeseriesTotal); }); - expect(response.currentPeriod.sessions.value).to.eql(0); - expect(response.currentPeriod.requests.value).to.eql(0); - expect(response.currentPeriod.crashRate.value).to.eql(0); - expect(response.currentPeriod.launchTimes.value).to.eql(null); + it('returns same requests', () => { + const { value, timeseries } = response.currentPeriod.requests; + const timeseriesTotal = sumBy(timeseries, 'y'); + expect(value).to.be(timeseriesTotal); + }); - expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql( - true - ); - expect( - response.currentPeriod.launchTimes.timeseries.every((item) => item.y === null) - ).to.eql(true); + it('returns same crashes', () => { + const { value, timeseries } = response.currentPeriod.crashRate; + const timeseriesMean = meanBy( + timeseries.filter((bucket) => bucket.y !== 0), + 'y' + ); + expect(value).to.be(timeseriesMean); + }); + it('returns same launch times', () => { + const { value, timeseries } = response.currentPeriod.launchTimes; + const timeseriesMean = meanBy( + timeseries.filter((bucket) => bucket.y !== null), + 'y' + ); + expect(value).to.be(timeseriesMean); + }); }); - it('returns the correct values when single filter is applied', async () => { - const response = await getMobileStats({ - serviceName: 'synth-android', - environment: 'production', - kuery: `service.version:"2.3"`, + describe('when filters are applied', () => { + it('returns empty state for filters', async () => { + const response = await getMobileStats({ + serviceName: 'synth-android', + environment: 'production', + kuery: `app.version:"none"`, + }); + + expect(response.currentPeriod.sessions.value).to.eql(0); + expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(0); + expect(response.currentPeriod.launchTimes.value).to.eql(null); + + expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql( + true + ); + expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql( + true + ); + expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql( + true + ); + expect( + response.currentPeriod.launchTimes.timeseries.every((item) => item.y === null) + ).to.eql(true); }); - expect(response.currentPeriod.sessions.value).to.eql(3); - expect(response.currentPeriod.requests.value).to.eql(0); - expect(response.currentPeriod.crashRate.value).to.eql(3); - expect(response.currentPeriod.launchTimes.value).to.eql(null); - }); + it('returns the correct values when single filter is applied', async () => { + const response = await getMobileStats({ + serviceName: 'synth-android', + environment: 'production', + kuery: `service.version:"2.3"`, + }); + + expect(response.currentPeriod.sessions.value).to.eql(3); + expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(3); + expect(response.currentPeriod.launchTimes.value).to.eql(null); + }); - it('returns the correct values when multiple filters are applied', async () => { - const response = await getMobileStats({ - serviceName: 'synth-android', - kuery: `service.version:"1.2" and service.environment: "production"`, + it('returns the correct values when multiple filters are applied', async () => { + const response = await getMobileStats({ + serviceName: 'synth-android', + kuery: `service.version:"1.2" and service.environment: "production"`, + }); + expect(response.currentPeriod.sessions.value).to.eql(3); + expect(response.currentPeriod.requests.value).to.eql(3); + expect(response.currentPeriod.crashRate.value).to.eql(1); + expect(response.currentPeriod.launchTimes.value).to.eql(100); }); - expect(response.currentPeriod.sessions.value).to.eql(3); - expect(response.currentPeriod.requests.value).to.eql(3); - expect(response.currentPeriod.crashRate.value).to.eql(1); - expect(response.currentPeriod.launchTimes.value).to.eql(100); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_terms_by_field.spec.ts similarity index 70% rename from x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_terms_by_field.spec.ts index 3ccdba0a24236..8dd4142109375 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/mobile_terms_by_field.spec.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; // we generate 3 transactions per each mobile device // timerange 15min, interval 5m, rate 1 @@ -124,10 +124,11 @@ async function generateData({ ]); } -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const synthtrace = getService('synthtrace'); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -163,7 +164,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .then(({ body }) => body); } - registry.when('Mobile terms when data is not loaded', { config: 'basic', archives: [] }, () => { + describe('Mobile terms', () => { describe('when no data', () => { it('handles empty state', async () => { const response = await getMobileTermsByField({ @@ -183,66 +184,67 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.terms).to.eql([]); }); }); - }); - // FLAKY: https://github.com/elastic/kibana/issues/177498 - registry.when.skip('Mobile terms', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateData({ - apmSynthtraceEsClient, - start, - end, + describe('Mobile terms', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + await generateData({ + apmSynthtraceEsClient, + start, + end, + }); }); - }); - after(() => apmSynthtraceEsClient.clean()); + after(() => apmSynthtraceEsClient.clean()); - describe('when data is loaded', () => { - it('returns mobile devices', async () => { - const response = await getMobileTermsByField({ - serviceName: 'synth-android', - environment: 'production', - fieldName: 'device.model.identifier', - size: 10, + describe('when data is loaded', () => { + it('returns mobile devices', async () => { + const response = await getMobileTermsByField({ + serviceName: 'synth-android', + environment: 'production', + fieldName: 'device.model.identifier', + size: 10, + }); + expect(response.terms).to.eql([ + { label: 'HUAWEI P2-0000', count: 3 }, + { label: 'SM-G973F', count: 3 }, + ]); }); - expect(response.terms).to.eql([ - { label: 'HUAWEI P2-0000', count: 3 }, - { label: 'SM-G973F', count: 3 }, - ]); - }); - it('returns mobile versions', async () => { - const response = await getMobileTermsByField({ - serviceName: 'synth-android', - environment: 'production', - fieldName: 'service.version', - size: 10, + it('returns mobile versions', async () => { + const response = await getMobileTermsByField({ + serviceName: 'synth-android', + environment: 'production', + fieldName: 'service.version', + size: 10, + }); + expect(response.terms).to.eql([ + { + label: '1.2', + count: 3, + }, + { + label: '2.3', + count: 3, + }, + ]); }); - expect(response.terms).to.eql([ - { - label: '1.2', - count: 3, - }, - { - label: '2.3', - count: 3, - }, - ]); - }); - it('return the most used mobile version', async () => { - const response = await getMobileTermsByField({ - serviceName: 'synth-android', - environment: 'production', - fieldName: 'service.version', - size: 1, + it('return the most used mobile version', async () => { + const response = await getMobileTermsByField({ + serviceName: 'synth-android', + environment: 'production', + fieldName: 'service.version', + size: 1, + }); + expect(response.terms).to.eql([ + { + label: '1.2', + count: 3, + }, + ]); }); - expect(response.terms).to.eql([ - { - label: '1.2', - count: 3, - }, - ]); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts deleted file mode 100644 index a36036b3ec8e2..0000000000000 --- a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import expect from '@kbn/expect'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -type ErrorGroups = - APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['errorGroups']; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const serviceName = 'synth-swift'; - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['params'] - > - ) { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics', - params: { - path: { serviceName, ...overrides?.path }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - ...overrides?.query, - }, - }, - }); - } - - registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await callApi(); - expect(response.status).to.be(200); - expect(response.body.errorGroups).to.empty(); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177651 - registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { - describe('errors group', () => { - const appleTransaction = { - name: 'GET /apple 🍎 ', - successRate: 75, - failureRate: 25, - }; - - const bananaTransaction = { - name: 'GET /banana 🍌', - successRate: 50, - failureRate: 50, - }; - - before(async () => { - const serviceInstance = apm - .service({ name: serviceName, environment: 'production', agentName: 'swift' }) - .instance('instance-a'); - - await apmSynthtraceEsClient.index([ - timerange(start, end) - .interval('1m') - .rate(appleTransaction.successRate) - .generator((timestamp) => - serviceInstance - .transaction({ transactionName: appleTransaction.name }) - .timestamp(timestamp) - .duration(1000) - .success() - ), - timerange(start, end) - .interval('1m') - .rate(appleTransaction.failureRate) - .generator((timestamp) => - serviceInstance - .transaction({ transactionName: appleTransaction.name }) - .errors( - serviceInstance - .crash({ - message: 'crash 1', - }) - .timestamp(timestamp) - ) - .duration(1000) - .timestamp(timestamp) - .failure() - ), - timerange(start, end) - .interval('1m') - .rate(bananaTransaction.successRate) - .generator((timestamp) => - serviceInstance - .transaction({ transactionName: bananaTransaction.name }) - .timestamp(timestamp) - .duration(1000) - .success() - ), - timerange(start, end) - .interval('1m') - .rate(bananaTransaction.failureRate) - .generator((timestamp) => - serviceInstance - .transaction({ transactionName: bananaTransaction.name }) - .errors( - serviceInstance - .crash({ - message: 'crash 2', - }) - .timestamp(timestamp) - ) - .duration(1000) - .timestamp(timestamp) - .failure() - ), - ]); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('returns the correct data', () => { - let errorGroups: ErrorGroups; - before(async () => { - const response = await callApi(); - errorGroups = response.body.errorGroups; - }); - it('returns correct number of crashes', () => { - expect(errorGroups.length).to.equal(2); - expect(errorGroups.map((error) => error.name).sort()).to.eql(['crash 1', 'crash 2']); - }); - - it('returns correct occurrences', () => { - const numberOfBuckets = 15; - expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([ - appleTransaction.failureRate * numberOfBuckets, - bananaTransaction.failureRate * numberOfBuckets, - ]); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts deleted file mode 100644 index 2fabce70d2696..0000000000000 --- a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import expect from '@kbn/expect'; -import { first, last, sumBy } from 'lodash'; -import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; -import { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { config, generateData } from './generate_data'; - -type ErrorsDistribution = - APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const serviceName = 'synth-swift'; - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>['params'] - > - ) { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution', - params: { - path: { - serviceName, - ...overrides?.path, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - ...overrides?.query, - }, - }, - }); - return response; - } - - registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await callApi(); - expect(response.status).to.be(200); - expect(response.body.currentPeriod.length).to.be(0); - expect(response.body.previousPeriod.length).to.be(0); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177652 - registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { - describe('errors distribution', () => { - const { appleTransaction, bananaTransaction } = config; - before(async () => { - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('without comparison', () => { - let errorsDistribution: ErrorsDistribution; - before(async () => { - const response = await callApi(); - errorsDistribution = response.body; - }); - - it('displays combined number of occurrences', () => { - const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); - const numberOfBuckets = 15; - expect(countSum).to.equal( - (appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets - ); - }); - - describe('displays correct start in errors distribution chart', () => { - let errorsDistributionWithComparison: ErrorsDistribution; - before(async () => { - const responseWithComparison = await callApi({ - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - offset: '15m', - }, - }); - errorsDistributionWithComparison = responseWithComparison.body; - }); - it('has same start time when comparison is enabled', () => { - expect(first(errorsDistribution.currentPeriod)?.x).to.equal( - first(errorsDistributionWithComparison.currentPeriod)?.x - ); - }); - }); - }); - - describe('displays occurrences for type "apple transaction" only', () => { - let errorsDistribution: ErrorsDistribution; - before(async () => { - const response = await callApi({ - query: { kuery: `error.exception.type:"${appleTransaction.name}"` }, - }); - errorsDistribution = response.body; - }); - it('displays combined number of occurrences', () => { - const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); - const numberOfBuckets = 15; - expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets); - }); - }); - - describe('with comparison', () => { - describe('when data is returned', () => { - let errorsDistribution: ErrorsDistribution; - before(async () => { - const fiveMinutes = 5 * 60 * 1000; - const response = await callApi({ - query: { - start: new Date(end - fiveMinutes).toISOString(), - end: new Date(end).toISOString(), - offset: '5m', - }, - }); - errorsDistribution = response.body; - }); - it('returns some data', () => { - const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) => - isFiniteNumber(y) - ); - - const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) => - isFiniteNumber(y) - ); - - expect(hasCurrentPeriodData).to.equal(true); - expect(hasPreviousPeriodData).to.equal(true); - }); - - it('has same start time for both periods', () => { - expect(first(errorsDistribution.currentPeriod)?.x).to.equal( - first(errorsDistribution.previousPeriod)?.x - ); - }); - - it('has same end time for both periods', () => { - expect(last(errorsDistribution.currentPeriod)?.x).to.equal( - last(errorsDistribution.previousPeriod)?.x - ); - }); - - it('returns same number of buckets for both periods', () => { - expect(errorsDistribution.currentPeriod.length).to.equal( - errorsDistribution.previousPeriod.length - ); - }); - }); - - describe('when no data is returned', () => { - let errorsDistribution: ErrorsDistribution; - before(async () => { - const response = await callApi({ - query: { - start: '2021-01-03T00:00:00.000Z', - end: '2021-01-03T00:15:00.000Z', - offset: '1d', - }, - }); - errorsDistribution = response.body; - }); - - it('has same start time for both periods', () => { - expect(first(errorsDistribution.currentPeriod)?.x).to.equal( - first(errorsDistribution.previousPeriod)?.x - ); - }); - - it('has same end time for both periods', () => { - expect(last(errorsDistribution.currentPeriod)?.x).to.equal( - last(errorsDistribution.previousPeriod)?.x - ); - }); - - it('returns same number of buckets for both periods', () => { - expect(errorsDistribution.currentPeriod.length).to.equal( - errorsDistribution.previousPeriod.length - ); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts deleted file mode 100644 index 129cbe2a71809..0000000000000 --- a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import expect from '@kbn/expect'; -import { timerange } from '@kbn/apm-synthtrace-client'; -import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; -import { orderBy } from 'lodash'; -import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { config, generateData } from './generate_data'; - -type ErrorGroupSamples = - APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>; - -type ErrorSampleDetails = - APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const serviceName = 'synth-go'; - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function callErrorGroupSamplesApi({ groupId }: { groupId: string }) { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples', - params: { - path: { - serviceName, - groupId, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }, - }); - return response; - } - - async function callErrorSampleDetailsApi(errorId: string) { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}', - params: { - path: { - serviceName, - groupId: 'foo', - errorId, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }, - }); - return response; - } - - registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await callErrorGroupSamplesApi({ groupId: 'foo' }); - expect(response.status).to.be(200); - expect(response.body.occurrencesCount).to.be(0); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177654 - registry.when.skip('when samples data is loaded', { config: 'basic', archives: [] }, () => { - const { bananaTransaction } = config; - describe('error group id', () => { - before(async () => { - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('return correct data', () => { - let errorsSamplesResponse: ErrorGroupSamples; - before(async () => { - const response = await callErrorGroupSamplesApi({ - groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03', - }); - errorsSamplesResponse = response.body; - }); - - it('displays correct number of occurrences', () => { - const numberOfBuckets = 15; - expect(errorsSamplesResponse.occurrencesCount).to.equal( - bananaTransaction.failureRate * numberOfBuckets - ); - }); - }); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177665 - registry.when.skip('when error sample data is loaded', { config: 'basic', archives: [] }, () => { - describe('error sample id', () => { - before(async () => { - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('return correct data', () => { - let errorSampleDetailsResponse: ErrorSampleDetails; - before(async () => { - const errorsSamplesResponse = await callErrorGroupSamplesApi({ - groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03', - }); - - const errorId = errorsSamplesResponse.body.errorSampleIds[0]; - - const response = await callErrorSampleDetailsApi(errorId); - errorSampleDetailsResponse = response.body; - }); - - it('displays correct error grouping_key', () => { - expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal( - '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03' - ); - }); - - it('displays correct error message', () => { - expect(errorSampleDetailsResponse.error.error.exception?.[0].message).to.equal('Error 1'); - }); - }); - }); - - describe('with sampled and unsampled transactions', () => { - let errorGroupSamplesResponse: ErrorGroupSamples; - - before(async () => { - const instance = service(serviceName, 'production', 'go').instance('a'); - const errorMessage = 'Error 1'; - const groupId = getErrorGroupingKey(errorMessage); - - await apmSynthtraceEsClient.index([ - timerange(start, end) - .interval('15m') - .rate(1) - .generator((timestamp) => { - return [ - instance - .transaction('GET /api/foo') - .duration(100) - .timestamp(timestamp) - .sample(false) - .errors( - instance.error({ message: errorMessage }).timestamp(timestamp), - instance.error({ message: errorMessage }).timestamp(timestamp + 1) - ), - instance - .transaction('GET /api/foo') - .duration(100) - .timestamp(timestamp) - .sample(true) - .errors(instance.error({ message: errorMessage }).timestamp(timestamp)), - ]; - }), - ]); - - errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body; - }); - - after(() => apmSynthtraceEsClient.clean()); - - it('returns the errors in the correct order (sampled first, then unsampled)', () => { - const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10)); - - // this checks whether the order of indexing is different from the order that is returned - // if it is not, scoring/sorting is broken - expect(errorGroupSamplesResponse.errorSampleIds.length).to.be(3); - expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors)); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts deleted file mode 100644 index cde19d07344d6..0000000000000 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { generateMobileData } from './generate_mobile_data'; - -type MostUsedCharts = - APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/most_used_charts'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2023-01-01T00:00:00.000Z').getTime(); - const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; - - async function getMobileMostUsedCharts({ - environment = ENVIRONMENT_ALL.value, - kuery = '', - serviceName, - transactionType = 'mobile', - }: { - environment?: string; - kuery?: string; - serviceName: string; - transactionType?: string; - }) { - return await apmApiClient - .readUser({ - endpoint: 'GET /internal/apm/mobile-services/{serviceName}/most_used_charts', - params: { - path: { serviceName }, - query: { - environment, - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - kuery, - transactionType, - }, - }, - }) - .then(({ body }) => body); - } - - registry.when( - 'Most used charts when data is not loaded', - { config: 'basic', archives: [] }, - () => { - describe('when no data', () => { - it('handles empty state', async () => { - const response: MostUsedCharts = await getMobileMostUsedCharts({ serviceName: 'foo' }); - expect(response.mostUsedCharts.length).to.eql(4); - expect(response.mostUsedCharts.every((chart) => chart.options.length === 0)).to.eql(true); - }); - }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177394 - registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateMobileData({ - apmSynthtraceEsClient, - start, - end, - }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - let response: MostUsedCharts; - - before(async () => { - response = await getMobileMostUsedCharts({ - serviceName: 'synth-android', - environment: 'production', - }); - }); - - it('should get the top 5 and the other option only', () => { - const deviceOptions = response.mostUsedCharts.find( - (chart) => chart.key === 'device' - )?.options; - expect(deviceOptions?.length).to.eql(6); - expect(deviceOptions?.find((option) => option.key === 'other')).to.not.be(undefined); - }); - - it('should get network connection type object from span events', () => { - const nctOptions = response.mostUsedCharts.find( - (chart) => chart.key === 'netConnectionType' - )?.options; - expect(nctOptions?.length).to.eql(2); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts deleted file mode 100644 index f7f3092935c31..0000000000000 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { generateMobileData } from './generate_mobile_data'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2023-01-01T00:00:00.000Z').getTime(); - const end = new Date('2023-01-01T02:00:00.000Z').getTime(); - - async function getSessionsChart({ - environment = ENVIRONMENT_ALL.value, - kuery = '', - serviceName, - transactionType = 'mobile', - offset, - }: { - environment?: string; - kuery?: string; - serviceName: string; - transactionType?: string; - offset?: string; - }) { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions', - params: { - path: { serviceName }, - query: { - environment, - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - offset, - kuery, - transactionType, - }, - }, - }); - } - - registry.when.skip('without data loaded', { config: 'basic', archives: [] }, () => { - describe('when no data', () => { - it('handles empty state', async () => { - const response = await getSessionsChart({ serviceName: 'foo' }); - expect(response.body.currentPeriod.timeseries).to.eql([]); - expect(response.body.previousPeriod.timeseries).to.eql([]); - expect(response.status).to.be(200); - }); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177393 - registry.when.skip('with data loaded', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateMobileData({ - apmSynthtraceEsClient, - start, - end, - }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('when data is loaded', () => { - it('returns timeseries for sessions chart', async () => { - const response = await getSessionsChart({ serviceName: 'synth-android', offset: '1d' }); - - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( - true - ); - - expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); - expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); - }); - - it('returns only current period timeseries when offset is not available', async () => { - const response = await getSessionsChart({ serviceName: 'synth-android' }); - - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( - true - ); - - expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); - expect(response.body.previousPeriod.timeseries).to.eql([]); - }); - }); - - describe('when filters are applied', () => { - it('returns empty state for filters', async () => { - const response = await getSessionsChart({ - serviceName: 'synth-android', - environment: 'production', - kuery: `app.version:"none"`, - }); - - expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); - expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); - }); - - it('returns the correct values filter is applied', async () => { - const response = await getSessionsChart({ - serviceName: 'synth-android', - environment: 'production', - kuery: `transaction.name : "Start View - View Appearing"`, - }); - - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( - true - ); - - expect(response.body.currentPeriod.timeseries[0].y).to.eql(6); - expect(response.body.previousPeriod.timeseries).to.eql([]); - }); - }); - }); -} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 9db41aecbb612..2ba14ceb1218c 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -187,6 +187,6 @@ "@kbn/alerting-types", "@kbn/ai-assistant-common", "@kbn/core-deprecations-common", - "@kbn/usage-collection-plugin", + "@kbn/usage-collection-plugin" ] }