mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[APM] Migrate `/mobile` API tests to deployment agnostic folder (#199021)](https://github.com/elastic/kibana/pull/199021) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Katerina","email":"aikaterini.patticha@elastic.co"},"sourceCommit":{"committedDate":"2024-11-11T13:49:54Z","message":"[APM] Migrate `/mobile` API tests to deployment agnostic folder (#199021)\n\ncloses https://github.com/elastic/kibana/issues/198980\r\n\r\nIn addition to migrating the mobile api tests, the PR includes\r\n\r\n- Fixing mapping issue with `error.grouping_name` which causing to drop\r\ndocuments\r\n- Fix and unskip mobile tests\r\n\r\n### How to test\r\n\r\n- Serverless\r\n\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n- Stateful\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\r\n\r\n```\r\n\r\nTODO \r\n- [x] flaky runner \r\n- [x] locally pass\r\n- [x] mki run\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo <carloshenrique.leonelcrespo@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"f77a8052f7a3e39cb3b61d1b61dbef8b6c254525","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services"],"number":199021,"url":"https://github.com/elastic/kibana/pull/199021","mergeCommit":{"message":"[APM] Migrate `/mobile` API tests to deployment agnostic folder (#199021)\n\ncloses https://github.com/elastic/kibana/issues/198980\r\n\r\nIn addition to migrating the mobile api tests, the PR includes\r\n\r\n- Fixing mapping issue with `error.grouping_name` which causing to drop\r\ndocuments\r\n- Fix and unskip mobile tests\r\n\r\n### How to test\r\n\r\n- Serverless\r\n\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n- Stateful\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\r\n\r\n```\r\n\r\nTODO \r\n- [x] flaky runner \r\n- [x] locally pass\r\n- [x] mki run\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo <carloshenrique.leonelcrespo@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"f77a8052f7a3e39cb3b61d1b61dbef8b6c254525"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199021","number":199021,"mergeCommit":{"message":"[APM] Migrate `/mobile` API tests to deployment agnostic folder (#199021)\n\ncloses https://github.com/elastic/kibana/issues/198980\r\n\r\nIn addition to migrating the mobile api tests, the PR includes\r\n\r\n- Fixing mapping issue with `error.grouping_name` which causing to drop\r\ndocuments\r\n- Fix and unskip mobile tests\r\n\r\n### How to test\r\n\r\n- Serverless\r\n\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n- Stateful\r\n```\r\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\r\n\r\n```\r\n\r\nTODO \r\n- [x] flaky runner \r\n- [x] locally pass\r\n- [x] mki run\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo <carloshenrique.leonelcrespo@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"f77a8052f7a3e39cb3b61d1b61dbef8b6c254525"}}]}] BACKPORT-->
This commit is contained in:
parent
db82fe50c8
commit
900a2def42
26 changed files with 1238 additions and 1213 deletions
|
@ -70,7 +70,6 @@ export class Instance extends Entity<ApmFields> {
|
|||
...this.fields,
|
||||
'error.type': 'crash',
|
||||
'error.exception': [{ message, ...(type ? { type } : {}) }],
|
||||
'error.grouping_name': getErrorGroupingKey(message),
|
||||
});
|
||||
}
|
||||
error({
|
||||
|
|
|
@ -262,7 +262,6 @@ export class MobileDevice extends Entity<ApmFields> {
|
|||
'error.type': 'crash',
|
||||
'error.id': generateLongIdWithSeed(message),
|
||||
'error.exception': [{ message, ...{ type: 'crash' } }],
|
||||
'error.grouping_name': groupingName || message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
it('returns a version when agent is listed in the file', async () => {
|
||||
const { status, body } = await callApi();
|
||||
expect(status).to.be(200);
|
||||
|
||||
const agents = body.data;
|
||||
|
||||
const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion;
|
||||
expect(nodeAgent?.latest_version).not.to.be(undefined);
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ export default function apmApiIntegrationTests({
|
|||
}: DeploymentAgnosticFtrProviderContext) {
|
||||
describe('APM', function () {
|
||||
loadTestFile(require.resolve('./agent_explorer'));
|
||||
loadTestFile(require.resolve('./mobile'));
|
||||
loadTestFile(require.resolve('./errors'));
|
||||
loadTestFile(require.resolve('./alerts'));
|
||||
loadTestFile(require.resolve('./custom_dashboards'));
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import {
|
||||
APIClientRequestParamsOf,
|
||||
APIReturnType,
|
||||
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
|
||||
type ErrorGroups =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['errorGroups'];
|
||||
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
const synthtrace = getService('synthtrace');
|
||||
|
||||
const serviceName = 'synth-swift';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['params']
|
||||
>
|
||||
) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics',
|
||||
params: {
|
||||
path: { serviceName, ...overrides?.path },
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Crash group list', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.errorGroups).to.empty();
|
||||
});
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
describe('errors group', () => {
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const appleTransaction = {
|
||||
name: 'GET /apple 🍎 ',
|
||||
successRate: 75,
|
||||
failureRate: 25,
|
||||
};
|
||||
|
||||
const bananaTransaction = {
|
||||
name: 'GET /banana 🍌',
|
||||
successRate: 50,
|
||||
failureRate: 50,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
const serviceInstance = apm
|
||||
.service({ name: serviceName, environment: 'production', agentName: 'swift' })
|
||||
.instance('instance-a');
|
||||
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await apmSynthtraceEsClient.index([
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(appleTransaction.successRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: appleTransaction.name })
|
||||
.timestamp(timestamp)
|
||||
.duration(1000)
|
||||
.success()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(appleTransaction.failureRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: appleTransaction.name })
|
||||
.errors(
|
||||
serviceInstance
|
||||
.crash({
|
||||
message: 'crash 1',
|
||||
})
|
||||
.timestamp(timestamp)
|
||||
)
|
||||
.duration(1000)
|
||||
.timestamp(timestamp)
|
||||
.failure()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(bananaTransaction.successRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: bananaTransaction.name })
|
||||
.timestamp(timestamp)
|
||||
.duration(1000)
|
||||
.success()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(bananaTransaction.failureRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: bananaTransaction.name })
|
||||
.errors(
|
||||
serviceInstance
|
||||
.crash({
|
||||
message: 'crash 2',
|
||||
})
|
||||
.timestamp(timestamp)
|
||||
)
|
||||
.duration(1000)
|
||||
.timestamp(timestamp)
|
||||
.failure()
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('returns the correct data', () => {
|
||||
let errorGroups: ErrorGroups;
|
||||
before(async () => {
|
||||
const response = await callApi();
|
||||
errorGroups = response.body.errorGroups;
|
||||
});
|
||||
it('returns correct number of crashes', () => {
|
||||
expect(errorGroups.length).to.equal(2);
|
||||
expect(errorGroups.map((error) => error.name).sort()).to.eql(['crash 1', 'crash 2']);
|
||||
});
|
||||
|
||||
it('returns correct occurrences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([
|
||||
appleTransaction.failureRate * numberOfBuckets,
|
||||
bananaTransaction.failureRate * numberOfBuckets,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { first, last, sumBy } from 'lodash';
|
||||
import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number';
|
||||
import {
|
||||
APIClientRequestParamsOf,
|
||||
APIReturnType,
|
||||
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
|
||||
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
import { config, generateData } from './generate_data';
|
||||
|
||||
type ErrorsDistribution =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>;
|
||||
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
const synthtrace = getService('synthtrace');
|
||||
|
||||
const serviceName = 'synth-swift';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>['params']
|
||||
>
|
||||
) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
...overrides?.path,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
describe('Distribution', () => {
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
});
|
||||
|
||||
it('handles the empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.length).to.be(0);
|
||||
expect(response.body.previousPeriod.length).to.be(0);
|
||||
});
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
describe('errors distribution', () => {
|
||||
const { appleTransaction, bananaTransaction } = config;
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('without comparison', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi();
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
|
||||
it('displays combined number of occurrences', () => {
|
||||
const countSum = sumBy(errorsDistribution.currentPeriod, 'y');
|
||||
const numberOfBuckets = 15;
|
||||
expect(countSum).to.equal(
|
||||
(appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets
|
||||
);
|
||||
});
|
||||
|
||||
describe('displays correct start in errors distribution chart', () => {
|
||||
let errorsDistributionWithComparison: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const responseWithComparison = await callApi({
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset: '15m',
|
||||
},
|
||||
});
|
||||
errorsDistributionWithComparison = responseWithComparison.body;
|
||||
});
|
||||
it('has same start time when comparison is enabled', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistributionWithComparison.currentPeriod)?.x
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('displays occurrences for type "apple transaction" only', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi({
|
||||
query: { kuery: `error.exception.type:"${appleTransaction.name}"` },
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
it('displays combined number of occurrences', () => {
|
||||
const countSum = sumBy(errorsDistribution.currentPeriod, 'y');
|
||||
const numberOfBuckets = 15;
|
||||
expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with comparison', () => {
|
||||
describe('when data is returned', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
const response = await callApi({
|
||||
query: {
|
||||
start: new Date(end - fiveMinutes).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset: '5m',
|
||||
},
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
it('returns some data', () => {
|
||||
const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) =>
|
||||
isFiniteNumber(y)
|
||||
);
|
||||
|
||||
const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) =>
|
||||
isFiniteNumber(y)
|
||||
);
|
||||
|
||||
expect(hasCurrentPeriodData).to.equal(true);
|
||||
expect(hasPreviousPeriodData).to.equal(true);
|
||||
});
|
||||
|
||||
it('has same start time for both periods', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('has same end time for both periods', () => {
|
||||
expect(last(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
last(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('returns same number of buckets for both periods', () => {
|
||||
expect(errorsDistribution.currentPeriod.length).to.equal(
|
||||
errorsDistribution.previousPeriod.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no data is returned', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi({
|
||||
query: {
|
||||
start: '2021-01-03T00:00:00.000Z',
|
||||
end: '2021-01-03T00:15:00.000Z',
|
||||
offset: '1d',
|
||||
},
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
|
||||
it('has same start time for both periods', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('has same end time for both periods', () => {
|
||||
expect(last(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
last(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('returns same number of buckets for both periods', () => {
|
||||
expect(errorsDistribution.currentPeriod.length).to.equal(
|
||||
errorsDistribution.previousPeriod.length
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service';
|
||||
import { orderBy } from 'lodash';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
import { config, generateData } from './generate_data';
|
||||
|
||||
type ErrorGroupSamples =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>;
|
||||
|
||||
type ErrorSampleDetails =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}'>;
|
||||
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
const synthtrace = getService('synthtrace');
|
||||
|
||||
const serviceName = 'synth-go';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callErrorGroupSamplesApi({ groupId }: { groupId: string }) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
groupId,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
async function callErrorSampleDetailsApi(errorId: string) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
groupId: 'foo',
|
||||
errorId,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
describe('Group id samples', () => {
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
});
|
||||
|
||||
it('handles the empty state', async () => {
|
||||
const response = await callErrorGroupSamplesApi({ groupId: 'foo' });
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.occurrencesCount).to.be(0);
|
||||
});
|
||||
|
||||
describe('when samples data is loaded', () => {
|
||||
let errorsSamplesResponse: ErrorGroupSamples;
|
||||
const { bananaTransaction } = config;
|
||||
describe('error group id', () => {
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
const response = await callErrorGroupSamplesApi({
|
||||
groupId: '0000000000000000000000000Error 1',
|
||||
});
|
||||
errorsSamplesResponse = response.body;
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('displays correct number of occurrences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(errorsSamplesResponse.occurrencesCount).to.equal(
|
||||
bananaTransaction.failureRate * numberOfBuckets
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// github.com/elastic/kibana/issues/177665
|
||||
describe('when error sample data is loaded', () => {
|
||||
describe('error sample id', () => {
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('return correct data', () => {
|
||||
let errorSampleDetailsResponse: ErrorSampleDetails;
|
||||
before(async () => {
|
||||
const errorsSamplesResponse = await callErrorGroupSamplesApi({
|
||||
groupId: '0000000000000000000000000Error 1',
|
||||
});
|
||||
|
||||
const errorId = errorsSamplesResponse.body.errorSampleIds[0];
|
||||
|
||||
const response = await callErrorSampleDetailsApi(errorId);
|
||||
errorSampleDetailsResponse = response.body;
|
||||
});
|
||||
|
||||
it('displays correct error grouping_key', () => {
|
||||
expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal(
|
||||
'0000000000000000000000000Error 1'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays correct error message', () => {
|
||||
expect(errorSampleDetailsResponse.error.error.exception?.[0].message).to.equal(
|
||||
'Error 1'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sampled and unsampled transactions', () => {
|
||||
let errorGroupSamplesResponse: ErrorGroupSamples;
|
||||
|
||||
before(async () => {
|
||||
const instance = service(serviceName, 'production', 'go').instance('a');
|
||||
const errorMessage = 'Error 1';
|
||||
const groupId = '0000000000000000000000000Error 1';
|
||||
|
||||
await apmSynthtraceEsClient.index([
|
||||
timerange(start, end)
|
||||
.interval('15m')
|
||||
.rate(1)
|
||||
.generator((timestamp) => {
|
||||
return [
|
||||
instance
|
||||
.transaction('GET /api/foo')
|
||||
.duration(100)
|
||||
.timestamp(timestamp)
|
||||
.sample(false)
|
||||
.errors(
|
||||
instance.error({ message: errorMessage }).timestamp(timestamp),
|
||||
instance.error({ message: errorMessage }).timestamp(timestamp + 1)
|
||||
),
|
||||
instance
|
||||
.transaction('GET /api/foo')
|
||||
.duration(100)
|
||||
.timestamp(timestamp)
|
||||
.sample(true)
|
||||
.errors(instance.error({ message: errorMessage }).timestamp(timestamp)),
|
||||
];
|
||||
}),
|
||||
]);
|
||||
|
||||
errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body;
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('returns the errors in the correct order (sampled first, then unsampled)', () => {
|
||||
const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) =>
|
||||
parseInt(id, 10)
|
||||
);
|
||||
|
||||
// this checks whether the order of indexing is different from the order that is returned
|
||||
// if it is not, scoring/sorting is broken
|
||||
expect(errorGroupSamplesResponse.errorSampleIds.length).to.be(3);
|
||||
expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||
describe('Mobile', () => {
|
||||
loadTestFile(require.resolve('./crashes/crash_group_list.spec.ts'));
|
||||
loadTestFile(require.resolve('./crashes/distribution.spec.ts'));
|
||||
loadTestFile(require.resolve('./errors/group_id_samples.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_detailed_statistics_by_field.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_filters.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_http_requests_timeseries.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_location_stats.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_main_statistics_by_field.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_most_used_chart.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_sessions_timeseries.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_stats.spec.ts'));
|
||||
loadTestFile(require.resolve('./mobile_terms_by_field.spec.ts'));
|
||||
});
|
||||
}
|
|
@ -10,16 +10,18 @@ import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_value
|
|||
import { isEmpty } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { generateMobileData, SERVICE_VERSIONS } from './generate_mobile_data';
|
||||
|
||||
type MobileDetailedStatisticsResponse =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/detailed_statistics'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -56,27 +58,24 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when(
|
||||
'Mobile detailed statistics when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileDetailedStatisticsByField({
|
||||
serviceName: 'foo',
|
||||
field: 'service.version',
|
||||
});
|
||||
expect(response).to.be.eql({ currentPeriod: {}, previousPeriod: {} });
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
describe('Mobile detailed statistics ', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177388
|
||||
registry.when.skip(
|
||||
'Mobile detailed statistics when data is loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is not loaded', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileDetailedStatisticsByField({
|
||||
serviceName: 'foo',
|
||||
field: 'service.version',
|
||||
});
|
||||
expect(response).to.be.eql({ currentPeriod: {}, previousPeriod: {} });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
before(async () => {
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
|
@ -85,8 +84,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when comparison is disable', () => {
|
||||
it('returns current period data only', async () => {
|
||||
const response = await getMobileDetailedStatisticsByField({
|
||||
|
@ -133,6 +130,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
type MobileFilters = APIReturnType<'GET /internal/apm/services/{serviceName}/mobile/filters'>;
|
||||
|
||||
|
@ -133,10 +133,11 @@ async function generateData({
|
|||
]);
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -166,7 +167,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when('Mobile filters when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('Mobile filters', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileFilters({ serviceName: 'foo' });
|
||||
|
@ -175,30 +176,25 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177389
|
||||
registry.when.skip('Mobile filters', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
let response: MobileFilters;
|
||||
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
response = await getMobileFilters({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
let response: MobileFilters;
|
||||
|
||||
it('returns correct filters for device', () => {
|
||||
response.mobileFilters.map(({ key, options }) => {
|
||||
if (key === 'device') {
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { generateMobileData } from './generate_mobile_data';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T02:00:00.000Z').getTime();
|
||||
|
@ -47,27 +49,20 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
}
|
||||
|
||||
registry.when.skip(
|
||||
'Mobile HTTP requests without data loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getHttpRequestsChart({ serviceName: 'foo' });
|
||||
expect(response.body.currentPeriod.timeseries).to.eql([]);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
expect(response.status).to.be(200);
|
||||
});
|
||||
describe('Mobile HTTP requests ', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getHttpRequestsChart({ serviceName: 'foo' });
|
||||
expect(response.body.currentPeriod.timeseries).to.eql([]);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
expect(response.status).to.be(200);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177390
|
||||
registry.when.skip(
|
||||
'Mobile HTTP requests with data loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
describe('when data is loaded', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
|
@ -76,32 +71,29 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
it('returns timeseries for http requests chart', async () => {
|
||||
const response = await getHttpRequestsChart({
|
||||
serviceName: 'synth-android',
|
||||
offset: '1d',
|
||||
});
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.body.previousPeriod.timeseries[0].y).to.eql(0);
|
||||
it('returns timeseries for http requests chart', async () => {
|
||||
const response = await getHttpRequestsChart({
|
||||
serviceName: 'synth-android',
|
||||
offset: '1d',
|
||||
});
|
||||
|
||||
it('returns only current period timeseries when offset is not available', async () => {
|
||||
const response = await getHttpRequestsChart({ serviceName: 'synth-android' });
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.body.previousPeriod.timeseries[0].y).to.eql(0);
|
||||
});
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(
|
||||
response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x)
|
||||
).to.eql(true);
|
||||
it('returns only current period timeseries when offset is not available', async () => {
|
||||
const response = await getHttpRequestsChart({ serviceName: 'synth-android' });
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(7);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
expect(response.status).to.be(200);
|
||||
expect(
|
||||
response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x)
|
||||
).to.eql(true);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(7);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
|
@ -138,6 +130,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(ntcCell.body.currentPeriod.timeseries[0].y).to.eql(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
type MobileLocationStats =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/location/stats'>;
|
||||
|
@ -176,10 +176,11 @@ async function generateData({
|
|||
]);
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -212,7 +213,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when('Location stats when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('Location stats', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileLocationStats({ serviceName: 'foo' });
|
||||
|
@ -230,111 +231,112 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177396
|
||||
registry.when.skip('Location stats', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
let response: MobileLocationStats;
|
||||
|
||||
describe('Location stats with data', () => {
|
||||
before(async () => {
|
||||
response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns location for most sessions', () => {
|
||||
const { location } = response.currentPeriod.mostSessions;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('returns location for most requests', () => {
|
||||
const { location } = response.currentPeriod.mostRequests;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
describe('when data is loaded', () => {
|
||||
let response: MobileLocationStats;
|
||||
|
||||
it('returns location for most crashes', () => {
|
||||
const { location } = response.currentPeriod.mostCrashes;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
|
||||
it('returns location for most launches', () => {
|
||||
const { location } = response.currentPeriod.mostLaunches;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters with no results', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
before(async () => {
|
||||
response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
});
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(0);
|
||||
|
||||
expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `service.version:"1.1"`,
|
||||
it('returns location for most sessions', () => {
|
||||
const { location } = response.currentPeriod.mostSessions;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1);
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
kuery: `service.version:"1.1" and service.environment: "production"`,
|
||||
it('returns location for most requests', () => {
|
||||
const { location } = response.currentPeriod.mostRequests;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1);
|
||||
it('returns location for most crashes', () => {
|
||||
const { location } = response.currentPeriod.mostCrashes;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(3);
|
||||
it('returns location for most launches', () => {
|
||||
const { location } = response.currentPeriod.mostLaunches;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters with no results', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(0);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(0);
|
||||
|
||||
expect(
|
||||
response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)
|
||||
).to.eql(true);
|
||||
expect(
|
||||
response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)
|
||||
).to.eql(true);
|
||||
expect(
|
||||
response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)
|
||||
).to.eql(true);
|
||||
expect(
|
||||
response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0)
|
||||
).to.eql(true);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `service.version:"1.1"`,
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1);
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
const response = await getMobileLocationStats({
|
||||
serviceName: 'synth-android',
|
||||
kuery: `service.version:"1.1" and service.environment: "production"`,
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1);
|
||||
expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1);
|
||||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostLaunches.value).to.eql(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,12 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
const GALAXY_DURATION = 500;
|
||||
const HUAWEI_DURATION = 20;
|
||||
|
@ -126,10 +125,11 @@ async function generateData({
|
|||
]);
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -162,91 +162,88 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when(
|
||||
'Mobile main statistics when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'foo',
|
||||
field: 'service.version',
|
||||
});
|
||||
expect(response.mainStatistics.length).to.be(0);
|
||||
describe('Mobile main statistics', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'foo',
|
||||
field: 'service.version',
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177395
|
||||
registry.when.skip('Mobile main statistics', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
expect(response.mainStatistics.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
describe('Mobile main statistics', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
const huaweiLatency = calculateLatency(HUAWEI_DURATION);
|
||||
const galaxyLatency = calculateLatency(GALAXY_DURATION);
|
||||
const huaweiThroughput = calculateThroughput({ start, end });
|
||||
const galaxyThroughput = calculateThroughput({ start, end });
|
||||
|
||||
it('returns the correct data for App version', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'service.version',
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
|
||||
expect(fieldValues).to.be.eql(SERVICE_VERSIONS);
|
||||
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
|
||||
expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]);
|
||||
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]);
|
||||
});
|
||||
it('returns the correct data for Os version', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'host.os.version',
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
const huaweiLatency = calculateLatency(HUAWEI_DURATION);
|
||||
const galaxyLatency = calculateLatency(GALAXY_DURATION);
|
||||
const huaweiThroughput = calculateThroughput({ start, end });
|
||||
const galaxyThroughput = calculateThroughput({ start, end });
|
||||
|
||||
it('returns the correct data for App version', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'service.version',
|
||||
});
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
|
||||
expect(fieldValues).to.be.eql(SERVICE_VERSIONS);
|
||||
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
|
||||
expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]);
|
||||
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]);
|
||||
});
|
||||
it('returns the correct data for Os version', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'host.os.version',
|
||||
});
|
||||
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
|
||||
expect(fieldValues).to.be.eql(OS_VERSIONS);
|
||||
expect(fieldValues).to.be.eql(OS_VERSIONS);
|
||||
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
|
||||
expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]);
|
||||
expect(latencyValues).to.be.eql([galaxyLatency, huaweiLatency]);
|
||||
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]);
|
||||
});
|
||||
it('returns the correct data for Devices', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'device.model.identifier',
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([galaxyThroughput, huaweiThroughput]);
|
||||
});
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
it('returns the correct data for Devices', async () => {
|
||||
const response = await getMobileMainStatisticsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
field: 'device.model.identifier',
|
||||
});
|
||||
const fieldValues = response.mainStatistics.map((item) => item.name);
|
||||
|
||||
expect(fieldValues).to.be.eql(['HUAWEI P2-0000', 'SM-G973F']);
|
||||
expect(fieldValues).to.be.eql(['HUAWEI P2-0000', 'SM-G973F']);
|
||||
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
const latencyValues = response.mainStatistics.map((item) => item.latency);
|
||||
|
||||
expect(latencyValues).to.be.eql([huaweiLatency, galaxyLatency]);
|
||||
expect(latencyValues).to.be.eql([huaweiLatency, galaxyLatency]);
|
||||
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([huaweiThroughput, galaxyThroughput]);
|
||||
const throughputValues = response.mainStatistics.map((item) => item.throughput);
|
||||
expect(throughputValues).to.be.eql([huaweiThroughput, galaxyThroughput]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import expect from '@kbn/expect';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { generateMobileData } from './generate_mobile_data';
|
||||
|
||||
type MostUsedCharts =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/most_used_charts'>;
|
||||
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function getMobileMostUsedCharts({
|
||||
environment = ENVIRONMENT_ALL.value,
|
||||
kuery = '',
|
||||
serviceName,
|
||||
transactionType = 'mobile',
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
}) {
|
||||
return await apmApiClient
|
||||
.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/most_used_charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
kuery,
|
||||
transactionType,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
describe('Most used charts', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response: MostUsedCharts = await getMobileMostUsedCharts({ serviceName: 'foo' });
|
||||
expect(response.mostUsedCharts.length).to.eql(4);
|
||||
expect(response.mostUsedCharts.every((chart) => chart.options.length === 0)).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile stats', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
let response: MostUsedCharts;
|
||||
|
||||
before(async () => {
|
||||
response = await getMobileMostUsedCharts({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the top 5 and the other option only', () => {
|
||||
const deviceOptions = response.mostUsedCharts.find(
|
||||
(chart) => chart.key === 'device'
|
||||
)?.options;
|
||||
expect(deviceOptions?.length).to.eql(6);
|
||||
expect(deviceOptions?.find((option) => option.key === 'other')).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('should get network connection type object from span events', () => {
|
||||
const nctOptions = response.mostUsedCharts.find(
|
||||
(chart) => chart.key === 'netConnectionType'
|
||||
)?.options;
|
||||
expect(nctOptions?.length).to.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import expect from '@kbn/expect';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { generateMobileData } from './generate_mobile_data';
|
||||
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T02:00:00.000Z').getTime();
|
||||
|
||||
async function getSessionsChart({
|
||||
environment = ENVIRONMENT_ALL.value,
|
||||
kuery = '',
|
||||
serviceName,
|
||||
transactionType = 'mobile',
|
||||
offset,
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
offset?: string;
|
||||
}) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset,
|
||||
kuery,
|
||||
transactionType,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Sessions charts', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'foo' });
|
||||
expect(response.body.currentPeriod.timeseries).to.eql([]);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
expect(response.status).to.be(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with data loaded', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
it('returns timeseries for sessions chart', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'synth-android', offset: '1d' });
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries[0].y).to.eql(0);
|
||||
});
|
||||
|
||||
it('returns only current period timeseries when offset is not available', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'synth-android' });
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters', async () => {
|
||||
const response = await getSessionsChart({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
});
|
||||
|
||||
expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true);
|
||||
expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the correct values filter is applied', async () => {
|
||||
const response = await getSessionsChart({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `transaction.name : "Start View - View Appearing"`,
|
||||
});
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { meanBy, sumBy } from 'lodash';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>;
|
||||
|
||||
|
@ -134,10 +134,11 @@ async function generateData({
|
|||
]);
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -170,7 +171,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when('Mobile stats when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('Mobile stats', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileStats({ serviceName: 'foo' });
|
||||
|
@ -182,109 +183,110 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177392
|
||||
registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
let response: MobileStats;
|
||||
|
||||
describe('Mobile stats', () => {
|
||||
before(async () => {
|
||||
response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns same sessions', () => {
|
||||
const { value, timeseries } = response.currentPeriod.sessions;
|
||||
const timeseriesTotal = sumBy(timeseries, 'y');
|
||||
expect(value).to.be(timeseriesTotal);
|
||||
});
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('returns same requests', () => {
|
||||
const { value, timeseries } = response.currentPeriod.requests;
|
||||
const timeseriesTotal = sumBy(timeseries, 'y');
|
||||
expect(value).to.be(timeseriesTotal);
|
||||
});
|
||||
describe('when data is loaded', () => {
|
||||
let response: MobileStats;
|
||||
|
||||
it('returns same crashes', () => {
|
||||
const { value, timeseries } = response.currentPeriod.crashRate;
|
||||
const timeseriesMean = meanBy(
|
||||
timeseries.filter((bucket) => bucket.y !== 0),
|
||||
'y'
|
||||
);
|
||||
expect(value).to.be(timeseriesMean);
|
||||
});
|
||||
it('returns same launch times', () => {
|
||||
const { value, timeseries } = response.currentPeriod.launchTimes;
|
||||
const timeseriesMean = meanBy(
|
||||
timeseries.filter((bucket) => bucket.y !== null),
|
||||
'y'
|
||||
);
|
||||
expect(value).to.be(timeseriesMean);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
before(async () => {
|
||||
response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
});
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.sessions.value).to.eql(0);
|
||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(0);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(null);
|
||||
|
||||
expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
response.currentPeriod.launchTimes.timeseries.every((item) => item.y === null)
|
||||
).to.eql(true);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `service.version:"2.3"`,
|
||||
it('returns same sessions', () => {
|
||||
const { value, timeseries } = response.currentPeriod.sessions;
|
||||
const timeseriesTotal = sumBy(timeseries, 'y');
|
||||
expect(value).to.be(timeseriesTotal);
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(3);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(null);
|
||||
it('returns same requests', () => {
|
||||
const { value, timeseries } = response.currentPeriod.requests;
|
||||
const timeseriesTotal = sumBy(timeseries, 'y');
|
||||
expect(value).to.be(timeseriesTotal);
|
||||
});
|
||||
|
||||
it('returns same crashes', () => {
|
||||
const { value, timeseries } = response.currentPeriod.crashRate;
|
||||
const timeseriesMean = meanBy(
|
||||
timeseries.filter((bucket) => bucket.y !== 0),
|
||||
'y'
|
||||
);
|
||||
expect(value).to.be(timeseriesMean);
|
||||
});
|
||||
it('returns same launch times', () => {
|
||||
const { value, timeseries } = response.currentPeriod.launchTimes;
|
||||
const timeseriesMean = meanBy(
|
||||
timeseries.filter((bucket) => bucket.y !== null),
|
||||
'y'
|
||||
);
|
||||
expect(value).to.be(timeseriesMean);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
kuery: `service.version:"1.2" and service.environment: "production"`,
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.sessions.value).to.eql(0);
|
||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(0);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(null);
|
||||
|
||||
expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
response.currentPeriod.launchTimes.timeseries.every((item) => item.y === null)
|
||||
).to.eql(true);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `service.version:"2.3"`,
|
||||
});
|
||||
|
||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(3);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(null);
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
const response = await getMobileStats({
|
||||
serviceName: 'synth-android',
|
||||
kuery: `service.version:"1.2" and service.environment: "production"`,
|
||||
});
|
||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.requests.value).to.eql(3);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(1);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(100);
|
||||
});
|
||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.requests.value).to.eql(3);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(1);
|
||||
expect(response.currentPeriod.launchTimes.value).to.eql(100);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
// we generate 3 transactions per each mobile device
|
||||
// timerange 15min, interval 5m, rate 1
|
||||
|
@ -124,10 +124,11 @@ async function generateData({
|
|||
]);
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||
const apmApiClient = getService('apmApi');
|
||||
|
||||
const synthtrace = getService('synthtrace');
|
||||
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
@ -163,7 +164,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when('Mobile terms when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('Mobile terms', () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
|
@ -183,66 +184,67 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(response.terms).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177498
|
||||
registry.when.skip('Mobile terms', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
describe('Mobile terms', () => {
|
||||
before(async () => {
|
||||
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
it('returns mobile devices', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'device.model.identifier',
|
||||
size: 10,
|
||||
await generateData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{ label: 'HUAWEI P2-0000', count: 3 },
|
||||
{ label: 'SM-G973F', count: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns mobile versions', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'service.version',
|
||||
size: 10,
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{
|
||||
label: '1.2',
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
label: '2.3',
|
||||
count: 3,
|
||||
},
|
||||
]);
|
||||
});
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('return the most used mobile version', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'service.version',
|
||||
size: 1,
|
||||
describe('when data is loaded', () => {
|
||||
it('returns mobile devices', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'device.model.identifier',
|
||||
size: 10,
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{ label: 'HUAWEI P2-0000', count: 3 },
|
||||
{ label: 'SM-G973F', count: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns mobile versions', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'service.version',
|
||||
size: 10,
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{
|
||||
label: '1.2',
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
label: '2.3',
|
||||
count: 3,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('return the most used mobile version', async () => {
|
||||
const response = await getMobileTermsByField({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
fieldName: 'service.version',
|
||||
size: 1,
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{
|
||||
label: '1.2',
|
||||
count: 3,
|
||||
},
|
||||
]);
|
||||
});
|
||||
expect(response.terms).to.eql([
|
||||
{
|
||||
label: '1.2',
|
||||
count: 3,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import {
|
||||
APIClientRequestParamsOf,
|
||||
APIReturnType,
|
||||
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
|
||||
type ErrorGroups =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['errorGroups'];
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
|
||||
const serviceName = 'synth-swift';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['params']
|
||||
>
|
||||
) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics',
|
||||
params: {
|
||||
path: { serviceName, ...overrides?.path },
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.errorGroups).to.empty();
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177651
|
||||
registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('errors group', () => {
|
||||
const appleTransaction = {
|
||||
name: 'GET /apple 🍎 ',
|
||||
successRate: 75,
|
||||
failureRate: 25,
|
||||
};
|
||||
|
||||
const bananaTransaction = {
|
||||
name: 'GET /banana 🍌',
|
||||
successRate: 50,
|
||||
failureRate: 50,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
const serviceInstance = apm
|
||||
.service({ name: serviceName, environment: 'production', agentName: 'swift' })
|
||||
.instance('instance-a');
|
||||
|
||||
await apmSynthtraceEsClient.index([
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(appleTransaction.successRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: appleTransaction.name })
|
||||
.timestamp(timestamp)
|
||||
.duration(1000)
|
||||
.success()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(appleTransaction.failureRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: appleTransaction.name })
|
||||
.errors(
|
||||
serviceInstance
|
||||
.crash({
|
||||
message: 'crash 1',
|
||||
})
|
||||
.timestamp(timestamp)
|
||||
)
|
||||
.duration(1000)
|
||||
.timestamp(timestamp)
|
||||
.failure()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(bananaTransaction.successRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: bananaTransaction.name })
|
||||
.timestamp(timestamp)
|
||||
.duration(1000)
|
||||
.success()
|
||||
),
|
||||
timerange(start, end)
|
||||
.interval('1m')
|
||||
.rate(bananaTransaction.failureRate)
|
||||
.generator((timestamp) =>
|
||||
serviceInstance
|
||||
.transaction({ transactionName: bananaTransaction.name })
|
||||
.errors(
|
||||
serviceInstance
|
||||
.crash({
|
||||
message: 'crash 2',
|
||||
})
|
||||
.timestamp(timestamp)
|
||||
)
|
||||
.duration(1000)
|
||||
.timestamp(timestamp)
|
||||
.failure()
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('returns the correct data', () => {
|
||||
let errorGroups: ErrorGroups;
|
||||
before(async () => {
|
||||
const response = await callApi();
|
||||
errorGroups = response.body.errorGroups;
|
||||
});
|
||||
it('returns correct number of crashes', () => {
|
||||
expect(errorGroups.length).to.equal(2);
|
||||
expect(errorGroups.map((error) => error.name).sort()).to.eql(['crash 1', 'crash 2']);
|
||||
});
|
||||
|
||||
it('returns correct occurrences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([
|
||||
appleTransaction.failureRate * numberOfBuckets,
|
||||
bananaTransaction.failureRate * numberOfBuckets,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { first, last, sumBy } from 'lodash';
|
||||
import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number';
|
||||
import {
|
||||
APIClientRequestParamsOf,
|
||||
APIReturnType,
|
||||
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { config, generateData } from './generate_data';
|
||||
|
||||
type ErrorsDistribution =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
|
||||
const serviceName = 'synth-swift';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>['params']
|
||||
>
|
||||
) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
...overrides?.path,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.length).to.be(0);
|
||||
expect(response.body.previousPeriod.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177652
|
||||
registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('errors distribution', () => {
|
||||
const { appleTransaction, bananaTransaction } = config;
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('without comparison', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi();
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
|
||||
it('displays combined number of occurrences', () => {
|
||||
const countSum = sumBy(errorsDistribution.currentPeriod, 'y');
|
||||
const numberOfBuckets = 15;
|
||||
expect(countSum).to.equal(
|
||||
(appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets
|
||||
);
|
||||
});
|
||||
|
||||
describe('displays correct start in errors distribution chart', () => {
|
||||
let errorsDistributionWithComparison: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const responseWithComparison = await callApi({
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset: '15m',
|
||||
},
|
||||
});
|
||||
errorsDistributionWithComparison = responseWithComparison.body;
|
||||
});
|
||||
it('has same start time when comparison is enabled', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistributionWithComparison.currentPeriod)?.x
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('displays occurrences for type "apple transaction" only', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi({
|
||||
query: { kuery: `error.exception.type:"${appleTransaction.name}"` },
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
it('displays combined number of occurrences', () => {
|
||||
const countSum = sumBy(errorsDistribution.currentPeriod, 'y');
|
||||
const numberOfBuckets = 15;
|
||||
expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with comparison', () => {
|
||||
describe('when data is returned', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
const response = await callApi({
|
||||
query: {
|
||||
start: new Date(end - fiveMinutes).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset: '5m',
|
||||
},
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
it('returns some data', () => {
|
||||
const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) =>
|
||||
isFiniteNumber(y)
|
||||
);
|
||||
|
||||
const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) =>
|
||||
isFiniteNumber(y)
|
||||
);
|
||||
|
||||
expect(hasCurrentPeriodData).to.equal(true);
|
||||
expect(hasPreviousPeriodData).to.equal(true);
|
||||
});
|
||||
|
||||
it('has same start time for both periods', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('has same end time for both periods', () => {
|
||||
expect(last(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
last(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('returns same number of buckets for both periods', () => {
|
||||
expect(errorsDistribution.currentPeriod.length).to.equal(
|
||||
errorsDistribution.previousPeriod.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no data is returned', () => {
|
||||
let errorsDistribution: ErrorsDistribution;
|
||||
before(async () => {
|
||||
const response = await callApi({
|
||||
query: {
|
||||
start: '2021-01-03T00:00:00.000Z',
|
||||
end: '2021-01-03T00:15:00.000Z',
|
||||
offset: '1d',
|
||||
},
|
||||
});
|
||||
errorsDistribution = response.body;
|
||||
});
|
||||
|
||||
it('has same start time for both periods', () => {
|
||||
expect(first(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
first(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('has same end time for both periods', () => {
|
||||
expect(last(errorsDistribution.currentPeriod)?.x).to.equal(
|
||||
last(errorsDistribution.previousPeriod)?.x
|
||||
);
|
||||
});
|
||||
|
||||
it('returns same number of buckets for both periods', () => {
|
||||
expect(errorsDistribution.currentPeriod.length).to.equal(
|
||||
errorsDistribution.previousPeriod.length
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service';
|
||||
import { orderBy } from 'lodash';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { config, generateData } from './generate_data';
|
||||
|
||||
type ErrorGroupSamples =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>;
|
||||
|
||||
type ErrorSampleDetails =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
|
||||
const serviceName = 'synth-go';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callErrorGroupSamplesApi({ groupId }: { groupId: string }) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
groupId,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
async function callErrorSampleDetailsApi(errorId: string) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
groupId: 'foo',
|
||||
errorId,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await callErrorGroupSamplesApi({ groupId: 'foo' });
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.occurrencesCount).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177654
|
||||
registry.when.skip('when samples data is loaded', { config: 'basic', archives: [] }, () => {
|
||||
const { bananaTransaction } = config;
|
||||
describe('error group id', () => {
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('return correct data', () => {
|
||||
let errorsSamplesResponse: ErrorGroupSamples;
|
||||
before(async () => {
|
||||
const response = await callErrorGroupSamplesApi({
|
||||
groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03',
|
||||
});
|
||||
errorsSamplesResponse = response.body;
|
||||
});
|
||||
|
||||
it('displays correct number of occurrences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(errorsSamplesResponse.occurrencesCount).to.equal(
|
||||
bananaTransaction.failureRate * numberOfBuckets
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177665
|
||||
registry.when.skip('when error sample data is loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('error sample id', () => {
|
||||
before(async () => {
|
||||
await generateData({ serviceName, start, end, apmSynthtraceEsClient });
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('return correct data', () => {
|
||||
let errorSampleDetailsResponse: ErrorSampleDetails;
|
||||
before(async () => {
|
||||
const errorsSamplesResponse = await callErrorGroupSamplesApi({
|
||||
groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03',
|
||||
});
|
||||
|
||||
const errorId = errorsSamplesResponse.body.errorSampleIds[0];
|
||||
|
||||
const response = await callErrorSampleDetailsApi(errorId);
|
||||
errorSampleDetailsResponse = response.body;
|
||||
});
|
||||
|
||||
it('displays correct error grouping_key', () => {
|
||||
expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal(
|
||||
'98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays correct error message', () => {
|
||||
expect(errorSampleDetailsResponse.error.error.exception?.[0].message).to.equal('Error 1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sampled and unsampled transactions', () => {
|
||||
let errorGroupSamplesResponse: ErrorGroupSamples;
|
||||
|
||||
before(async () => {
|
||||
const instance = service(serviceName, 'production', 'go').instance('a');
|
||||
const errorMessage = 'Error 1';
|
||||
const groupId = getErrorGroupingKey(errorMessage);
|
||||
|
||||
await apmSynthtraceEsClient.index([
|
||||
timerange(start, end)
|
||||
.interval('15m')
|
||||
.rate(1)
|
||||
.generator((timestamp) => {
|
||||
return [
|
||||
instance
|
||||
.transaction('GET /api/foo')
|
||||
.duration(100)
|
||||
.timestamp(timestamp)
|
||||
.sample(false)
|
||||
.errors(
|
||||
instance.error({ message: errorMessage }).timestamp(timestamp),
|
||||
instance.error({ message: errorMessage }).timestamp(timestamp + 1)
|
||||
),
|
||||
instance
|
||||
.transaction('GET /api/foo')
|
||||
.duration(100)
|
||||
.timestamp(timestamp)
|
||||
.sample(true)
|
||||
.errors(instance.error({ message: errorMessage }).timestamp(timestamp)),
|
||||
];
|
||||
}),
|
||||
]);
|
||||
|
||||
errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body;
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
it('returns the errors in the correct order (sampled first, then unsampled)', () => {
|
||||
const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10));
|
||||
|
||||
// this checks whether the order of indexing is different from the order that is returned
|
||||
// if it is not, scoring/sorting is broken
|
||||
expect(errorGroupSamplesResponse.errorSampleIds.length).to.be(3);
|
||||
expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { generateMobileData } from './generate_mobile_data';
|
||||
|
||||
type MostUsedCharts =
|
||||
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/most_used_charts'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function getMobileMostUsedCharts({
|
||||
environment = ENVIRONMENT_ALL.value,
|
||||
kuery = '',
|
||||
serviceName,
|
||||
transactionType = 'mobile',
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
}) {
|
||||
return await apmApiClient
|
||||
.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/most_used_charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
kuery,
|
||||
transactionType,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ body }) => body);
|
||||
}
|
||||
|
||||
registry.when(
|
||||
'Most used charts when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response: MostUsedCharts = await getMobileMostUsedCharts({ serviceName: 'foo' });
|
||||
expect(response.mostUsedCharts.length).to.eql(4);
|
||||
expect(response.mostUsedCharts.every((chart) => chart.options.length === 0)).to.eql(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177394
|
||||
registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
let response: MostUsedCharts;
|
||||
|
||||
before(async () => {
|
||||
response = await getMobileMostUsedCharts({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the top 5 and the other option only', () => {
|
||||
const deviceOptions = response.mostUsedCharts.find(
|
||||
(chart) => chart.key === 'device'
|
||||
)?.options;
|
||||
expect(deviceOptions?.length).to.eql(6);
|
||||
expect(deviceOptions?.find((option) => option.key === 'other')).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('should get network connection type object from span events', () => {
|
||||
const nctOptions = response.mostUsedCharts.find(
|
||||
(chart) => chart.key === 'netConnectionType'
|
||||
)?.options;
|
||||
expect(nctOptions?.length).to.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { generateMobileData } from './generate_mobile_data';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const registry = getService('registry');
|
||||
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
|
||||
|
||||
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2023-01-01T02:00:00.000Z').getTime();
|
||||
|
||||
async function getSessionsChart({
|
||||
environment = ENVIRONMENT_ALL.value,
|
||||
kuery = '',
|
||||
serviceName,
|
||||
transactionType = 'mobile',
|
||||
offset,
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
offset?: string;
|
||||
}) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
offset,
|
||||
kuery,
|
||||
transactionType,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registry.when.skip('without data loaded', { config: 'basic', archives: [] }, () => {
|
||||
describe('when no data', () => {
|
||||
it('handles empty state', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'foo' });
|
||||
expect(response.body.currentPeriod.timeseries).to.eql([]);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
expect(response.status).to.be(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177393
|
||||
registry.when.skip('with data loaded', { config: 'basic', archives: [] }, () => {
|
||||
before(async () => {
|
||||
await generateMobileData({
|
||||
apmSynthtraceEsClient,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
});
|
||||
|
||||
after(() => apmSynthtraceEsClient.clean());
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
it('returns timeseries for sessions chart', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'synth-android', offset: '1d' });
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries[0].y).to.eql(0);
|
||||
});
|
||||
|
||||
it('returns only current period timeseries when offset is not available', async () => {
|
||||
const response = await getSessionsChart({ serviceName: 'synth-android' });
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
it('returns empty state for filters', async () => {
|
||||
const response = await getSessionsChart({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `app.version:"none"`,
|
||||
});
|
||||
|
||||
expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true);
|
||||
expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql(true);
|
||||
});
|
||||
|
||||
it('returns the correct values filter is applied', async () => {
|
||||
const response = await getSessionsChart({
|
||||
serviceName: 'synth-android',
|
||||
environment: 'production',
|
||||
kuery: `transaction.name : "Start View - View Appearing"`,
|
||||
});
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql(
|
||||
true
|
||||
);
|
||||
|
||||
expect(response.body.currentPeriod.timeseries[0].y).to.eql(6);
|
||||
expect(response.body.previousPeriod.timeseries).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -187,6 +187,6 @@
|
|||
"@kbn/alerting-types",
|
||||
"@kbn/ai-assistant-common",
|
||||
"@kbn/core-deprecations-common",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/usage-collection-plugin"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue