[APM] Migrate /correlations to deployment agnostic test (#199276)

## Summary

Closes https://github.com/elastic/kibana/issues/198962
Part of https://github.com/elastic/kibana/issues/193245

This PR contains the changes to migrate `correlations` test folder to
Deployment-agnostic testing strategy.

### How to test

- Serverless

```
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM"
```

It's recommended to be run against
[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)

- Stateful
```
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM"
```

## Checks

- [ ] (OPTIONAL, only if a test has been unskipped) Run flaky test suite
- [x] local run for serverless
- [x] local run for stateful
- [x] MKI run for serverless
This commit is contained in:
Sergi Romeu 2024-11-12 00:23:39 +01:00 committed by GitHub
parent e4298492b5
commit c97b85d169
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 452 additions and 388 deletions

View file

@ -0,0 +1,32 @@
/*
* 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.
*/
type ArchiveName =
| '8.0.0'
| 'apm_8.0.0'
| 'apm_mappings_only_8.0.0'
| 'infra_metrics_and_apm'
| 'metrics_8.0.0'
| 'ml_8.0.0'
| 'observability_overview'
| 'rum_8.0.0'
| 'rum_test_data';
export const ARCHIVER_ROUTES: { [key in ArchiveName]: string } = {
'8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0',
'apm_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0',
'apm_mappings_only_8.0.0':
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0',
infra_metrics_and_apm:
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm',
'metrics_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0',
'ml_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/ml_8.0.0',
observability_overview:
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/observability_overview',
'rum_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_8.0.0',
rum_test_data: 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data',
};

View file

@ -0,0 +1,236 @@
/*
* 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 { orderBy } from 'lodash';
import expect from '@kbn/expect';
import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
// These tests go through the full sequence of queries required
// to get the final results for a failed transactions correlation analysis.
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
});
describe('failed transactions', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);
const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);
const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});
expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);
const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
},
},
});
expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);
const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};
expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
0,
`Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);
});
});
describe('with data', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);
const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);
const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});
expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);
const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
(t) => !(t === EVENT_OUTCOME)
);
// Identified 80 fieldCandidates.
expect(fieldCandidates.length).to.eql(
80,
`Expected field candidates length to be '80', got '${fieldCandidates.length}'`
);
const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates,
},
},
});
expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);
const fieldsToSample = new Set<string>();
if (
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0
) {
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach(
(d) => {
fieldsToSample.add(d.fieldName);
}
);
}
const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
errorHistogram: errorDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};
expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
expect(finalRawResponse?.errorHistogram?.length).to.be(101);
expect(finalRawResponse?.overallHistogram?.length).to.be(101);
expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
29,
`Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);
const sortedCorrelations = orderBy(
finalRawResponse?.failedTransactionsCorrelations,
['score', 'fieldName', 'fieldValue'],
['desc', 'asc', 'asc']
);
const correlation = sortedCorrelations?.[0];
expect(typeof correlation).to.be('object');
expect(correlation?.doc_count).to.be(31);
expect(correlation?.score).to.be(83.70467673605746);
expect(correlation?.bg_count).to.be(31);
expect(correlation?.fieldName).to.be('transaction.result');
expect(correlation?.fieldValue).to.be('HTTP 5xx');
expect(typeof correlation?.pValue).to.be('number');
expect(typeof correlation?.normalizedScore).to.be('number');
expect(typeof correlation?.failurePercentage).to.be('number');
expect(typeof correlation?.successPercentage).to.be('number');
});
});
});
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
const getOptions = () => ({
params: {
query: {
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
},
},
});
describe('field candidates', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.be(200);
// If the source indices are empty, there will be no field candidates
// because of the `include_empty_fields: false` option in the query.
expect(response.body?.fieldCandidates.length).to.be(0);
});
});
describe('with data and default args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
it('returns field candidates', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.eql(200);
expect(response.body?.fieldCandidates.length).to.be(81);
});
});
});
}

View file

@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/field_value_pairs/transactions';
@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
registry.when('field value pairs without data', { config: 'trial', archives: [] }, () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
describe('field value pairs', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.be(200);
expect(response.body?.fieldValuePairs.length).to.be(0);
});
});
describe('with data and default args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
expect(response.status).to.be(200);
expect(response.body?.fieldValuePairs.length).to.be(0);
});
});
registry.when(
'field value pairs with data and default args',
{ config: 'trial', archives: ['8.0.0'] },
() => {
it('returns field value pairs', async () => {
const response = await apmApiClient.readUser({
endpoint,
@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.fieldValuePairs.length).to.be(124);
});
}
);
});
});
}

View file

@ -0,0 +1,18 @@
/*
* 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('correlations', () => {
loadTestFile(require.resolve('./failed_transactions.spec.ts'));
loadTestFile(require.resolve('./field_candidates.spec.ts'));
loadTestFile(require.resolve('./field_value_pairs.spec.ts'));
loadTestFile(require.resolve('./latency.spec.ts'));
loadTestFile(require.resolve('./p_values.spec.ts'));
});
}

View file

@ -14,13 +14,14 @@ import type {
LatencyCorrelationsResponse,
} from '@kbn/apm-plugin/common/correlations/latency_correlations/types';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
// These tests go through the full sequence of queries required
// to get the final results for a latency correlation analysis.
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
@ -30,10 +31,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
kuery: '',
});
registry.when(
'correlations latency overall without data',
{ config: 'trial', archives: [] },
() => {
describe('latency', () => {
describe('overall without data', () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
@ -104,13 +103,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(finalRawResponse?.overallHistogram).to.be(undefined);
expect(finalRawResponse?.latencyCorrelations?.length).to.be(0);
});
}
);
});
describe('with data and opbeans-node args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
registry.when(
'correlations latency with data and opbeans-node args',
{ config: 'trial', archives: ['8.0.0'] },
() => {
// putting this into a single `it` because the responses depend on each other
it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
@ -250,6 +252,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(correlation?.ksTest).to.be(1.9848961005439386e-12);
expect(correlation?.histogram?.length).to.be(101);
});
}
);
});
});
}

View file

@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/p_values/transactions';
@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
registry.when('p values without data', { config: 'trial', archives: [] }, () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
describe('p values', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.be(200);
expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
});
});
describe('with data and default args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
expect(response.status).to.be(200);
expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
});
});
registry.when(
'p values with data and default args',
{ config: 'trial', archives: ['8.0.0'] },
() => {
it('returns p values', async () => {
const response = await apmApiClient.readUser({
endpoint,
@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.failedTransactionsCorrelations.length).to.be(15);
});
}
);
});
});
}

View file

@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/significant_correlations/transactions';
@ -65,22 +66,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
registry.when('significant correlations without data', { config: 'trial', archives: [] }, () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
describe('significant correlations', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.be(200);
expect(response.body?.latencyCorrelations.length).to.be(0);
});
});
describe('with data and default args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});
expect(response.status).to.be(200);
expect(response.body?.latencyCorrelations.length).to.be(0);
});
});
registry.when(
'significant correlations with data and default args',
{ config: 'trial', archives: ['8.0.0'] },
() => {
it('returns significant correlations', async () => {
const response = await apmApiClient.readUser({
endpoint,
@ -90,6 +96,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.latencyCorrelations.length).to.be(7);
});
}
);
});
});
}

View file

@ -15,6 +15,7 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./mobile'));
loadTestFile(require.resolve('./custom_dashboards'));
loadTestFile(require.resolve('./dependencies'));
loadTestFile(require.resolve('./correlations'));
loadTestFile(require.resolve('./entities'));
loadTestFile(require.resolve('./cold_start'));
});

View file

@ -3786,10 +3786,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
"lifecycle": {
"name": "apm-rollover-30-days",
"rollover_alias": "apm-8.0.0-error"
},
"mapping": {
"total_fields": {
"limit": "2000"
@ -4243,8 +4239,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}
@ -8183,10 +8178,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
"lifecycle": {
"name": "apm-rollover-30-days",
"rollover_alias": "apm-8.0.0-metric"
},
"mapping": {
"total_fields": {
"limit": "2000"
@ -8640,8 +8631,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}
@ -12871,8 +12861,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}
@ -16653,10 +16642,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
"lifecycle": {
"name": "apm-rollover-30-days",
"rollover_alias": "apm-8.0.0-profile"
},
"mapping": {
"total_fields": {
"limit": "2000"
@ -17110,8 +17095,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}
@ -20899,10 +20883,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
"lifecycle": {
"name": "apm-rollover-30-days",
"rollover_alias": "apm-8.0.0-span"
},
"mapping": {
"total_fields": {
"limit": "2000"
@ -21356,8 +21336,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}
@ -25242,10 +25221,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
"lifecycle": {
"name": "apm-rollover-30-days",
"rollover_alias": "apm-8.0.0-transaction"
},
"mapping": {
"total_fields": {
"limit": "2000"
@ -25699,8 +25674,7 @@
"transaction.message.queue.name",
"fields.*"
]
},
"refresh_interval": "1ms"
}
}
}
}

View file

@ -1,223 +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 { orderBy } from 'lodash';
import expect from '@kbn/expect';
import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
// These tests go through the full sequence of queries required
// to get the final results for a failed transactions correlation analysis.
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
});
registry.when('failed transactions without data', { config: 'trial', archives: [] }, () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);
const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);
const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});
expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);
const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
},
},
});
expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);
const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};
expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
0,
`Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);
});
});
registry.when('failed transactions with data', { config: 'trial', archives: ['8.0.0'] }, () => {
it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);
const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});
expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);
const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});
expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);
const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
(t) => !(t === EVENT_OUTCOME)
);
// Identified 80 fieldCandidates.
expect(fieldCandidates.length).to.eql(
80,
`Expected field candidates length to be '80', got '${fieldCandidates.length}'`
);
const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates,
},
},
});
expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);
const fieldsToSample = new Set<string>();
if (failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0) {
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach((d) => {
fieldsToSample.add(d.fieldName);
});
}
const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
errorHistogram: errorDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};
expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
expect(finalRawResponse?.errorHistogram?.length).to.be(101);
expect(finalRawResponse?.overallHistogram?.length).to.be(101);
expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
29,
`Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);
const sortedCorrelations = orderBy(
finalRawResponse?.failedTransactionsCorrelations,
['score', 'fieldName', 'fieldValue'],
['desc', 'asc', 'asc']
);
const correlation = sortedCorrelations?.[0];
expect(typeof correlation).to.be('object');
expect(correlation?.doc_count).to.be(31);
expect(correlation?.score).to.be(83.70467673605746);
expect(correlation?.bg_count).to.be(31);
expect(correlation?.fieldName).to.be('transaction.result');
expect(correlation?.fieldValue).to.be('HTTP 5xx');
expect(typeof correlation?.pValue).to.be('number');
expect(typeof correlation?.normalizedScore).to.be('number');
expect(typeof correlation?.failurePercentage).to.be('number');
expect(typeof correlation?.successPercentage).to.be('number');
});
});
}

View file

@ -1,57 +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 { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
const getOptions = () => ({
params: {
query: {
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
},
},
});
registry.when('field candidates without data', { config: 'trial', archives: [] }, () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.be(200);
// If the source indices are empty, there will be no field candidates
// because of the `include_empty_fields: false` option in the query.
expect(response.body?.fieldCandidates.length).to.be(0);
});
});
registry.when(
'field candidates with data and default args',
{ config: 'trial', archives: ['8.0.0'] },
() => {
it('returns field candidates', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});
expect(response.status).to.eql(200);
expect(response.body?.fieldCandidates.length).to.be(81);
});
}
);
}