From 820cfc02cfaf830a0159a29bde1e5d9e0766a360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:14:10 +0100 Subject: [PATCH] [APM] Mobile most launches (#168925) ## Summary Enabling the "Most launches" Mobile dashboard panel which shows an aggregation of log events that contain the attribute `labels.lifecycle_state` set to either `created` (for Android) or `active` (for iOS). ![Screenshot 2023-10-27 at 09 37 08 copy](https://github.com/elastic/kibana/assets/56847527/911c769c-1456-4f38-bf07-5e71b6ce5ae5) ### Checklist Delete any items that are not applicable to this PR. - [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 ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Katerina --- .../src/lib/apm/apm_fields.ts | 3 + .../src/lib/apm/event.ts | 30 +++++ .../src/lib/apm/mobile_device.ts | 7 +- .../src/lib/apm/transaction.ts | 29 ++++- .../get_routing_transform.ts | 5 + .../__snapshots__/es_fields.test.ts.snap | 6 + x-pack/plugins/apm/common/es_fields/apm.ts | 1 + .../service_overview/stats/location_stats.tsx | 11 +- .../get_request_base.ts | 1 - .../create_apm_event_client/index.ts | 48 +++++++- .../mobile/get_mobile_crashes_by_location.ts | 6 +- .../get_mobile_http_requests_by_location.ts | 8 +- .../mobile/get_mobile_launches_by_location.ts | 112 ++++++++++++++++++ .../mobile/get_mobile_location_stats.ts | 20 +++- .../apm/typings/es_schemas/raw/event_raw.ts | 25 ++++ .../apm/typings/es_schemas/ui/event.ts | 13 ++ .../mobile/mobile_location_stats.spec.ts | 29 ++++- 17 files changed, 330 insertions(+), 24 deletions(-) create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/apm/event.ts create mode 100644 x-pack/plugins/apm/server/routes/mobile/get_mobile_launches_by_location.ts create mode 100644 x-pack/plugins/apm/typings/es_schemas/raw/event_raw.ts create mode 100644 x-pack/plugins/apm/typings/es_schemas/ui/event.ts diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts index 911271f584152..7c3ab2c2b1e3a 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts @@ -38,6 +38,7 @@ export type ApmUserAgentFields = Partial<{ export interface ApmException { message: string; } + export interface Observer { type: string; version: string; @@ -94,6 +95,7 @@ export type ApmFields = Fields<{ 'error.type': string; 'event.ingested': number; 'event.name': string; + 'event.action': string; 'event.outcome': string; 'event.outcome_numeric': | number @@ -121,6 +123,7 @@ export type ApmFields = Fields<{ 'kubernetes.pod.uid': string; 'labels.name': string; 'labels.telemetry_auto_version': string; + 'labels.lifecycle_state': string; 'metricset.name': string; 'network.carrier.icc': string; 'network.carrier.mcc': string; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/event.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/event.ts new file mode 100644 index 0000000000000..8efd5968a349e --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/event.ts @@ -0,0 +1,30 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ApmFields } from './apm_fields'; +import { Serializable } from '../serializable'; + +export class Event extends Serializable { + constructor(fields: ApmFields) { + super({ + ...fields, + }); + } + + lifecycle(state: string): this { + this.fields['event.action'] = 'lifecycle'; + this.fields['labels.lifecycle_state'] = state; + return this; + } + + override timestamp(timestamp: number) { + const ret = super.timestamp(timestamp); + this.fields['timestamp.us'] = timestamp * 1000; + return ret; + } +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts index 9a904d0d94dbb..b0ea0aea4663e 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts @@ -9,7 +9,8 @@ import { Entity } from '../entity'; import { Span } from './span'; import { Transaction } from './transaction'; -import { ApmFields, SpanParams, GeoLocation, ApmApplicationMetricFields } from './apm_fields'; +import { Event } from './event'; +import { ApmApplicationMetricFields, ApmFields, GeoLocation, SpanParams } from './apm_fields'; import { generateLongId } from '../utils/generate_id'; import { Metricset } from './metricset'; import { ApmError } from './apm_error'; @@ -143,6 +144,10 @@ export class MobileDevice extends Entity { return this; } + event(): Event { + return new Event({ ...this.fields }); + } + transaction( ...options: | [{ transactionName: string; frameworkName?: string; frameworkVersion?: string }] diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts index 55883957c6990..1b15ac17a4a3d 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts @@ -7,6 +7,7 @@ */ import { ApmError } from './apm_error'; +import { Event } from './event'; import { BaseSpan } from './base_span'; import { generateShortId } from '../utils/generate_id'; import { ApmFields } from './apm_fields'; @@ -15,6 +16,7 @@ import { getBreakdownMetrics } from './processors/get_breakdown_metrics'; export class Transaction extends BaseSpan { private _sampled: boolean = true; private readonly _errors: ApmError[] = []; + private readonly _events: Event[] = []; constructor(fields: ApmFields) { super({ @@ -35,6 +37,27 @@ export class Transaction extends BaseSpan { error.fields['transaction.sampled'] = this.fields['transaction.sampled']; }); + this._events.forEach((event) => { + event.fields['trace.id'] = this.fields['trace.id']; + event.fields['transaction.id'] = this.fields['transaction.id']; + event.fields['transaction.type'] = this.fields['transaction.type']; + event.fields['transaction.sampled'] = this.fields['transaction.sampled']; + }); + + return this; + } + + events(...events: Event[]) { + events.forEach((event) => { + event.fields['trace.id'] = this.fields['trace.id']; + event.fields['transaction.id'] = this.fields['transaction.id']; + event.fields['transaction.name'] = this.fields['transaction.name']; + event.fields['transaction.type'] = this.fields['transaction.type']; + event.fields['transaction.sampled'] = this.fields['transaction.sampled']; + }); + + this._events.push(...events); + return this; } @@ -62,6 +85,9 @@ export class Transaction extends BaseSpan { this._errors.forEach((error) => { error.fields['transaction.sampled'] = sampled; }); + this._events.forEach((event) => { + event.fields['transaction.sampled'] = sampled; + }); return this; } @@ -69,6 +95,7 @@ export class Transaction extends BaseSpan { const [transaction, ...spans] = super.serialize(); const errors = this._errors.flatMap((error) => error.serialize()); + const logEvents = this._events.flatMap((event) => event.serialize()); const directChildren = this.getChildren().map((child) => child.fields); @@ -80,6 +107,6 @@ export class Transaction extends BaseSpan { events.push(...spans); } - return events.concat(errors).concat(breakdownMetrics); + return events.concat(errors).concat(breakdownMetrics).concat(logEvents); } } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_routing_transform.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_routing_transform.ts index a5428b1a084e9..4ca2498c610b1 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_routing_transform.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_routing_transform.ts @@ -46,6 +46,11 @@ export function getRoutingTransform() { index = `metrics-apm.internal-${namespace}`; } break; + default: + if (document['event.action'] != null) { + index = `logs-apm.app-${namespace}`; + } + break; } if (!index) { diff --git a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap index 9386fb27f88d2..0fa795b696455 100644 --- a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap +++ b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap @@ -158,6 +158,8 @@ exports[`Error KUBERNETES_REPLICASET_NAME 1`] = `undefined`; exports[`Error LABEL_GC 1`] = `undefined`; +exports[`Error LABEL_LIFECYCLE_STATE 1`] = `undefined`; + exports[`Error LABEL_NAME 1`] = `undefined`; exports[`Error LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; @@ -485,6 +487,8 @@ exports[`Span KUBERNETES_REPLICASET_NAME 1`] = `undefined`; exports[`Span LABEL_GC 1`] = `undefined`; +exports[`Span LABEL_LIFECYCLE_STATE 1`] = `undefined`; + exports[`Span LABEL_NAME 1`] = `undefined`; exports[`Span LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; @@ -822,6 +826,8 @@ exports[`Transaction KUBERNETES_REPLICASET_NAME 1`] = `undefined`; exports[`Transaction LABEL_GC 1`] = `undefined`; +exports[`Transaction LABEL_LIFECYCLE_STATE 1`] = `undefined`; + exports[`Transaction LABEL_NAME 1`] = `undefined`; exports[`Transaction LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/es_fields/apm.ts b/x-pack/plugins/apm/common/es_fields/apm.ts index 8c1c0e3670baf..a6cef6ce73e11 100644 --- a/x-pack/plugins/apm/common/es_fields/apm.ts +++ b/x-pack/plugins/apm/common/es_fields/apm.ts @@ -140,6 +140,7 @@ export const LABEL_NAME = 'labels.name'; export const LABEL_GC = 'labels.gc'; export const LABEL_TYPE = 'labels.type'; export const LABEL_TELEMETRY_AUTO_VERSION = 'labels.telemetry_auto_version'; +export const LABEL_LIFECYCLE_STATE = 'labels.lifecycle_state'; export const HOST = 'host'; export const HOST_HOSTNAME = 'host.hostname'; // Do not use. Please use `HOST_NAME` instead. diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx index 22e95e54817dd..24bbd7709d8d7 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx @@ -163,17 +163,18 @@ export function MobileLocationStats({ trendShape: MetricTrendShape.Area, }, { - color: euiTheme.eui.euiColorDisabled, + color: euiTheme.eui.euiColorLightestShade, title: i18n.translate('xpack.apm.mobile.location.metrics.launches', { defaultMessage: 'Most launches', }), - subtitle: i18n.translate('xpack.apm.mobile.coming.soon', { - defaultMessage: 'Coming Soon', + extra: getComparisonValueFormatter({ + currentPeriodValue: currentPeriod?.mostLaunches.value, + previousPeriodValue: previousPeriod?.mostLaunches.value, }), icon: getIcon('launch'), - value: NOT_AVAILABLE_LABEL, + value: currentPeriod?.mostLaunches.location ?? NOT_AVAILABLE_LABEL, valueFormatter: (value) => `${value}`, - trend: [], + trend: currentPeriod?.mostLaunches.timeseries, trendShape: MetricTrendShape.Area, }, ]; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts index 1c2c3ad296101..1046a2ad47cfe 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts @@ -10,7 +10,6 @@ import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { uniq } from 'lodash'; import { ApmDataSource } from '../../../../../common/data_source'; -import {} from '../../../../../common/document_type'; import { PROCESSOR_EVENT } from '../../../../../common/es_fields/apm'; import { getConfigForDocumentType, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index a9a5e02b9eae1..fcac29b1d57d5 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -11,8 +11,8 @@ import type { FieldCapsResponse, MsearchMultisearchBody, MsearchMultisearchHeader, - TermsEnumResponse, TermsEnumRequest, + TermsEnumResponse, } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient, KibanaRequest } from '@kbn/core/server'; import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; @@ -26,6 +26,7 @@ import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { Metric } from '../../../../../typings/es_schemas/ui/metric'; import { Span } from '../../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; +import { Event } from '../../../../../typings/es_schemas/ui/event'; import { withApmSpan } from '../../../../utils/with_apm_span'; import { callAsyncWithDebug, @@ -46,6 +47,13 @@ export type APMEventESSearchRequest = Omit & { }; }; +export type APMLogEventESSearchRequest = Omit & { + body: { + size: number; + track_total_hits: boolean | number; + }; +}; + type APMEventWrapper = Omit & { apm: { events: ProcessorEvent[] }; }; @@ -63,6 +71,9 @@ type TypeOfProcessorEvent = { metric: Metric; }[T]; +type TypedLogEventSearchResponse = + InferSearchResponseOf; + type TypedSearchResponse = InferSearchResponseOf< TypeOfProcessorEvent< @@ -196,6 +207,41 @@ export class APMEventClient { }); } + async logEventSearch( + operationName: string, + params: TParams + ): Promise> { + // Reusing indices configured for errors since both events and errors are stored as logs. + const index = processorEventsToIndex([ProcessorEvent.error], this.indices); + + const searchParams = { + ...omit(params, 'body'), + index, + body: { + ...params.body, + query: { + bool: { + must: compact([params.body.query]), + }, + }, + }, + ...(this.includeFrozen ? { ignore_throttled: false } : {}), + ignore_unavailable: true, + preference: 'any', + expand_wildcards: ['open' as const, 'hidden' as const], + }; + + return this.callAsyncWithDebug({ + cb: (opts) => + this.esClient.search(searchParams, opts) as unknown as Promise<{ + body: TypedLogEventSearchResponse; + }>, + operationName, + params: searchParams, + requestType: 'search', + }); + } + async msearch( operationName: string, ...allParams: TParams[] diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts index 4117e6a220bd4..855ae8fb35d05 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts @@ -11,7 +11,7 @@ import { rangeQuery, termQuery, } from '@kbn/observability-plugin/server'; -import { SERVICE_NAME, ERROR_TYPE } from '../../../common/es_fields/apm'; +import { ERROR_TYPE, SERVICE_NAME } 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'; @@ -101,9 +101,7 @@ export async function getCrashesByLocation({ timeseries: response.aggregations?.timeseries?.buckets.map((bucket) => ({ x: bucket.key, - y: - response.aggregations?.crashes?.crashesByLocation?.buckets[0] - ?.doc_count ?? 0, + y: bucket?.crashes?.crashesByLocation?.buckets[0]?.doc_count ?? 0, })) ?? [], }; } diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_http_requests_by_location.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_http_requests_by_location.ts index 568e370a92adf..6141376491243 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_http_requests_by_location.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_http_requests_by_location.ts @@ -6,15 +6,15 @@ */ import { - termQuery, kqlQuery, rangeQuery, + termQuery, } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { SERVICE_NAME, - SPAN_TYPE, SPAN_SUBTYPE, + SPAN_TYPE, } from '../../../common/es_fields/apm'; import { environmentQuery } from '../../../common/utils/environment_query'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; @@ -111,9 +111,7 @@ export async function getHttpRequestsByLocation({ timeseries: response.aggregations?.timeseries?.buckets.map((bucket) => ({ x: bucket.key, - y: - response.aggregations?.requests?.requestsByLocation?.buckets[0] - ?.doc_count ?? 0, + y: bucket?.requests?.requestsByLocation?.buckets[0]?.doc_count ?? 0, })) ?? [], }; } diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_launches_by_location.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_launches_by_location.ts new file mode 100644 index 0000000000000..5668d1befa31a --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_launches_by_location.ts @@ -0,0 +1,112 @@ +/* + * 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 { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + LABEL_LIFECYCLE_STATE, + SERVICE_NAME, +} 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 getLaunchesByLocation({ + 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 = { + launches: { + filter: { + terms: { [LABEL_LIFECYCLE_STATE]: ['created', 'active'] }, + }, + aggs: { + byLocation: { + terms: { + field: locationField, + }, + }, + }, + }, + }; + + const response = await apmEventClient.logEventSearch( + 'get_mobile_location_launches', + { + 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?.launches?.byLocation?.buckets[0] + ?.key as string, + value: + response.aggregations?.launches?.byLocation?.buckets[0]?.doc_count ?? 0, + timeseries: + response.aggregations?.timeseries?.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.launches?.byLocation?.buckets[0]?.doc_count ?? 0, + })) ?? [], + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts index ccc777e3cfe31..985c92ebd43ea 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts @@ -10,6 +10,7 @@ import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_ev 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 { getLaunchesByLocation } from './get_mobile_launches_by_location'; import { Maybe } from '../../../typings/common'; export type Timeseries = Array<{ x: number; y: number }>; @@ -30,6 +31,11 @@ interface LocationStats { value: Maybe; timeseries: Timeseries; }; + mostLaunches: { + location?: string; + value: Maybe; + timeseries: Timeseries; + }; } export interface MobileLocationStats { @@ -69,16 +75,19 @@ async function getMobileLocationStats({ offset, }; - const [mostSessions, mostRequests, mostCrashes] = await Promise.all([ - getSessionsByLocation({ ...commonProps }), - getHttpRequestsByLocation({ ...commonProps }), - getCrashesByLocation({ ...commonProps }), - ]); + const [mostSessions, mostRequests, mostCrashes, mostLaunches] = + await Promise.all([ + getSessionsByLocation({ ...commonProps }), + getHttpRequestsByLocation({ ...commonProps }), + getCrashesByLocation({ ...commonProps }), + getLaunchesByLocation({ ...commonProps }), + ]); return { mostSessions, mostRequests, mostCrashes, + mostLaunches, }; } @@ -117,6 +126,7 @@ export async function getMobileLocationStatsPeriods({ mostSessions: { value: null, timeseries: [] }, mostRequests: { value: null, timeseries: [] }, mostCrashes: { value: null, timeseries: [] }, + mostLaunches: { value: null, timeseries: [] }, }; const [currentPeriod, previousPeriod] = await Promise.all([ diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/event_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/event_raw.ts new file mode 100644 index 0000000000000..31a1952cdc03d --- /dev/null +++ b/x-pack/plugins/apm/typings/es_schemas/raw/event_raw.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APMBaseDoc } from './apm_base_doc'; +import { TimestampUs } from './fields/timestamp_us'; + +export interface EventRaw extends APMBaseDoc { + timestamp: TimestampUs; + transaction?: { + id: string; + sampled?: boolean; + type: string; + }; + log: { + message?: string; + }; + event: { + action: string; + category: string; + }; +} diff --git a/x-pack/plugins/apm/typings/es_schemas/ui/event.ts b/x-pack/plugins/apm/typings/es_schemas/ui/event.ts new file mode 100644 index 0000000000000..8d9fccea1c8bf --- /dev/null +++ b/x-pack/plugins/apm/typings/es_schemas/ui/event.ts @@ -0,0 +1,13 @@ +/* + * 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 { EventRaw } from '../raw/event_raw'; +import { Agent } from './fields/agent'; + +export interface Event extends EventRaw { + agent: Agent; +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts index 94193f2946ece..eddfb143b2c8b 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts @@ -130,7 +130,7 @@ async function generateData({ carrierMCC: '440', }); - return await synthtraceEsClient.index([ + await synthtraceEsClient.index([ timerange(start, end) .interval('5m') .rate(1) @@ -142,6 +142,7 @@ async function generateData({ galaxy10 .transaction('Start View - View Appearing', 'Android Activity') .errors(galaxy10.crash({ message: 'error' }).timestamp(timestamp)) + .events(galaxy10.event().lifecycle('created').timestamp(timestamp)) .timestamp(timestamp) .duration(500) .success() @@ -159,12 +160,14 @@ async function generateData({ galaxy7 .transaction('Start View - View Appearing', 'Android Activity') .errors(galaxy7.crash({ message: 'error' }).timestamp(timestamp)) + .events(galaxy7.event().lifecycle('created').timestamp(timestamp)) .timestamp(timestamp) .duration(20) .success(), huaweiP2 .transaction('Start View - View Appearing', 'huaweiP2 Activity') .errors(huaweiP2.crash({ message: 'error' }).timestamp(timestamp)) + .events(huaweiP2.event().lifecycle('created').timestamp(timestamp)) .timestamp(timestamp) .duration(20) .success(), @@ -222,6 +225,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); }); }); @@ -261,6 +267,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { location } = response.currentPeriod.mostCrashes; expect(location).to.be('China'); }); + + it('returns location for most launches', () => { + const { location } = response.currentPeriod.mostLaunches; + expect(location).to.be('China'); + }); }); describe('when filters are applied', () => { @@ -274,6 +285,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.mostLaunches.value).to.eql(0); expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -284,6 +296,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostLaunches.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); it('returns the correct values when single filter is applied', async () => { @@ -293,9 +308,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { kuery: `service.version:"1.1"`, }); + expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); expect(response.currentPeriod.mostCrashes.value).to.eql(3); + expect(response.currentPeriod.mostLaunches.value).to.eql(3); }); it('returns the correct values when multiple filters are applied', async () => { @@ -304,9 +325,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { kuery: `service.version:"1.1" and service.environment: "production"`, }); + expect(response.currentPeriod.mostSessions.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostCrashes.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostRequests.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostLaunches.timeseries[0].y).to.eql(1); + expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); expect(response.currentPeriod.mostCrashes.value).to.eql(3); + expect(response.currentPeriod.mostLaunches.value).to.eql(3); }); }); });