mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* adding tests for error distribution * addressing pr changes * addressing pr comments * fixing ts issues Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
This commit is contained in:
parent
80db29a1c0
commit
90a4c0c9e9
6 changed files with 249 additions and 56 deletions
|
@ -45,40 +45,40 @@ export function Example() {
|
|||
const distribution = {
|
||||
bucketSize: 62350,
|
||||
currentPeriod: [
|
||||
{ key: 1624279912350, count: 6 },
|
||||
{ key: 1624279974700, count: 1 },
|
||||
{ key: 1624280037050, count: 2 },
|
||||
{ key: 1624280099400, count: 3 },
|
||||
{ key: 1624280161750, count: 13 },
|
||||
{ key: 1624280224100, count: 1 },
|
||||
{ key: 1624280286450, count: 2 },
|
||||
{ key: 1624280348800, count: 0 },
|
||||
{ key: 1624280411150, count: 4 },
|
||||
{ key: 1624280473500, count: 4 },
|
||||
{ key: 1624280535850, count: 1 },
|
||||
{ key: 1624280598200, count: 4 },
|
||||
{ key: 1624280660550, count: 0 },
|
||||
{ key: 1624280722900, count: 2 },
|
||||
{ key: 1624280785250, count: 3 },
|
||||
{ key: 1624280847600, count: 0 },
|
||||
{ x: 1624279912350, y: 6 },
|
||||
{ x: 1624279974700, y: 1 },
|
||||
{ x: 1624280037050, y: 2 },
|
||||
{ x: 1624280099400, y: 3 },
|
||||
{ x: 1624280161750, y: 13 },
|
||||
{ x: 1624280224100, y: 1 },
|
||||
{ x: 1624280286450, y: 2 },
|
||||
{ x: 1624280348800, y: 0 },
|
||||
{ x: 1624280411150, y: 4 },
|
||||
{ x: 1624280473500, y: 4 },
|
||||
{ x: 1624280535850, y: 1 },
|
||||
{ x: 1624280598200, y: 4 },
|
||||
{ x: 1624280660550, y: 0 },
|
||||
{ x: 1624280722900, y: 2 },
|
||||
{ x: 1624280785250, y: 3 },
|
||||
{ x: 1624280847600, y: 0 },
|
||||
],
|
||||
previousPeriod: [
|
||||
{ key: 1624279912350, count: 6 },
|
||||
{ key: 1624279974700, count: 1 },
|
||||
{ key: 1624280037050, count: 2 },
|
||||
{ key: 1624280099400, count: 3 },
|
||||
{ key: 1624280161750, count: 13 },
|
||||
{ key: 1624280224100, count: 1 },
|
||||
{ key: 1624280286450, count: 2 },
|
||||
{ key: 1624280348800, count: 0 },
|
||||
{ key: 1624280411150, count: 4 },
|
||||
{ key: 1624280473500, count: 4 },
|
||||
{ key: 1624280535850, count: 1 },
|
||||
{ key: 1624280598200, count: 4 },
|
||||
{ key: 1624280660550, count: 0 },
|
||||
{ key: 1624280722900, count: 2 },
|
||||
{ key: 1624280785250, count: 3 },
|
||||
{ key: 1624280847600, count: 0 },
|
||||
{ x: 1624279912350, y: 6 },
|
||||
{ x: 1624279974700, y: 1 },
|
||||
{ x: 1624280037050, y: 2 },
|
||||
{ x: 1624280099400, y: 3 },
|
||||
{ x: 1624280161750, y: 13 },
|
||||
{ x: 1624280224100, y: 1 },
|
||||
{ x: 1624280286450, y: 2 },
|
||||
{ x: 1624280348800, y: 0 },
|
||||
{ x: 1624280411150, y: 4 },
|
||||
{ x: 1624280473500, y: 4 },
|
||||
{ x: 1624280535850, y: 1 },
|
||||
{ x: 1624280598200, y: 4 },
|
||||
{ x: 1624280660550, y: 0 },
|
||||
{ x: 1624280722900, y: 2 },
|
||||
{ x: 1624280785250, y: 3 },
|
||||
{ x: 1624280847600, y: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import { ALERT_RULE_TYPE_ID as ALERT_RULE_TYPE_ID_NON_TYPED } from '@kbn/rule-da
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { AlertType } from '../../../../../common/alert_types';
|
||||
|
@ -31,7 +30,6 @@ import { ChartContainer } from '../../../shared/charts/chart_container';
|
|||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { LazyAlertsFlyout } from '../../../../../../observability/public';
|
||||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { Coordinate } from '../../../../../typings/timeseries';
|
||||
import { getTimeZone } from '../../../shared/charts/helper/timezone';
|
||||
|
||||
const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED =
|
||||
|
@ -40,18 +38,6 @@ const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED =
|
|||
type ErrorDistributionAPIResponse =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>;
|
||||
|
||||
export function getCoordinatedBuckets(
|
||||
buckets:
|
||||
| ErrorDistributionAPIResponse['currentPeriod']
|
||||
| ErrorDistributionAPIResponse['previousPeriod']
|
||||
): Coordinate[] {
|
||||
return buckets.map(({ count, key }) => {
|
||||
return {
|
||||
x: key,
|
||||
y: count,
|
||||
};
|
||||
});
|
||||
}
|
||||
interface Props {
|
||||
fetchStatus: FETCH_STATUS;
|
||||
distribution: ErrorDistributionAPIResponse;
|
||||
|
@ -61,15 +47,13 @@ interface Props {
|
|||
export function ErrorDistribution({ distribution, title, fetchStatus }: Props) {
|
||||
const { core } = useApmPluginContext();
|
||||
const theme = useTheme();
|
||||
const currentPeriod = getCoordinatedBuckets(distribution.currentPeriod);
|
||||
const previousPeriod = getCoordinatedBuckets(distribution.previousPeriod);
|
||||
|
||||
const { urlParams } = useUrlParams();
|
||||
const { comparisonEnabled } = urlParams;
|
||||
|
||||
const timeseries = [
|
||||
{
|
||||
data: currentPeriod,
|
||||
data: distribution.currentPeriod,
|
||||
color: theme.eui.euiColorVis1,
|
||||
title: i18n.translate('xpack.apm.errorGroup.chart.ocurrences', {
|
||||
defaultMessage: 'Occurences',
|
||||
|
@ -78,10 +62,7 @@ export function ErrorDistribution({ distribution, title, fetchStatus }: Props) {
|
|||
...(comparisonEnabled
|
||||
? [
|
||||
{
|
||||
data: offsetPreviousPeriodCoordinates({
|
||||
currentPeriodTimeseries: currentPeriod,
|
||||
previousPeriodTimeseries: previousPeriod,
|
||||
}),
|
||||
data: distribution.previousPeriod,
|
||||
color: theme.eui.euiColorMediumShade,
|
||||
title: i18n.translate(
|
||||
'xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel',
|
||||
|
|
|
@ -80,8 +80,8 @@ export async function getBuckets({
|
|||
|
||||
const buckets = (resp.aggregations?.distribution.buckets || []).map(
|
||||
(bucket) => ({
|
||||
key: bucket.key,
|
||||
count: bucket.doc_count,
|
||||
x: bucket.key,
|
||||
y: bucket.doc_count,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate';
|
||||
import { Setup } from '../../helpers/setup_request';
|
||||
import { BUCKET_TARGET_COUNT } from '../../transactions/constants';
|
||||
import { getBuckets } from './get_buckets';
|
||||
|
@ -64,7 +65,10 @@ export async function getErrorDistribution({
|
|||
|
||||
return {
|
||||
currentPeriod: currentPeriod.buckets,
|
||||
previousPeriod: previousPeriod.buckets,
|
||||
previousPeriod: offsetPreviousPeriodCoordinates({
|
||||
currentPeriodTimeseries: currentPeriod.buckets,
|
||||
previousPeriodTimeseries: previousPeriod.buckets,
|
||||
}),
|
||||
bucketSize,
|
||||
};
|
||||
}
|
||||
|
|
204
x-pack/test/apm_api_integration/tests/errors/distribution.ts
Normal file
204
x-pack/test/apm_api_integration/tests/errors/distribution.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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 { service, timerange } from '@elastic/apm-synthtrace';
|
||||
import expect from '@kbn/expect';
|
||||
import { first, last, sumBy } from 'lodash';
|
||||
import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number';
|
||||
import {
|
||||
APIClientRequestParamsOf,
|
||||
APIReturnType,
|
||||
} from '../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import { RecursivePartial } from '../../../../plugins/apm/typings/common';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { registry } from '../../common/registry';
|
||||
|
||||
type ErrorsDistribution =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
const synthtraceEsClient = getService('synthtraceEsClient');
|
||||
|
||||
const serviceName = 'synth-go';
|
||||
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
|
||||
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
|
||||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/distribution'>['params']
|
||||
>
|
||||
) {
|
||||
const response = await apmApiClient.readUser({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
...overrides?.path,
|
||||
},
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.currentPeriod.length).to.be(0);
|
||||
expect(response.body.previousPeriod.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
registry.when(
|
||||
'when data is loaded',
|
||||
{ config: 'basic', archives: ['apm_mappings_only_8.0.0'] },
|
||||
() => {
|
||||
describe('errors distribution', () => {
|
||||
const appleTransaction = {
|
||||
name: 'GET /apple 🍎 ',
|
||||
successRate: 75,
|
||||
failureRate: 25,
|
||||
};
|
||||
const bananaTransaction = {
|
||||
name: 'GET /banana 🍌',
|
||||
successRate: 50,
|
||||
failureRate: 50,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
const serviceGoProdInstance = service(serviceName, 'production', 'go').instance(
|
||||
'instance-a'
|
||||
);
|
||||
|
||||
const interval = '1m';
|
||||
|
||||
const indices = [appleTransaction, bananaTransaction]
|
||||
.map((transaction, index) => {
|
||||
return [
|
||||
...timerange(start, end)
|
||||
.interval(interval)
|
||||
.rate(transaction.successRate)
|
||||
.flatMap((timestamp) =>
|
||||
serviceGoProdInstance
|
||||
.transaction(transaction.name)
|
||||
.timestamp(timestamp)
|
||||
.duration(1000)
|
||||
.success()
|
||||
.serialize()
|
||||
),
|
||||
...timerange(start, end)
|
||||
.interval(interval)
|
||||
.rate(transaction.failureRate)
|
||||
.flatMap((timestamp) =>
|
||||
serviceGoProdInstance
|
||||
.transaction(transaction.name)
|
||||
.errors(
|
||||
serviceGoProdInstance
|
||||
.error(`Error ${index}`, transaction.name)
|
||||
.timestamp(timestamp)
|
||||
)
|
||||
.duration(1000)
|
||||
.timestamp(timestamp)
|
||||
.failure()
|
||||
.serialize()
|
||||
),
|
||||
];
|
||||
})
|
||||
.flatMap((_) => _);
|
||||
|
||||
await synthtraceEsClient.index(indices);
|
||||
});
|
||||
|
||||
after(() => synthtraceEsClient.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 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', () => {
|
||||
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(),
|
||||
comparisonStart: new Date(start).toISOString(),
|
||||
comparisonEnd: new Date(start + fiveMinutes).toISOString(),
|
||||
},
|
||||
});
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -241,6 +241,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
|
|||
loadTestFile(require.resolve('./latency/service_apis'));
|
||||
});
|
||||
|
||||
describe('errors/distribution', function () {
|
||||
loadTestFile(require.resolve('./errors/distribution'));
|
||||
});
|
||||
|
||||
registry.run(providerContext);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue