mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Mobile UI crash widget (#163527)
## Summary Implemented crash widget & most crashes by location widget in the mobile landing page in APM. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
707fbf115a
commit
70a2f4cdb5
12 changed files with 354 additions and 24 deletions
|
@ -254,6 +254,7 @@ export class MobileDevice extends Entity<ApmFields> {
|
|||
return new ApmError({
|
||||
...this.fields,
|
||||
'error.type': 'crash',
|
||||
'error.id': generateLongId(message),
|
||||
'error.exception': [{ message, ...{ type: 'crash' } }],
|
||||
'error.grouping_name': groupingName || message,
|
||||
});
|
||||
|
|
|
@ -133,17 +133,18 @@ export function MobileLocationStats({
|
|||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
{
|
||||
color: euiTheme.eui.euiColorDisabled,
|
||||
color: euiTheme.eui.euiColorLightestShade,
|
||||
title: i18n.translate('xpack.apm.mobile.location.metrics.crashes', {
|
||||
defaultMessage: 'Most crashes',
|
||||
}),
|
||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
||||
defaultMessage: 'Coming Soon',
|
||||
extra: getComparisonValueFormatter({
|
||||
currentPeriodValue: currentPeriod?.mostCrashes.value,
|
||||
previousPeriodValue: previousPeriod?.mostCrashes.value,
|
||||
}),
|
||||
icon: getIcon('bug'),
|
||||
value: NOT_AVAILABLE_LABEL,
|
||||
value: currentPeriod?.mostCrashes.location ?? NOT_AVAILABLE_LABEL,
|
||||
valueFormatter: (value) => `${value}`,
|
||||
trend: [],
|
||||
trend: currentPeriod?.mostCrashes.timeseries,
|
||||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTheme } from '@kbn/observability-shared-plugin/public';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n';
|
||||
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
|
||||
import {
|
||||
useFetcher,
|
||||
|
@ -104,17 +105,16 @@ export function MobileStats({
|
|||
|
||||
const metrics: MetricDatum[] = [
|
||||
{
|
||||
color: euiTheme.eui.euiColorDisabled,
|
||||
color: euiTheme.eui.euiColorLightestShade,
|
||||
title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', {
|
||||
defaultMessage: 'Crash Rate (Crash per minute)',
|
||||
}),
|
||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
||||
defaultMessage: 'Coming Soon',
|
||||
defaultMessage: 'Crash rate',
|
||||
}),
|
||||
icon: getIcon('bug'),
|
||||
value: 'N/A',
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: [],
|
||||
value: data?.currentPeriod?.crashRate?.value ?? NOT_AVAILABLE_LABEL,
|
||||
valueFormatter: (value: number) =>
|
||||
valueFormatter(Number((value * 100).toPrecision(2)), '%'),
|
||||
trend: data?.currentPeriod?.crashRate?.timeseries,
|
||||
extra: getComparisonValueFormatter(data?.previousPeriod.crashRate?.value),
|
||||
trendShape: MetricTrendShape.Area,
|
||||
},
|
||||
{
|
||||
|
@ -137,7 +137,7 @@ export function MobileStats({
|
|||
defaultMessage: 'Sessions',
|
||||
}),
|
||||
icon: getIcon('timeslider'),
|
||||
value: data?.currentPeriod?.sessions?.value ?? NaN,
|
||||
value: data?.currentPeriod?.sessions?.value ?? NOT_AVAILABLE_LABEL,
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: data?.currentPeriod?.sessions?.timeseries,
|
||||
extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value),
|
||||
|
@ -149,7 +149,7 @@ export function MobileStats({
|
|||
defaultMessage: 'HTTP requests',
|
||||
}),
|
||||
icon: getIcon('kubernetesPod'),
|
||||
value: data?.currentPeriod?.requests?.value ?? NaN,
|
||||
value: data?.currentPeriod?.requests?.value ?? NOT_AVAILABLE_LABEL,
|
||||
extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value),
|
||||
valueFormatter: (value: number) => valueFormatter(value),
|
||||
trend: data?.currentPeriod?.requests?.timeseries,
|
||||
|
|
165
x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts
Normal file
165
x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 { ProcessorEvent } from '@kbn/observability-plugin/common';
|
||||
import {
|
||||
kqlQuery,
|
||||
rangeQuery,
|
||||
termQuery,
|
||||
} from '@kbn/observability-plugin/server';
|
||||
import { Coordinate } from '../../../typings/timeseries';
|
||||
import { Maybe } from '../../../typings/common';
|
||||
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
|
||||
import { getBucketSize } from '../../../common/utils/get_bucket_size';
|
||||
import {
|
||||
ERROR_TYPE,
|
||||
ERROR_ID,
|
||||
SERVICE_NAME,
|
||||
} from '../../../common/es_fields/apm';
|
||||
import { environmentQuery } from '../../../common/utils/environment_query';
|
||||
import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
|
||||
import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate';
|
||||
|
||||
export interface CrashRateTimeseries {
|
||||
currentPeriod: { timeseries: Coordinate[]; value: Maybe<number> };
|
||||
previousPeriod: { timeseries: Coordinate[]; value: Maybe<number> };
|
||||
}
|
||||
|
||||
interface Props {
|
||||
apmEventClient: APMEventClient;
|
||||
serviceName: string;
|
||||
transactionName?: string;
|
||||
environment: string;
|
||||
start: number;
|
||||
end: number;
|
||||
kuery: string;
|
||||
offset?: string;
|
||||
}
|
||||
|
||||
async function getMobileCrashTimeseries({
|
||||
apmEventClient,
|
||||
serviceName,
|
||||
transactionName,
|
||||
environment,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
offset,
|
||||
}: Props) {
|
||||
const { startWithOffset, endWithOffset } = getOffsetInMs({
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
});
|
||||
|
||||
const { intervalString } = getBucketSize({
|
||||
start: startWithOffset,
|
||||
end: endWithOffset,
|
||||
minBucketSize: 60,
|
||||
});
|
||||
|
||||
const aggs = {
|
||||
crashes: {
|
||||
cardinality: { field: ERROR_ID },
|
||||
},
|
||||
};
|
||||
|
||||
const response = await apmEventClient.search('get_mobile_crash_rate', {
|
||||
apm: {
|
||||
events: [ProcessorEvent.error],
|
||||
},
|
||||
body: {
|
||||
track_total_hits: false,
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
...termQuery(ERROR_TYPE, 'crash'),
|
||||
...termQuery(SERVICE_NAME, serviceName),
|
||||
...rangeQuery(startWithOffset, endWithOffset),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: { min: startWithOffset, max: endWithOffset },
|
||||
},
|
||||
aggs,
|
||||
},
|
||||
...aggs,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const timeseries =
|
||||
response?.aggregations?.timeseries.buckets.map((bucket) => {
|
||||
return {
|
||||
x: bucket.key,
|
||||
y: bucket.crashes.value,
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
return {
|
||||
timeseries,
|
||||
value: response.aggregations?.crashes?.value,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getMobileCrashRate({
|
||||
kuery,
|
||||
apmEventClient,
|
||||
serviceName,
|
||||
transactionName,
|
||||
environment,
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
}: Props): Promise<CrashRateTimeseries> {
|
||||
const options = {
|
||||
serviceName,
|
||||
transactionName,
|
||||
apmEventClient,
|
||||
kuery,
|
||||
environment,
|
||||
};
|
||||
|
||||
const currentPeriodPromise = getMobileCrashTimeseries({
|
||||
...options,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
const previousPeriodPromise = offset
|
||||
? getMobileCrashTimeseries({
|
||||
...options,
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
})
|
||||
: { timeseries: [], value: null };
|
||||
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
currentPeriodPromise,
|
||||
previousPeriodPromise,
|
||||
]);
|
||||
return {
|
||||
currentPeriod,
|
||||
previousPeriod: {
|
||||
timeseries: offsetPreviousPeriodCoordinates({
|
||||
currentPeriodTimeseries: currentPeriod.timeseries,
|
||||
previousPeriodTimeseries: previousPeriod.timeseries,
|
||||
}),
|
||||
value: previousPeriod?.value,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 { ProcessorEvent } from '@kbn/observability-plugin/common';
|
||||
import {
|
||||
kqlQuery,
|
||||
rangeQuery,
|
||||
termQuery,
|
||||
} from '@kbn/observability-plugin/server';
|
||||
import { SERVICE_NAME, ERROR_TYPE } from '../../../common/es_fields/apm';
|
||||
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
|
||||
import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
|
||||
import { getBucketSize } from '../../../common/utils/get_bucket_size';
|
||||
import { environmentQuery } from '../../../common/utils/environment_query';
|
||||
|
||||
interface Props {
|
||||
kuery: string;
|
||||
apmEventClient: APMEventClient;
|
||||
serviceName: string;
|
||||
environment: string;
|
||||
start: number;
|
||||
end: number;
|
||||
locationField?: string;
|
||||
offset?: string;
|
||||
}
|
||||
|
||||
export async function getCrashesByLocation({
|
||||
kuery,
|
||||
apmEventClient,
|
||||
serviceName,
|
||||
environment,
|
||||
start,
|
||||
end,
|
||||
locationField,
|
||||
offset,
|
||||
}: Props) {
|
||||
const { startWithOffset, endWithOffset } = getOffsetInMs({
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
});
|
||||
|
||||
const { intervalString } = getBucketSize({
|
||||
start: startWithOffset,
|
||||
end: endWithOffset,
|
||||
minBucketSize: 60,
|
||||
});
|
||||
|
||||
const aggs = {
|
||||
crashes: {
|
||||
filter: { term: { [ERROR_TYPE]: 'crash' } },
|
||||
aggs: {
|
||||
crashesByLocation: {
|
||||
terms: {
|
||||
field: locationField,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const response = await apmEventClient.search('get_mobile_location_crashes', {
|
||||
apm: {
|
||||
events: [ProcessorEvent.error],
|
||||
},
|
||||
body: {
|
||||
track_total_hits: false,
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
...termQuery(SERVICE_NAME, serviceName),
|
||||
...rangeQuery(startWithOffset, endWithOffset),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
},
|
||||
aggs,
|
||||
},
|
||||
...aggs,
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
location: response.aggregations?.crashes?.crashesByLocation?.buckets[0]
|
||||
?.key as string,
|
||||
value:
|
||||
response.aggregations?.crashes?.crashesByLocation?.buckets[0]
|
||||
?.doc_count ?? 0,
|
||||
timeseries:
|
||||
response.aggregations?.timeseries?.buckets.map((bucket) => ({
|
||||
x: bucket.key,
|
||||
y:
|
||||
response.aggregations?.crashes?.crashesByLocation?.buckets[0]
|
||||
?.doc_count ?? 0,
|
||||
})) ?? [],
|
||||
};
|
||||
}
|
|
@ -9,6 +9,7 @@ import { CLIENT_GEO_COUNTRY_NAME } from '../../../common/es_fields/apm';
|
|||
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
|
||||
import { getSessionsByLocation } from './get_mobile_sessions_by_location';
|
||||
import { getHttpRequestsByLocation } from './get_mobile_http_requests_by_location';
|
||||
import { getCrashesByLocation } from './get_mobile_crashes_by_location';
|
||||
import { Maybe } from '../../../typings/common';
|
||||
|
||||
export type Timeseries = Array<{ x: number; y: number }>;
|
||||
|
@ -24,6 +25,11 @@ interface LocationStats {
|
|||
value: Maybe<number>;
|
||||
timeseries: Timeseries;
|
||||
};
|
||||
mostCrashes: {
|
||||
location?: string;
|
||||
value: Maybe<number>;
|
||||
timeseries: Timeseries;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MobileLocationStats {
|
||||
|
@ -63,14 +69,16 @@ async function getMobileLocationStats({
|
|||
offset,
|
||||
};
|
||||
|
||||
const [mostSessions, mostRequests] = await Promise.all([
|
||||
const [mostSessions, mostRequests, mostCrashes] = await Promise.all([
|
||||
getSessionsByLocation({ ...commonProps }),
|
||||
getHttpRequestsByLocation({ ...commonProps }),
|
||||
getCrashesByLocation({ ...commonProps }),
|
||||
]);
|
||||
|
||||
return {
|
||||
mostSessions,
|
||||
mostRequests,
|
||||
mostCrashes,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -108,6 +116,7 @@ export async function getMobileLocationStatsPeriods({
|
|||
: {
|
||||
mostSessions: { value: null, timeseries: [] },
|
||||
mostRequests: { value: null, timeseries: [] },
|
||||
mostCrashes: { value: null, timeseries: [] },
|
||||
};
|
||||
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
|
|
|
@ -9,6 +9,7 @@ import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_ev
|
|||
import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
|
||||
import { getMobileSessions } from './get_mobile_sessions';
|
||||
import { getMobileHttpRequests } from './get_mobile_http_requests';
|
||||
import { getMobileCrashRate } from './get_mobile_crash_rate';
|
||||
import { Maybe } from '../../../typings/common';
|
||||
|
||||
export interface Timeseries {
|
||||
|
@ -18,6 +19,7 @@ export interface Timeseries {
|
|||
interface MobileStats {
|
||||
sessions: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||
requests: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||
crashRate: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||
}
|
||||
|
||||
export interface MobilePeriodStats {
|
||||
|
@ -60,9 +62,10 @@ async function getMobileStats({
|
|||
offset,
|
||||
};
|
||||
|
||||
const [sessions, httpRequests] = await Promise.all([
|
||||
const [sessions, httpRequests, crashes] = await Promise.all([
|
||||
getMobileSessions({ ...commonProps }),
|
||||
getMobileHttpRequests({ ...commonProps }),
|
||||
getMobileCrashRate({ ...commonProps }),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -74,6 +77,18 @@ async function getMobileStats({
|
|||
value: httpRequests.currentPeriod.value,
|
||||
timeseries: httpRequests.currentPeriod.timeseries as Timeseries[],
|
||||
},
|
||||
crashRate: {
|
||||
value: sessions.currentPeriod.value
|
||||
? (crashes.currentPeriod.value ?? 0) / sessions.currentPeriod.value
|
||||
: 0,
|
||||
timeseries: crashes.currentPeriod.timeseries.map((bucket, i) => {
|
||||
const sessionValue = sessions.currentPeriod.timeseries[i].y;
|
||||
return {
|
||||
x: bucket.x,
|
||||
y: sessionValue ? (bucket.y ?? 0) / sessionValue : 0,
|
||||
};
|
||||
}) as Timeseries[],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -107,6 +122,7 @@ export async function getMobileStatsPeriods({
|
|||
: {
|
||||
sessions: { timeseries: [], value: null },
|
||||
requests: { timeseries: [], value: null },
|
||||
crashRate: { timeseries: [], value: null },
|
||||
};
|
||||
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
|
|
|
@ -8271,7 +8271,6 @@
|
|||
"xpack.apm.mobile.location.metrics.http.requests.title": "Le plus utilisé dans",
|
||||
"xpack.apm.mobile.location.metrics.launches": "La plupart des lancements",
|
||||
"xpack.apm.mobile.location.metrics.sessions": "La plupart des sessions",
|
||||
"xpack.apm.mobile.metrics.crash.rate": "Taux de panne (pannes par minute)",
|
||||
"xpack.apm.mobile.metrics.http.requests": "Requêtes HTTP",
|
||||
"xpack.apm.mobile.metrics.load.time": "Temps de chargement de l'application le plus lent",
|
||||
"xpack.apm.mobile.metrics.sessions": "Sessions",
|
||||
|
|
|
@ -8287,7 +8287,6 @@
|
|||
"xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている",
|
||||
"xpack.apm.mobile.location.metrics.launches": "最も多い起動",
|
||||
"xpack.apm.mobile.location.metrics.sessions": "最も多いセッション",
|
||||
"xpack.apm.mobile.metrics.crash.rate": "クラッシュ率(毎分のクラッシュ数)",
|
||||
"xpack.apm.mobile.metrics.http.requests": "HTTPリクエスト",
|
||||
"xpack.apm.mobile.metrics.load.time": "最も遅いアプリ読み込み時間",
|
||||
"xpack.apm.mobile.metrics.sessions": "セッション",
|
||||
|
|
|
@ -8286,7 +8286,6 @@
|
|||
"xpack.apm.mobile.location.metrics.http.requests.title": "最常用于",
|
||||
"xpack.apm.mobile.location.metrics.launches": "大多数启动",
|
||||
"xpack.apm.mobile.location.metrics.sessions": "大多数会话",
|
||||
"xpack.apm.mobile.metrics.crash.rate": "崩溃速率(每分钟崩溃数)",
|
||||
"xpack.apm.mobile.metrics.http.requests": "HTTP 请求",
|
||||
"xpack.apm.mobile.metrics.load.time": "最慢应用加载时间",
|
||||
"xpack.apm.mobile.metrics.sessions": "会话",
|
||||
|
|
|
@ -219,6 +219,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -253,6 +256,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const { location } = response.currentPeriod.mostRequests;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
|
||||
it('returns location for most crashes', () => {
|
||||
const { location } = response.currentPeriod.mostCrashes;
|
||||
expect(location).to.be('China');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
|
@ -265,6 +273,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
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.mostSessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
|
@ -272,6 +281,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
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
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
|
@ -283,6 +295,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
|
@ -293,6 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(response.currentPeriod.mostSessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostRequests.value).to.eql(3);
|
||||
expect(response.currentPeriod.mostCrashes.value).to.eql(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ 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 { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values';
|
||||
import { sumBy } from 'lodash';
|
||||
import { sumBy, meanBy } from 'lodash';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>;
|
||||
|
@ -103,7 +103,7 @@ async function generateData({
|
|||
return [
|
||||
galaxy10
|
||||
.transaction('Start View - View Appearing', 'Android Activity')
|
||||
.errors(galaxy10.crash({ message: 'error' }).timestamp(timestamp))
|
||||
.errors(galaxy10.crash({ message: 'error C' }).timestamp(timestamp))
|
||||
.timestamp(timestamp)
|
||||
.duration(500)
|
||||
.success()
|
||||
|
@ -120,7 +120,11 @@ async function generateData({
|
|||
),
|
||||
huaweiP2
|
||||
.transaction('Start View - View Appearing', 'huaweiP2 Activity')
|
||||
.errors(huaweiP2.crash({ message: 'error' }).timestamp(timestamp))
|
||||
.errors(
|
||||
huaweiP2.crash({ message: 'error A' }).timestamp(timestamp),
|
||||
huaweiP2.crash({ message: 'error B' }).timestamp(timestamp),
|
||||
huaweiP2.crash({ message: 'error D' }).timestamp(timestamp)
|
||||
)
|
||||
.timestamp(timestamp)
|
||||
.duration(20)
|
||||
.success(),
|
||||
|
@ -211,6 +215,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filters are applied', () => {
|
||||
|
@ -223,6 +236,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
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.sessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||
true
|
||||
|
@ -230,6 +244,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
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
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the correct values when single filter is applied', async () => {
|
||||
|
@ -241,6 +258,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
||||
expect(response.currentPeriod.crashRate.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('returns the correct values when multiple filters are applied', async () => {
|
||||
|
@ -248,9 +266,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue