From a4d19608828c2b690eac9445c8b9726bf2901da6 Mon Sep 17 00:00:00 2001 From: Jenny Date: Fri, 8 Nov 2024 15:59:29 +0100 Subject: [PATCH 1/5] [APM] Migrate /test/apm_api_integration/tests/errors/ to be deployment-agnostic API tests --- .../apm/errors/distribution.spec.ts | 208 +++++++++++++++++ .../apm/errors/error_group_list.spec.ts | 154 +++++++++++++ .../apm}/errors/generate_data.ts | 0 .../apm/errors/group_id_samples.spec.ts | 202 +++++++++++++++++ .../apis/observability/apm/errors/index.ts | 20 ++ .../generate_data.ts | 0 .../top_erroneous_transactions.spec.ts | 214 ++++++++++++++++++ .../generate_data.ts | 0 .../top_errors_main_stats.spec.ts | 116 ++++++++++ .../apis/observability/apm/index.ts | 1 + .../apis/observability/apm/utils/common.ts | 13 ++ .../tests/errors/distribution.spec.ts | 203 ----------------- .../tests/errors/error_group_list.spec.ts | 150 ------------ .../tests/errors/group_id_samples.spec.ts | 189 ---------------- .../top_erroneous_transactions.spec.ts | 205 ----------------- .../top_errors_main_stats.spec.ts | 108 --------- 16 files changed, 928 insertions(+), 855 deletions(-) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/errors/generate_data.ts (100%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/index.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/errors/top_erroneous_transactions/generate_data.ts (100%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/errors/top_errors_for_transaction/generate_data.ts (100%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/utils/common.ts delete mode 100644 x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts new file mode 100644 index 0000000000000..3c5f057040b49 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts @@ -0,0 +1,208 @@ +/* + * Copyright 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 { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; +import { config, generateData } from './generate_data'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { isFiniteNumber } from '../utils/common'; + +type ErrorsDistribution = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; + +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 callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/distribution'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/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('Error Distributions', () => { + describe('when data is not loaded', () => { + 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/177336 + describe('when data is loaded', () => { + describe('errors distribution', () => { + const { appleTransaction, bananaTransaction } = config; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + 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/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts new file mode 100644 index 0000000000000..569ec82a6d800 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts @@ -0,0 +1,154 @@ +/* + * Copyright 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 type { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +type ErrorGroups = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['errorGroups']; + +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 callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/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('Error Group List', () => { + describe('when data is not loaded', () => { + 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/177382 + 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 () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + const serviceInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .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.error({ message: 'error 1', type: 'foo' }).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.error({ message: 'error 2', type: 'bar' }).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 errors', () => { + expect(errorGroups.length).to.equal(2); + expect(errorGroups.map((error) => error.name).sort()).to.eql(['error 1', 'error 2']); + }); + + it('returns correct occurences', () => { + 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/errors/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/errors/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts new file mode 100644 index 0000000000000..ee17168cae6b2 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts @@ -0,0 +1,202 @@ +/* + * Copyright 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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; +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('Error Group Id Samples', () => { + describe('when data is not loaded', () => { + 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/177397 + describe('when samples data is loaded', () => { + const { bananaTransaction } = config; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + describe('error group id', () => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + 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/177383 + describe('when error sample data is loaded', () => { + describe('error sample id', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + 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; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + const instance = service(serviceName, 'production', 'go').instance('a'); + const errorMessage = 'Error 1'; + const groupId = getErrorGroupingKey(errorMessage); + + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + 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/api_integration/deployment_agnostic/apis/observability/apm/errors/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/index.ts new file mode 100644 index 0000000000000..4061edd217462 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('errors', () => { + loadTestFile(require.resolve('./error_group_list.spec.ts')); + loadTestFile(require.resolve('./group_id_samples.spec.ts')); + loadTestFile(require.resolve('./distribution.spec.ts')); + loadTestFile(require.resolve('./top_errors_for_transaction/top_errors_main_stats.spec.ts')); + loadTestFile( + require.resolve('./top_erroneous_transactions/top_erroneous_transactions.spec.ts') + ); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts new file mode 100644 index 0000000000000..0baf98acb1ba9 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { sumBy, first, last } from 'lodash'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { config, generateData } from './generate_data'; +import { isFiniteNumber } from '../../utils/common'; + +type ErroneousTransactions = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions'>; + +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; + const groupId = '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03'; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions', + params: { + path: { + serviceName, + groupId: 'test', + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + offset: undefined, + numBuckets: 15, + ...overrides?.query, + }, + }, + }); + return response; + } + + describe('Top erroneous transactions', () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.topErroneousTransactions).to.be.empty(); + }); + }); + + // FLAKY: https://github.com/elastic/kibana/issues/177637 + describe('when data is loaded', () => { + const { + firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, + secondTransaction: { + name: secondTransactionName, + failureRate: secondTransactionFailureRate, + }, + } = config; + + describe('returns the correct data', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('without comparison', () => { + const numberOfBuckets = 15; + let erroneousTransactions: ErroneousTransactions; + + before(async () => { + const response = await callApi({ + path: { groupId }, + }); + erroneousTransactions = response.body; + }); + + it('displays the correct number of occurrences', () => { + const { topErroneousTransactions } = erroneousTransactions; + expect(topErroneousTransactions.length).to.be(2); + + const firstTransaction = topErroneousTransactions.find( + (x) => x.transactionName === firstTransactionName + ); + expect(firstTransaction).to.not.be(undefined); + expect(firstTransaction?.occurrences).to.be( + firstTransactionFailureRate * numberOfBuckets + ); + + const secondTransaction = topErroneousTransactions.find( + (x) => x.transactionName === secondTransactionName + ); + expect(secondTransaction).to.not.be(undefined); + expect(secondTransaction?.occurrences).to.be( + secondTransactionFailureRate * numberOfBuckets + ); + }); + + it('displays the correct number of occurrences in time series', () => { + const { topErroneousTransactions } = erroneousTransactions; + + const firstTransaction = topErroneousTransactions.find( + (x) => x.transactionName === firstTransactionName + ); + const firstErrorCount = sumBy(firstTransaction?.currentPeriodTimeseries, 'y'); + expect(firstErrorCount).to.be(firstTransactionFailureRate * numberOfBuckets); + + const secondTransaction = topErroneousTransactions.find( + (x) => x.transactionName === secondTransactionName + ); + const secondErrorCount = sumBy(secondTransaction?.currentPeriodTimeseries, 'y'); + expect(secondErrorCount).to.be(secondTransactionFailureRate * numberOfBuckets); + }); + }); + + describe('with comparison', () => { + describe('when there are data for the time periods', () => { + let erroneousTransactions: ErroneousTransactions; + + before(async () => { + const fiveMinutes = 5 * 60 * 1000; + const response = await callApi({ + path: { groupId }, + query: { + start: new Date(end - fiveMinutes).toISOString(), + end: new Date(end).toISOString(), + offset: '5m', + }, + }); + erroneousTransactions = response.body; + }); + + it('returns some data', () => { + const { topErroneousTransactions } = erroneousTransactions; + + const hasCurrentPeriodData = topErroneousTransactions[0].currentPeriodTimeseries.some( + ({ y }) => isFiniteNumber(y) + ); + + const hasPreviousPeriodData = + topErroneousTransactions[0].previousPeriodTimeseries.some(({ y }) => + isFiniteNumber(y) + ); + + expect(hasCurrentPeriodData).to.be(true); + expect(hasPreviousPeriodData).to.be(true); + }); + + it('has the same start time for both periods', () => { + const { topErroneousTransactions } = erroneousTransactions; + expect(first(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( + first(topErroneousTransactions[0].previousPeriodTimeseries)?.x + ); + }); + + it('has same end time for both periods', () => { + const { topErroneousTransactions } = erroneousTransactions; + expect(last(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( + last(topErroneousTransactions[0].previousPeriodTimeseries)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + const { topErroneousTransactions } = erroneousTransactions; + expect(topErroneousTransactions[0].currentPeriodTimeseries.length).to.be( + topErroneousTransactions[0].previousPeriodTimeseries.length + ); + }); + }); + + describe('when there are no data for the time period', () => { + it('returns an empty array', async () => { + const response = await callApi({ + path: { groupId }, + query: { + start: '2021-01-03T00:00:00.000Z', + end: '2021-01-03T00:15:00.000Z', + offset: '1d', + }, + }); + + const { + body: { topErroneousTransactions }, + } = response; + + expect(topErroneousTransactions).to.be.empty(); + }); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts new file mode 100644 index 0000000000000..d5e744a06c296 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import type { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import moment from 'moment'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { config, generateData } from './generate_data'; + +type ErrorGroups = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name'>['errorGroups']; + +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 callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name', + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + maxNumberOfErrorGroups: 5, + transactionType: 'request', + transactionName: '', + ...overrides?.query, + }, + }, + }); + } + + describe('Top Errors main stats', () => { + describe('when data is not loaded', () => { + 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/177638 + describe('when data is loaded', () => { + describe('top errors for transaction', () => { + const { + firstTransaction: { + name: firstTransactionName, + failureRate: firstTransactionFailureRate, + }, + } = config; + + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('returns the correct data', () => { + const NUMBER_OF_BUCKETS = 15; + let errorGroups: ErrorGroups; + before(async () => { + const response = await callApi({ query: { transactionName: firstTransactionName } }); + errorGroups = response.body.errorGroups; + }); + + it('returns correct number of errors', () => { + expect(errorGroups.length).to.equal(2); + }); + + it('error 1 is correct', () => { + const firstErrorId = `b6c1d4d41b0b60b841f40232497344ba36856fcbea0692a4695562ca73e790bd`; + const firstError = errorGroups.find((x) => x.groupId === firstErrorId); + expect(firstError).to.not.be(undefined); + expect(firstError?.groupId).to.be(firstErrorId); + expect(firstError?.name).to.be(`Error 1 transaction GET /apple 🍎`); + expect(firstError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); + expect(firstError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); + }); + + it('error 2 is correct', () => { + const secondErrorId = `c3f388e4f7276d4fab85aa2fad2d2a42e70637f65cd5ec9f085de28b36e69ba5`; + const secondError = errorGroups.find((x) => x.groupId === secondErrorId); + expect(secondError).to.not.be(undefined); + expect(secondError?.groupId).to.be(secondErrorId); + expect(secondError?.name).to.be(`Error 2 transaction GET /apple 🍎`); + expect(secondError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); + expect(secondError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); + }); + }); + }); + }); + }); +} 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 a62c11d40b1af..c9b8e1208b1b4 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,5 +12,6 @@ export default function apmApiIntegrationTests({ }: DeploymentAgnosticFtrProviderContext) { describe('APM', function () { loadTestFile(require.resolve('./agent_explorer')); + loadTestFile(require.resolve('./errors')); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/utils/common.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/utils/common.ts new file mode 100644 index 0000000000000..e5c9af80c7d69 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/utils/common.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isFinite } from 'lodash'; + +// _.isNumber() returns true for NaN, _.isFinite() does not refine +export function isFiniteNumber(value: any): value is number { + return isFinite(value); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts deleted file mode 100644 index 544ca97817af0..0000000000000 --- a/x-pack/test/apm_api_integration/tests/errors/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/services/{serviceName}/errors/distribution'>; - -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 callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/distribution'>['params'] - > - ) { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/errors/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/177336 - registry.when('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/errors/error_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts deleted file mode 100644 index fd01833cb4f50..0000000000000 --- a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts +++ /dev/null @@ -1,150 +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/services/{serviceName}/errors/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-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 callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['params'] - > - ) { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/errors/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/177382 - registry.when('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: 'go' }) - .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.error({ message: 'error 1', type: 'foo' }).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.error({ message: 'error 2', type: 'bar' }).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 errors', () => { - expect(errorGroups.length).to.equal(2); - expect(errorGroups.map((error) => error.name).sort()).to.eql(['error 1', 'error 2']); - }); - - it('returns correct occurences', () => { - 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/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts deleted file mode 100644 index ea74f1fa622d8..0000000000000 --- a/x-pack/test/apm_api_integration/tests/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/177397 - 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/177383 - 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/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts deleted file mode 100644 index 53b305f093ce4..0000000000000 --- a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ /dev/null @@ -1,205 +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 { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import { sumBy, first, last } from 'lodash'; -import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { config, generateData } from './generate_data'; - -type ErroneousTransactions = - APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions'>; - -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; - const groupId = '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03'; - - async function callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions'>['params'] - > - ) { - const response = await apmApiClient.readUser({ - endpoint: - 'GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions', - params: { - path: { - serviceName, - groupId: 'test', - ...overrides?.path, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - offset: undefined, - numBuckets: 15, - ...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.topErroneousTransactions).to.be.empty(); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177637 - registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { - const { - firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, - secondTransaction: { name: secondTransactionName, failureRate: secondTransactionFailureRate }, - } = config; - - describe('returns the correct data', () => { - before(async () => { - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('without comparison', () => { - const numberOfBuckets = 15; - let erroneousTransactions: ErroneousTransactions; - - before(async () => { - const response = await callApi({ - path: { groupId }, - }); - erroneousTransactions = response.body; - }); - - it.skip('displays the correct number of occurrences', () => { - const { topErroneousTransactions } = erroneousTransactions; - expect(topErroneousTransactions.length).to.be(2); - - const firstTransaction = topErroneousTransactions.find( - (x) => x.transactionName === firstTransactionName - ); - expect(firstTransaction).to.not.be(undefined); - expect(firstTransaction?.occurrences).to.be( - firstTransactionFailureRate * numberOfBuckets - ); - - const secondTransaction = topErroneousTransactions.find( - (x) => x.transactionName === secondTransactionName - ); - expect(secondTransaction).to.not.be(undefined); - expect(secondTransaction?.occurrences).to.be( - secondTransactionFailureRate * numberOfBuckets - ); - }); - - it('displays the correct number of occurrences in time series', () => { - const { topErroneousTransactions } = erroneousTransactions; - - const firstTransaction = topErroneousTransactions.find( - (x) => x.transactionName === firstTransactionName - ); - const firstErrorCount = sumBy(firstTransaction?.currentPeriodTimeseries, 'y'); - expect(firstErrorCount).to.be(firstTransactionFailureRate * numberOfBuckets); - - const secondTransaction = topErroneousTransactions.find( - (x) => x.transactionName === secondTransactionName - ); - const secondErrorCount = sumBy(secondTransaction?.currentPeriodTimeseries, 'y'); - expect(secondErrorCount).to.be(secondTransactionFailureRate * numberOfBuckets); - }); - }); - - describe('with comparison', () => { - describe('when there are data for the time periods', () => { - let erroneousTransactions: ErroneousTransactions; - - before(async () => { - const fiveMinutes = 5 * 60 * 1000; - const response = await callApi({ - path: { groupId }, - query: { - start: new Date(end - fiveMinutes).toISOString(), - end: new Date(end).toISOString(), - offset: '5m', - }, - }); - erroneousTransactions = response.body; - }); - - it('returns some data', () => { - const { topErroneousTransactions } = erroneousTransactions; - - const hasCurrentPeriodData = topErroneousTransactions[0].currentPeriodTimeseries.some( - ({ y }) => isFiniteNumber(y) - ); - - const hasPreviousPeriodData = topErroneousTransactions[0].previousPeriodTimeseries.some( - ({ y }) => isFiniteNumber(y) - ); - - expect(hasCurrentPeriodData).to.be(true); - expect(hasPreviousPeriodData).to.be(true); - }); - - it('has the same start time for both periods', () => { - const { topErroneousTransactions } = erroneousTransactions; - expect(first(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( - first(topErroneousTransactions[0].previousPeriodTimeseries)?.x - ); - }); - - it('has same end time for both periods', () => { - const { topErroneousTransactions } = erroneousTransactions; - expect(last(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( - last(topErroneousTransactions[0].previousPeriodTimeseries)?.x - ); - }); - - it('returns same number of buckets for both periods', () => { - const { topErroneousTransactions } = erroneousTransactions; - expect(topErroneousTransactions[0].currentPeriodTimeseries.length).to.be( - topErroneousTransactions[0].previousPeriodTimeseries.length - ); - }); - }); - - describe('when there are no data for the time period', () => { - it('returns an empty array', async () => { - const response = await callApi({ - path: { groupId }, - query: { - start: '2021-01-03T00:00:00.000Z', - end: '2021-01-03T00:15:00.000Z', - offset: '1d', - }, - }); - - const { - body: { topErroneousTransactions }, - } = response; - - expect(topErroneousTransactions).to.be.empty(); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts deleted file mode 100644 index a6476e76a3918..0000000000000 --- a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ /dev/null @@ -1,108 +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 { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import moment from 'moment'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { config, generateData } from './generate_data'; - -type ErrorGroups = - APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name'>['errorGroups']; - -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 callApi( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name'>['params'] - > - ) { - return await apmApiClient.readUser({ - endpoint: - 'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name', - params: { - path: { serviceName, ...overrides?.path }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - maxNumberOfErrorGroups: 5, - transactionType: 'request', - transactionName: '', - ...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/177638 - registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { - describe('top errors for transaction', () => { - const { - firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, - } = config; - - before(async () => { - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('returns the correct data', () => { - const NUMBER_OF_BUCKETS = 15; - let errorGroups: ErrorGroups; - before(async () => { - const response = await callApi({ query: { transactionName: firstTransactionName } }); - errorGroups = response.body.errorGroups; - }); - - it('returns correct number of errors', () => { - expect(errorGroups.length).to.equal(2); - }); - - it('error 1 is correct', () => { - const firstErrorId = `b6c1d4d41b0b60b841f40232497344ba36856fcbea0692a4695562ca73e790bd`; - const firstError = errorGroups.find((x) => x.groupId === firstErrorId); - expect(firstError).to.not.be(undefined); - expect(firstError?.groupId).to.be(firstErrorId); - expect(firstError?.name).to.be(`Error 1 transaction GET /apple 🍎`); - expect(firstError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); - expect(firstError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); - }); - - it('error 2 is correct', () => { - const secondErrorId = `c3f388e4f7276d4fab85aa2fad2d2a42e70637f65cd5ec9f085de28b36e69ba5`; - const secondError = errorGroups.find((x) => x.groupId === secondErrorId); - expect(secondError).to.not.be(undefined); - expect(secondError?.groupId).to.be(secondErrorId); - expect(secondError?.name).to.be(`Error 2 transaction GET /apple 🍎`); - expect(secondError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); - expect(secondError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); - }); - }); - }); - }); -} From c24520e49706471f70df5e47803ba065baaa9d96 Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 11 Nov 2024 15:28:54 +0100 Subject: [PATCH 2/5] Change generate data --- .../apm/errors/distribution.spec.ts | 2 +- .../observability/apm/errors/generate_data.ts | 35 ++++++++----------- .../apm/errors/group_id_samples.spec.ts | 10 +++--- .../generate_data.ts | 2 +- .../top_erroneous_transactions.spec.ts | 2 +- .../generate_data.ts | 2 +- .../top_errors_main_stats.spec.ts | 2 +- 7 files changed, 25 insertions(+), 30 deletions(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts index 3c5f057040b49..2089085ec5056 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts @@ -69,7 +69,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + return generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts index a7e627a048e05..63995546fabd3 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts @@ -35,14 +35,12 @@ export async function generateData({ .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); - const interval = '1m'; - const { bananaTransaction, appleTransaction } = config; + const interval = timerange(start, end).interval('1m'); const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { return [ - timerange(start, end) - .interval(interval) + interval .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance @@ -51,23 +49,20 @@ export async function generateData({ .duration(1000) .success() ), - timerange(start, end) - .interval(interval) - .rate(transaction.failureRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: transaction.name }) - .errors( - serviceGoProdInstance - .error({ message: `Error ${index}`, type: transaction.name }) - .timestamp(timestamp) - ) - .duration(1000) - .timestamp(timestamp) - .failure() - ), + interval.rate(transaction.failureRate).generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: transaction.name }) + .errors( + serviceGoProdInstance + .error({ message: `Error ${index}`, type: transaction.name }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), ]; }); - await apmSynthtraceEsClient.index(documents); + return await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts index ee17168cae6b2..ffc3aa8e26099 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts @@ -83,7 +83,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon describe('error group id', () => { before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + return generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); @@ -114,7 +114,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + return generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); @@ -156,7 +156,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon const groupId = getErrorGroupingKey(errorMessage); apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await apmSynthtraceEsClient.index([ + await apmSynthtraceEsClient.index( timerange(start, end) .interval('15m') .rate(1) @@ -178,8 +178,8 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon .sample(true) .errors(instance.error({ message: errorMessage }).timestamp(timestamp)), ]; - }), - ]); + }) + ); errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body; }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts index 7732d85efa58f..2e3a63bf13947 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts @@ -69,5 +69,5 @@ export async function generateData({ ]; }); - await apmSynthtraceEsClient.index(documents); + return await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts index 0baf98acb1ba9..7b355ba394948 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -80,7 +80,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + return generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts index 9f983fbb8877b..35fabcfbf86ec 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts @@ -72,5 +72,5 @@ export async function generateData({ ]; }); - await apmSynthtraceEsClient.index(documents); + return await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts index d5e744a06c296..4318ab5f8b92f 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -73,7 +73,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + return generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); From 3def34218f2eddcd6cf0986bbf9728f72d82fb15 Mon Sep 17 00:00:00 2001 From: Jenny Date: Tue, 12 Nov 2024 21:17:13 +0100 Subject: [PATCH 3/5] Test fixes --- .../apm/errors/distribution.spec.ts | 2 +- .../apm/errors/error_group_list.spec.ts | 6 +- .../observability/apm/errors/generate_data.ts | 2 +- .../apm/errors/group_id_samples.spec.ts | 58 +++++------ .../generate_data.ts | 12 +-- .../top_erroneous_transactions.spec.ts | 96 +++++++++---------- .../generate_data.ts | 2 +- .../top_errors_main_stats.spec.ts | 2 +- 8 files changed, 84 insertions(+), 96 deletions(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts index 2089085ec5056..3c5f057040b49 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts @@ -69,7 +69,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - return generateData({ serviceName, start, end, apmSynthtraceEsClient }); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts index 569ec82a6d800..82b208e9b914d 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts @@ -25,11 +25,11 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon 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( + const callApi = async ( overrides?: RecursivePartial< APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['params'] > - ) { + ) => { return await apmApiClient.readUser({ endpoint: 'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics', params: { @@ -43,7 +43,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }, }, }); - } + }; describe('Error Group List', () => { describe('when data is not loaded', () => { it('handles empty state', async () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts index 63995546fabd3..ff7de94f951b6 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts @@ -64,5 +64,5 @@ export async function generateData({ ]; }); - return await apmSynthtraceEsClient.index(documents); + await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts index ffc3aa8e26099..818900bd7ca51 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts @@ -9,7 +9,6 @@ import { timerange } from '@kbn/apm-synthtrace-client'; import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; import { orderBy } from 'lodash'; import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { config, generateData } from './generate_data'; @@ -67,42 +66,38 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon return response; } describe('Error Group Id Samples', () => { - describe('when data is not loaded', () => { - 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); - }); + 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); }); // FLAKY: https://github.com/elastic/kibana/issues/177397 describe('when samples data is loaded', () => { + let errorsSamplesResponse: ErrorGroupSamples; const { bananaTransaction } = config; - let apmSynthtraceEsClient: ApmSynthtraceEsClient; describe('error group id', () => { before(async () => { - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - return generateData({ serviceName, start, end, apmSynthtraceEsClient }); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + const response = await callErrorGroupSamplesApi({ + groupId: '0000000000000000000000000Error 1', + }); + errorsSamplesResponse = response.body; }); 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 - ); - }); + it('displays correct number of occurrences', () => { + const numberOfBuckets = 15; + expect(errorsSamplesResponse.occurrencesCount).to.equal( + bananaTransaction.failureRate * numberOfBuckets + ); }); }); }); @@ -110,11 +105,8 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon // FLAKY: https://github.com/elastic/kibana/issues/177383 describe('when error sample data is loaded', () => { describe('error sample id', () => { - let apmSynthtraceEsClient: ApmSynthtraceEsClient; - before(async () => { - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - return generateData({ serviceName, start, end, apmSynthtraceEsClient }); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); @@ -123,7 +115,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon let errorSampleDetailsResponse: ErrorSampleDetails; before(async () => { const errorsSamplesResponse = await callErrorGroupSamplesApi({ - groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03', + groupId: '0000000000000000000000000Error 1', }); const errorId = errorsSamplesResponse.body.errorSampleIds[0]; @@ -134,7 +126,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon it('displays correct error grouping_key', () => { expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal( - '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03' + '0000000000000000000000000Error 1' ); }); @@ -148,14 +140,12 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon describe('with sampled and unsampled transactions', () => { let errorGroupSamplesResponse: ErrorGroupSamples; - let apmSynthtraceEsClient: ApmSynthtraceEsClient; before(async () => { const instance = service(serviceName, 'production', 'go').instance('a'); const errorMessage = 'Error 1'; - const groupId = getErrorGroupingKey(errorMessage); + const groupId = '0000000000000000000000000Error 1'; - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await apmSynthtraceEsClient.index( timerange(start, end) .interval('15m') diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts index 2e3a63bf13947..a7e627a048e05 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts @@ -8,12 +8,12 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; export const config = { - firstTransaction: { + appleTransaction: { name: 'GET /apple 🍎 ', successRate: 75, failureRate: 25, }, - secondTransaction: { + bananaTransaction: { name: 'GET /banana 🍌', successRate: 50, failureRate: 50, @@ -37,9 +37,9 @@ export async function generateData({ const interval = '1m'; - const { firstTransaction, secondTransaction } = config; + const { bananaTransaction, appleTransaction } = config; - const documents = [firstTransaction, secondTransaction].flatMap((transaction) => { + const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { return [ timerange(start, end) .interval(interval) @@ -59,7 +59,7 @@ export async function generateData({ .transaction({ transactionName: transaction.name }) .errors( serviceGoProdInstance - .error({ message: 'Error 1', type: transaction.name }) + .error({ message: `Error ${index}`, type: transaction.name }) .timestamp(timestamp) ) .duration(1000) @@ -69,5 +69,5 @@ export async function generateData({ ]; }); - return await apmSynthtraceEsClient.index(documents); + await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts index 7b355ba394948..2ce51f06fe856 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -26,7 +26,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon 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; - const groupId = '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03'; + const groupId = '0000000000000000000000000Error 1'; async function callApi( overrides?: RecursivePartial< @@ -39,7 +39,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon params: { path: { serviceName, - groupId: 'test', + groupId, ...overrides?.path, }, query: { @@ -57,39 +57,34 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon } describe('Top erroneous transactions', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await callApi(); - expect(response.status).to.be(200); - expect(response.body.topErroneousTransactions).to.be.empty(); - }); + 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.topErroneousTransactions).to.be.empty(); }); // FLAKY: https://github.com/elastic/kibana/issues/177637 describe('when data is loaded', () => { const { - firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, - secondTransaction: { - name: secondTransactionName, - failureRate: secondTransactionFailureRate, + bananaTransaction: { + name: bananaTransactionName, + failureRate: bananaTransactionFailureRate, }, + appleTransaction: { name: appleTransactionName, failureRate: appleTransactionFailureRate }, } = config; describe('returns the correct data', () => { - let apmSynthtraceEsClient: ApmSynthtraceEsClient; - - before(async () => { - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - return generateData({ serviceName, start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - describe('without comparison', () => { const numberOfBuckets = 15; let erroneousTransactions: ErroneousTransactions; before(async () => { + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); const response = await callApi({ path: { groupId }, }); @@ -100,37 +95,37 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon const { topErroneousTransactions } = erroneousTransactions; expect(topErroneousTransactions.length).to.be(2); - const firstTransaction = topErroneousTransactions.find( - (x) => x.transactionName === firstTransactionName + const bananaTransaction = topErroneousTransactions.find( + (x) => x.transactionName === bananaTransactionName ); - expect(firstTransaction).to.not.be(undefined); - expect(firstTransaction?.occurrences).to.be( - firstTransactionFailureRate * numberOfBuckets + expect(bananaTransaction).to.not.be(undefined); + expect(bananaTransaction?.occurrences).to.be( + bananaTransactionFailureRate * numberOfBuckets ); - const secondTransaction = topErroneousTransactions.find( - (x) => x.transactionName === secondTransactionName + const appleTransaction = topErroneousTransactions.find( + (x) => x.transactionName === appleTransactionName ); - expect(secondTransaction).to.not.be(undefined); - expect(secondTransaction?.occurrences).to.be( - secondTransactionFailureRate * numberOfBuckets + expect(appleTransaction).to.not.be(undefined); + expect(appleTransaction?.occurrences).to.be( + appleTransactionFailureRate * numberOfBuckets ); }); it('displays the correct number of occurrences in time series', () => { const { topErroneousTransactions } = erroneousTransactions; - const firstTransaction = topErroneousTransactions.find( - (x) => x.transactionName === firstTransactionName + const bananaTransaction = topErroneousTransactions.find( + (x) => x.transactionName === bananaTransactionName ); - const firstErrorCount = sumBy(firstTransaction?.currentPeriodTimeseries, 'y'); - expect(firstErrorCount).to.be(firstTransactionFailureRate * numberOfBuckets); + const firstErrorCount = sumBy(bananaTransaction?.currentPeriodTimeseries, 'y'); + expect(firstErrorCount).to.be(bananaTransactionFailureRate * numberOfBuckets); - const secondTransaction = topErroneousTransactions.find( - (x) => x.transactionName === secondTransactionName + const appleTransaction = topErroneousTransactions.find( + (x) => x.transactionName === appleTransactionName ); - const secondErrorCount = sumBy(secondTransaction?.currentPeriodTimeseries, 'y'); - expect(secondErrorCount).to.be(secondTransactionFailureRate * numberOfBuckets); + const secondErrorCount = sumBy(appleTransaction?.currentPeriodTimeseries, 'y'); + expect(secondErrorCount).to.be(appleTransactionFailureRate * numberOfBuckets); }); }); @@ -154,12 +149,13 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon it('returns some data', () => { const { topErroneousTransactions } = erroneousTransactions; - const hasCurrentPeriodData = topErroneousTransactions[0].currentPeriodTimeseries.some( - ({ y }) => isFiniteNumber(y) - ); + const hasCurrentPeriodData = + topErroneousTransactions[0]?.currentPeriodTimeseries.some(({ y }) => + isFiniteNumber(y) + ); const hasPreviousPeriodData = - topErroneousTransactions[0].previousPeriodTimeseries.some(({ y }) => + topErroneousTransactions[0]?.previousPeriodTimeseries.some(({ y }) => isFiniteNumber(y) ); @@ -169,22 +165,22 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon it('has the same start time for both periods', () => { const { topErroneousTransactions } = erroneousTransactions; - expect(first(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( - first(topErroneousTransactions[0].previousPeriodTimeseries)?.x + expect(first(topErroneousTransactions[0]?.currentPeriodTimeseries)?.x).to.be( + first(topErroneousTransactions[0]?.previousPeriodTimeseries)?.x ); }); it('has same end time for both periods', () => { const { topErroneousTransactions } = erroneousTransactions; - expect(last(topErroneousTransactions[0].currentPeriodTimeseries)?.x).to.be( - last(topErroneousTransactions[0].previousPeriodTimeseries)?.x + expect(last(topErroneousTransactions[0]?.currentPeriodTimeseries)?.x).to.be( + last(topErroneousTransactions[0]?.previousPeriodTimeseries)?.x ); }); it('returns same number of buckets for both periods', () => { const { topErroneousTransactions } = erroneousTransactions; - expect(topErroneousTransactions[0].currentPeriodTimeseries.length).to.be( - topErroneousTransactions[0].previousPeriodTimeseries.length + expect(topErroneousTransactions[0]?.currentPeriodTimeseries.length).to.be( + topErroneousTransactions[0]?.previousPeriodTimeseries.length ); }); }); @@ -210,5 +206,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); }); + + after(() => apmSynthtraceEsClient.clean()); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts index 35fabcfbf86ec..9f983fbb8877b 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts @@ -72,5 +72,5 @@ export async function generateData({ ]; }); - return await apmSynthtraceEsClient.index(documents); + await apmSynthtraceEsClient.index(documents); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts index 4318ab5f8b92f..d5e744a06c296 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -73,7 +73,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon before(async () => { apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - return generateData({ serviceName, start, end, apmSynthtraceEsClient }); + await generateData({ serviceName, start, end, apmSynthtraceEsClient }); }); after(() => apmSynthtraceEsClient.clean()); From c673071157b36a74a021303f175eb765f2be9a81 Mon Sep 17 00:00:00 2001 From: Jenny Date: Wed, 13 Nov 2024 16:23:43 +0100 Subject: [PATCH 4/5] Test data fixes and test adjustments --- .../observability/apm/errors/generate_data.ts | 2 +- .../generate_data.ts | 46 ++++++++--------- .../top_erroneous_transactions.spec.ts | 2 +- .../generate_data.ts | 47 ++++++++--------- .../top_errors_main_stats.spec.ts | 50 ++++++++----------- 5 files changed, 65 insertions(+), 82 deletions(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts index ff7de94f951b6..ea22c866bd668 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/generate_data.ts @@ -9,7 +9,7 @@ import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; export const config = { appleTransaction: { - name: 'GET /apple 🍎 ', + name: 'GET /apple 🍎', successRate: 75, failureRate: 25, }, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts index a7e627a048e05..0a2e690ccf966 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/generate_data.ts @@ -9,14 +9,14 @@ import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; export const config = { appleTransaction: { - name: 'GET /apple 🍎 ', - successRate: 75, - failureRate: 25, + name: 'GET /apple 🍎', + successRate: 25, + failureRate: 75, }, bananaTransaction: { name: 'GET /banana 🍌', - successRate: 50, - failureRate: 50, + successRate: 80, + failureRate: 20, }, }; @@ -35,14 +35,12 @@ export async function generateData({ .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); - const interval = '1m'; - const { bananaTransaction, appleTransaction } = config; + const interval = timerange(start, end).interval('1m'); const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { return [ - timerange(start, end) - .interval(interval) + interval .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance @@ -51,21 +49,21 @@ export async function generateData({ .duration(1000) .success() ), - timerange(start, end) - .interval(interval) - .rate(transaction.failureRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: transaction.name }) - .errors( - serviceGoProdInstance - .error({ message: `Error ${index}`, type: transaction.name }) - .timestamp(timestamp) - ) - .duration(1000) - .timestamp(timestamp) - .failure() - ), + interval.rate(transaction.failureRate).generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: transaction.name }) + .errors( + serviceGoProdInstance + .error({ message: `Error 1`, type: transaction.name }) + .timestamp(timestamp), + serviceGoProdInstance + .error({ message: `Error 2`, type: transaction.name }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), ]; }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts index 2ce51f06fe856..0bde11c19a9a1 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -71,11 +71,11 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon // FLAKY: https://github.com/elastic/kibana/issues/177637 describe('when data is loaded', () => { const { + appleTransaction: { name: appleTransactionName, failureRate: appleTransactionFailureRate }, bananaTransaction: { name: bananaTransactionName, failureRate: bananaTransactionFailureRate, }, - appleTransaction: { name: appleTransactionName, failureRate: appleTransactionFailureRate }, } = config; describe('returns the correct data', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts index 9f983fbb8877b..0259c70b36448 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/generate_data.ts @@ -8,12 +8,12 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; export const config = { - firstTransaction: { + appleTransaction: { name: 'GET /apple 🍎', successRate: 75, failureRate: 25, }, - secondTransaction: { + bananaTransaction: { name: 'GET /banana 🍌', successRate: 50, failureRate: 50, @@ -35,14 +35,12 @@ export async function generateData({ .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); - const interval = '1m'; + const { appleTransaction, bananaTransaction } = config; + const interval = timerange(start, end).interval('1m'); - const { firstTransaction, secondTransaction } = config; - - const documents = [firstTransaction, secondTransaction].flatMap((transaction, index) => { + const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { return [ - timerange(start, end) - .interval(interval) + interval .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance @@ -51,24 +49,21 @@ export async function generateData({ .duration(1000) .success() ), - timerange(start, end) - .interval(interval) - .rate(transaction.failureRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: transaction.name }) - .errors( - serviceGoProdInstance - .error({ message: `Error 1 transaction ${transaction.name}` }) - .timestamp(timestamp), - serviceGoProdInstance - .error({ message: `Error 2 transaction ${transaction.name}` }) - .timestamp(timestamp) - ) - .duration(1000) - .timestamp(timestamp) - .failure() - ), + interval.rate(transaction.failureRate).generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: transaction.name }) + .errors( + serviceGoProdInstance + .error({ message: `Error 1 transaction ${transaction.name}` }) + .timestamp(timestamp), + serviceGoProdInstance + .error({ message: `Error 2 transaction ${transaction.name}` }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), ]; }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts index d5e744a06c296..05d565aabf046 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -43,7 +43,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon kuery: '', maxNumberOfErrorGroups: 5, transactionType: 'request', - transactionName: '', + transactionName: overrides?.query?.transactionName ?? '', ...overrides?.query, }, }, @@ -51,62 +51,52 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon } describe('Top Errors main stats', () => { - describe('when data is not loaded', () => { - it('handles empty state', async () => { - const response = await callApi(); - expect(response.status).to.be(200); - expect(response.body.errorGroups).to.empty(); - }); + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + 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/177638 describe('when data is loaded', () => { + let errorGroups: ErrorGroups; + const { + appleTransaction: { name: appleTransactionName, failureRate: appleTransactionFailureRate }, + } = config; describe('top errors for transaction', () => { - const { - firstTransaction: { - name: firstTransactionName, - failureRate: firstTransactionFailureRate, - }, - } = config; - - let apmSynthtraceEsClient: ApmSynthtraceEsClient; - before(async () => { - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await generateData({ serviceName, start, end, apmSynthtraceEsClient }); + const response = await callApi({ query: { transactionName: appleTransactionName } }); + errorGroups = response.body.errorGroups; }); after(() => apmSynthtraceEsClient.clean()); describe('returns the correct data', () => { const NUMBER_OF_BUCKETS = 15; - let errorGroups: ErrorGroups; - before(async () => { - const response = await callApi({ query: { transactionName: firstTransactionName } }); - errorGroups = response.body.errorGroups; - }); it('returns correct number of errors', () => { expect(errorGroups.length).to.equal(2); }); it('error 1 is correct', () => { - const firstErrorId = `b6c1d4d41b0b60b841f40232497344ba36856fcbea0692a4695562ca73e790bd`; - const firstError = errorGroups.find((x) => x.groupId === firstErrorId); + const firstError = errorGroups[0]; expect(firstError).to.not.be(undefined); - expect(firstError?.groupId).to.be(firstErrorId); expect(firstError?.name).to.be(`Error 1 transaction GET /apple 🍎`); - expect(firstError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); + expect(firstError?.occurrences).to.be(appleTransactionFailureRate * NUMBER_OF_BUCKETS); expect(firstError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); }); it('error 2 is correct', () => { - const secondErrorId = `c3f388e4f7276d4fab85aa2fad2d2a42e70637f65cd5ec9f085de28b36e69ba5`; - const secondError = errorGroups.find((x) => x.groupId === secondErrorId); + const secondError = errorGroups[1]; expect(secondError).to.not.be(undefined); - expect(secondError?.groupId).to.be(secondErrorId); expect(secondError?.name).to.be(`Error 2 transaction GET /apple 🍎`); - expect(secondError?.occurrences).to.be(firstTransactionFailureRate * NUMBER_OF_BUCKETS); + expect(secondError?.occurrences).to.be(appleTransactionFailureRate * NUMBER_OF_BUCKETS); expect(secondError?.lastSeen).to.be(moment(end).startOf('minute').valueOf()); }); }); From db168ab13c121940ffcb57697815b21201107980 Mon Sep 17 00:00:00 2001 From: Jenny Date: Wed, 13 Nov 2024 17:55:31 +0100 Subject: [PATCH 5/5] Remove flaky mark comments after using flaky test runner --- .../apis/observability/apm/errors/distribution.spec.ts | 1 - .../apis/observability/apm/errors/error_group_list.spec.ts | 1 - .../apis/observability/apm/errors/group_id_samples.spec.ts | 2 -- .../top_erroneous_transactions.spec.ts | 1 - .../top_errors_for_transaction/top_errors_main_stats.spec.ts | 1 - 5 files changed, 6 deletions(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts index 3c5f057040b49..3c80c8df83018 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/distribution.spec.ts @@ -61,7 +61,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - // FLAKY: https://github.com/elastic/kibana/issues/177336 describe('when data is loaded', () => { describe('errors distribution', () => { const { appleTransaction, bananaTransaction } = config; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts index 82b208e9b914d..c99cbfd3df280 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/error_group_list.spec.ts @@ -53,7 +53,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - // FLAKY: https://github.com/elastic/kibana/issues/177382 describe('when data is loaded', () => { describe('errors group', () => { let apmSynthtraceEsClient: ApmSynthtraceEsClient; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts index 818900bd7ca51..9c20c97fde868 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/group_id_samples.spec.ts @@ -77,7 +77,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon expect(response.body.occurrencesCount).to.be(0); }); - // FLAKY: https://github.com/elastic/kibana/issues/177397 describe('when samples data is loaded', () => { let errorsSamplesResponse: ErrorGroupSamples; const { bananaTransaction } = config; @@ -102,7 +101,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - // FLAKY: https://github.com/elastic/kibana/issues/177383 describe('when error sample data is loaded', () => { describe('error sample id', () => { before(async () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts index 0bde11c19a9a1..1fb3cdadd8f67 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -68,7 +68,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon expect(response.body.topErroneousTransactions).to.be.empty(); }); - // FLAKY: https://github.com/elastic/kibana/issues/177637 describe('when data is loaded', () => { const { appleTransaction: { name: appleTransactionName, failureRate: appleTransactionFailureRate }, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts index 05d565aabf046..10088af2061d5 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -62,7 +62,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon expect(response.body.errorGroups).to.empty(); }); - // FLAKY: https://github.com/elastic/kibana/issues/177638 describe('when data is loaded', () => { let errorGroups: ErrorGroups; const {