mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -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({
|
return new ApmError({
|
||||||
...this.fields,
|
...this.fields,
|
||||||
'error.type': 'crash',
|
'error.type': 'crash',
|
||||||
|
'error.id': generateLongId(message),
|
||||||
'error.exception': [{ message, ...{ type: 'crash' } }],
|
'error.exception': [{ message, ...{ type: 'crash' } }],
|
||||||
'error.grouping_name': groupingName || message,
|
'error.grouping_name': groupingName || message,
|
||||||
});
|
});
|
||||||
|
|
|
@ -133,17 +133,18 @@ export function MobileLocationStats({
|
||||||
trendShape: MetricTrendShape.Area,
|
trendShape: MetricTrendShape.Area,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: euiTheme.eui.euiColorDisabled,
|
color: euiTheme.eui.euiColorLightestShade,
|
||||||
title: i18n.translate('xpack.apm.mobile.location.metrics.crashes', {
|
title: i18n.translate('xpack.apm.mobile.location.metrics.crashes', {
|
||||||
defaultMessage: 'Most crashes',
|
defaultMessage: 'Most crashes',
|
||||||
}),
|
}),
|
||||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
extra: getComparisonValueFormatter({
|
||||||
defaultMessage: 'Coming Soon',
|
currentPeriodValue: currentPeriod?.mostCrashes.value,
|
||||||
|
previousPeriodValue: previousPeriod?.mostCrashes.value,
|
||||||
}),
|
}),
|
||||||
icon: getIcon('bug'),
|
icon: getIcon('bug'),
|
||||||
value: NOT_AVAILABLE_LABEL,
|
value: currentPeriod?.mostCrashes.location ?? NOT_AVAILABLE_LABEL,
|
||||||
valueFormatter: (value) => `${value}`,
|
valueFormatter: (value) => `${value}`,
|
||||||
trend: [],
|
trend: currentPeriod?.mostCrashes.timeseries,
|
||||||
trendShape: MetricTrendShape.Area,
|
trendShape: MetricTrendShape.Area,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTheme } from '@kbn/observability-shared-plugin/public';
|
import { useTheme } from '@kbn/observability-shared-plugin/public';
|
||||||
|
import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n';
|
||||||
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
|
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
|
||||||
import {
|
import {
|
||||||
useFetcher,
|
useFetcher,
|
||||||
|
@ -104,17 +105,16 @@ export function MobileStats({
|
||||||
|
|
||||||
const metrics: MetricDatum[] = [
|
const metrics: MetricDatum[] = [
|
||||||
{
|
{
|
||||||
color: euiTheme.eui.euiColorDisabled,
|
color: euiTheme.eui.euiColorLightestShade,
|
||||||
title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', {
|
title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', {
|
||||||
defaultMessage: 'Crash Rate (Crash per minute)',
|
defaultMessage: 'Crash rate',
|
||||||
}),
|
|
||||||
subtitle: i18n.translate('xpack.apm.mobile.coming.soon', {
|
|
||||||
defaultMessage: 'Coming Soon',
|
|
||||||
}),
|
}),
|
||||||
icon: getIcon('bug'),
|
icon: getIcon('bug'),
|
||||||
value: 'N/A',
|
value: data?.currentPeriod?.crashRate?.value ?? NOT_AVAILABLE_LABEL,
|
||||||
valueFormatter: (value: number) => valueFormatter(value),
|
valueFormatter: (value: number) =>
|
||||||
trend: [],
|
valueFormatter(Number((value * 100).toPrecision(2)), '%'),
|
||||||
|
trend: data?.currentPeriod?.crashRate?.timeseries,
|
||||||
|
extra: getComparisonValueFormatter(data?.previousPeriod.crashRate?.value),
|
||||||
trendShape: MetricTrendShape.Area,
|
trendShape: MetricTrendShape.Area,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -137,7 +137,7 @@ export function MobileStats({
|
||||||
defaultMessage: 'Sessions',
|
defaultMessage: 'Sessions',
|
||||||
}),
|
}),
|
||||||
icon: getIcon('timeslider'),
|
icon: getIcon('timeslider'),
|
||||||
value: data?.currentPeriod?.sessions?.value ?? NaN,
|
value: data?.currentPeriod?.sessions?.value ?? NOT_AVAILABLE_LABEL,
|
||||||
valueFormatter: (value: number) => valueFormatter(value),
|
valueFormatter: (value: number) => valueFormatter(value),
|
||||||
trend: data?.currentPeriod?.sessions?.timeseries,
|
trend: data?.currentPeriod?.sessions?.timeseries,
|
||||||
extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value),
|
extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value),
|
||||||
|
@ -149,7 +149,7 @@ export function MobileStats({
|
||||||
defaultMessage: 'HTTP requests',
|
defaultMessage: 'HTTP requests',
|
||||||
}),
|
}),
|
||||||
icon: getIcon('kubernetesPod'),
|
icon: getIcon('kubernetesPod'),
|
||||||
value: data?.currentPeriod?.requests?.value ?? NaN,
|
value: data?.currentPeriod?.requests?.value ?? NOT_AVAILABLE_LABEL,
|
||||||
extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value),
|
extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value),
|
||||||
valueFormatter: (value: number) => valueFormatter(value),
|
valueFormatter: (value: number) => valueFormatter(value),
|
||||||
trend: data?.currentPeriod?.requests?.timeseries,
|
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 { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
|
||||||
import { getSessionsByLocation } from './get_mobile_sessions_by_location';
|
import { getSessionsByLocation } from './get_mobile_sessions_by_location';
|
||||||
import { getHttpRequestsByLocation } from './get_mobile_http_requests_by_location';
|
import { getHttpRequestsByLocation } from './get_mobile_http_requests_by_location';
|
||||||
|
import { getCrashesByLocation } from './get_mobile_crashes_by_location';
|
||||||
import { Maybe } from '../../../typings/common';
|
import { Maybe } from '../../../typings/common';
|
||||||
|
|
||||||
export type Timeseries = Array<{ x: number; y: number }>;
|
export type Timeseries = Array<{ x: number; y: number }>;
|
||||||
|
@ -24,6 +25,11 @@ interface LocationStats {
|
||||||
value: Maybe<number>;
|
value: Maybe<number>;
|
||||||
timeseries: Timeseries;
|
timeseries: Timeseries;
|
||||||
};
|
};
|
||||||
|
mostCrashes: {
|
||||||
|
location?: string;
|
||||||
|
value: Maybe<number>;
|
||||||
|
timeseries: Timeseries;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MobileLocationStats {
|
export interface MobileLocationStats {
|
||||||
|
@ -63,14 +69,16 @@ async function getMobileLocationStats({
|
||||||
offset,
|
offset,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [mostSessions, mostRequests] = await Promise.all([
|
const [mostSessions, mostRequests, mostCrashes] = await Promise.all([
|
||||||
getSessionsByLocation({ ...commonProps }),
|
getSessionsByLocation({ ...commonProps }),
|
||||||
getHttpRequestsByLocation({ ...commonProps }),
|
getHttpRequestsByLocation({ ...commonProps }),
|
||||||
|
getCrashesByLocation({ ...commonProps }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mostSessions,
|
mostSessions,
|
||||||
mostRequests,
|
mostRequests,
|
||||||
|
mostCrashes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +116,7 @@ export async function getMobileLocationStatsPeriods({
|
||||||
: {
|
: {
|
||||||
mostSessions: { value: null, timeseries: [] },
|
mostSessions: { value: null, timeseries: [] },
|
||||||
mostRequests: { value: null, timeseries: [] },
|
mostRequests: { value: null, timeseries: [] },
|
||||||
|
mostCrashes: { value: null, timeseries: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
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 { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
|
||||||
import { getMobileSessions } from './get_mobile_sessions';
|
import { getMobileSessions } from './get_mobile_sessions';
|
||||||
import { getMobileHttpRequests } from './get_mobile_http_requests';
|
import { getMobileHttpRequests } from './get_mobile_http_requests';
|
||||||
|
import { getMobileCrashRate } from './get_mobile_crash_rate';
|
||||||
import { Maybe } from '../../../typings/common';
|
import { Maybe } from '../../../typings/common';
|
||||||
|
|
||||||
export interface Timeseries {
|
export interface Timeseries {
|
||||||
|
@ -18,6 +19,7 @@ export interface Timeseries {
|
||||||
interface MobileStats {
|
interface MobileStats {
|
||||||
sessions: { timeseries: Timeseries[]; value: Maybe<number> };
|
sessions: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||||
requests: { timeseries: Timeseries[]; value: Maybe<number> };
|
requests: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||||
|
crashRate: { timeseries: Timeseries[]; value: Maybe<number> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MobilePeriodStats {
|
export interface MobilePeriodStats {
|
||||||
|
@ -60,9 +62,10 @@ async function getMobileStats({
|
||||||
offset,
|
offset,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [sessions, httpRequests] = await Promise.all([
|
const [sessions, httpRequests, crashes] = await Promise.all([
|
||||||
getMobileSessions({ ...commonProps }),
|
getMobileSessions({ ...commonProps }),
|
||||||
getMobileHttpRequests({ ...commonProps }),
|
getMobileHttpRequests({ ...commonProps }),
|
||||||
|
getMobileCrashRate({ ...commonProps }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -74,6 +77,18 @@ async function getMobileStats({
|
||||||
value: httpRequests.currentPeriod.value,
|
value: httpRequests.currentPeriod.value,
|
||||||
timeseries: httpRequests.currentPeriod.timeseries as Timeseries[],
|
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 },
|
sessions: { timeseries: [], value: null },
|
||||||
requests: { timeseries: [], value: null },
|
requests: { timeseries: [], value: null },
|
||||||
|
crashRate: { timeseries: [], value: null },
|
||||||
};
|
};
|
||||||
|
|
||||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
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.http.requests.title": "Le plus utilisé dans",
|
||||||
"xpack.apm.mobile.location.metrics.launches": "La plupart des lancements",
|
"xpack.apm.mobile.location.metrics.launches": "La plupart des lancements",
|
||||||
"xpack.apm.mobile.location.metrics.sessions": "La plupart des sessions",
|
"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.http.requests": "Requêtes HTTP",
|
||||||
"xpack.apm.mobile.metrics.load.time": "Temps de chargement de l'application le plus lent",
|
"xpack.apm.mobile.metrics.load.time": "Temps de chargement de l'application le plus lent",
|
||||||
"xpack.apm.mobile.metrics.sessions": "Sessions",
|
"xpack.apm.mobile.metrics.sessions": "Sessions",
|
||||||
|
|
|
@ -8287,7 +8287,6 @@
|
||||||
"xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている",
|
"xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている",
|
||||||
"xpack.apm.mobile.location.metrics.launches": "最も多い起動",
|
"xpack.apm.mobile.location.metrics.launches": "最も多い起動",
|
||||||
"xpack.apm.mobile.location.metrics.sessions": "最も多いセッション",
|
"xpack.apm.mobile.location.metrics.sessions": "最も多いセッション",
|
||||||
"xpack.apm.mobile.metrics.crash.rate": "クラッシュ率(毎分のクラッシュ数)",
|
|
||||||
"xpack.apm.mobile.metrics.http.requests": "HTTPリクエスト",
|
"xpack.apm.mobile.metrics.http.requests": "HTTPリクエスト",
|
||||||
"xpack.apm.mobile.metrics.load.time": "最も遅いアプリ読み込み時間",
|
"xpack.apm.mobile.metrics.load.time": "最も遅いアプリ読み込み時間",
|
||||||
"xpack.apm.mobile.metrics.sessions": "セッション",
|
"xpack.apm.mobile.metrics.sessions": "セッション",
|
||||||
|
|
|
@ -8286,7 +8286,6 @@
|
||||||
"xpack.apm.mobile.location.metrics.http.requests.title": "最常用于",
|
"xpack.apm.mobile.location.metrics.http.requests.title": "最常用于",
|
||||||
"xpack.apm.mobile.location.metrics.launches": "大多数启动",
|
"xpack.apm.mobile.location.metrics.launches": "大多数启动",
|
||||||
"xpack.apm.mobile.location.metrics.sessions": "大多数会话",
|
"xpack.apm.mobile.location.metrics.sessions": "大多数会话",
|
||||||
"xpack.apm.mobile.metrics.crash.rate": "崩溃速率(每分钟崩溃数)",
|
|
||||||
"xpack.apm.mobile.metrics.http.requests": "HTTP 请求",
|
"xpack.apm.mobile.metrics.http.requests": "HTTP 请求",
|
||||||
"xpack.apm.mobile.metrics.load.time": "最慢应用加载时间",
|
"xpack.apm.mobile.metrics.load.time": "最慢应用加载时间",
|
||||||
"xpack.apm.mobile.metrics.sessions": "会话",
|
"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(
|
expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||||
true
|
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;
|
const { location } = response.currentPeriod.mostRequests;
|
||||||
expect(location).to.be('China');
|
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', () => {
|
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.mostSessions.value).to.eql(0);
|
||||||
expect(response.currentPeriod.mostRequests.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(
|
expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||||
true
|
true
|
||||||
|
@ -272,6 +281,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql(
|
expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||||
true
|
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 () => {
|
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.mostSessions.value).to.eql(3);
|
||||||
expect(response.currentPeriod.mostRequests.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 () => {
|
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.mostSessions.value).to.eql(3);
|
||||||
expect(response.currentPeriod.mostRequests.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 { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||||
import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
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 { 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';
|
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||||
|
|
||||||
type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>;
|
type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>;
|
||||||
|
@ -103,7 +103,7 @@ async function generateData({
|
||||||
return [
|
return [
|
||||||
galaxy10
|
galaxy10
|
||||||
.transaction('Start View - View Appearing', 'Android Activity')
|
.transaction('Start View - View Appearing', 'Android Activity')
|
||||||
.errors(galaxy10.crash({ message: 'error' }).timestamp(timestamp))
|
.errors(galaxy10.crash({ message: 'error C' }).timestamp(timestamp))
|
||||||
.timestamp(timestamp)
|
.timestamp(timestamp)
|
||||||
.duration(500)
|
.duration(500)
|
||||||
.success()
|
.success()
|
||||||
|
@ -120,7 +120,11 @@ async function generateData({
|
||||||
),
|
),
|
||||||
huaweiP2
|
huaweiP2
|
||||||
.transaction('Start View - View Appearing', 'huaweiP2 Activity')
|
.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)
|
.timestamp(timestamp)
|
||||||
.duration(20)
|
.duration(20)
|
||||||
.success(),
|
.success(),
|
||||||
|
@ -211,6 +215,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
const timeseriesTotal = sumBy(timeseries, 'y');
|
const timeseriesTotal = sumBy(timeseries, 'y');
|
||||||
expect(value).to.be(timeseriesTotal);
|
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', () => {
|
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.sessions.value).to.eql(0);
|
||||||
expect(response.currentPeriod.requests.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(
|
expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql(
|
||||||
true
|
true
|
||||||
|
@ -230,6 +244,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql(
|
expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql(
|
||||||
true
|
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 () => {
|
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.sessions.value).to.eql(3);
|
||||||
expect(response.currentPeriod.requests.value).to.eql(0);
|
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 () => {
|
it('returns the correct values when multiple filters are applied', async () => {
|
||||||
|
@ -248,9 +266,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
serviceName: 'synth-android',
|
serviceName: 'synth-android',
|
||||||
kuery: `service.version:"1.2" and service.environment: "production"`,
|
kuery: `service.version:"1.2" and service.environment: "production"`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.currentPeriod.sessions.value).to.eql(3);
|
expect(response.currentPeriod.sessions.value).to.eql(3);
|
||||||
expect(response.currentPeriod.requests.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