diff --git a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 04b759ac007d9..a7c61d1c79da4 100644 --- a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -10,6 +10,8 @@ exports[`Error ERROR_GROUP_ID 1`] = `"grouping key"`; exports[`Error ERROR_LOG_MESSAGE 1`] = `undefined`; +exports[`Error ERROR_PAGE_URL 1`] = `undefined`; + exports[`Error HTTP_REQUEST_METHOD 1`] = `undefined`; exports[`Error METRIC_JAVA_HEAP_MEMORY_COMMITTED 1`] = `undefined`; @@ -72,6 +74,8 @@ exports[`Error TRANSACTION_ID 1`] = `"transaction id"`; exports[`Error TRANSACTION_NAME 1`] = `undefined`; +exports[`Error TRANSACTION_PAGE_URL 1`] = `undefined`; + exports[`Error TRANSACTION_RESULT 1`] = `undefined`; exports[`Error TRANSACTION_SAMPLED 1`] = `undefined`; @@ -92,6 +96,8 @@ exports[`Span ERROR_GROUP_ID 1`] = `undefined`; exports[`Span ERROR_LOG_MESSAGE 1`] = `undefined`; +exports[`Span ERROR_PAGE_URL 1`] = `undefined`; + exports[`Span HTTP_REQUEST_METHOD 1`] = `undefined`; exports[`Span METRIC_JAVA_HEAP_MEMORY_COMMITTED 1`] = `undefined`; @@ -154,6 +160,8 @@ exports[`Span TRANSACTION_ID 1`] = `"transaction id"`; exports[`Span TRANSACTION_NAME 1`] = `undefined`; +exports[`Span TRANSACTION_PAGE_URL 1`] = `undefined`; + exports[`Span TRANSACTION_RESULT 1`] = `undefined`; exports[`Span TRANSACTION_SAMPLED 1`] = `undefined`; @@ -174,6 +182,8 @@ exports[`Transaction ERROR_GROUP_ID 1`] = `undefined`; exports[`Transaction ERROR_LOG_MESSAGE 1`] = `undefined`; +exports[`Transaction ERROR_PAGE_URL 1`] = `undefined`; + exports[`Transaction HTTP_REQUEST_METHOD 1`] = `"GET"`; exports[`Transaction METRIC_JAVA_HEAP_MEMORY_COMMITTED 1`] = `undefined`; @@ -236,6 +246,8 @@ exports[`Transaction TRANSACTION_ID 1`] = `"transaction id"`; exports[`Transaction TRANSACTION_NAME 1`] = `"transaction name"`; +exports[`Transaction TRANSACTION_PAGE_URL 1`] = `undefined`; + exports[`Transaction TRANSACTION_RESULT 1`] = `"transaction result"`; exports[`Transaction TRANSACTION_SAMPLED 1`] = `true`; diff --git a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts index 1dbfa0592ec8e..a5d057817893c 100644 --- a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts @@ -21,6 +21,7 @@ export const TRANSACTION_NAME = 'transaction.name'; export const TRANSACTION_ID = 'transaction.id'; export const TRANSACTION_SAMPLED = 'transaction.sampled'; export const TRANSACTION_BREAKDOWN_COUNT = 'transaction.breakdown.count'; +export const TRANSACTION_PAGE_URL = 'transaction.page.url'; export const TRACE_ID = 'trace.id'; @@ -40,6 +41,7 @@ export const ERROR_CULPRIT = 'error.culprit'; export const ERROR_LOG_MESSAGE = 'error.log.message'; export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array +export const ERROR_PAGE_URL = 'error.page.url'; // METRICS export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx index 5a2bb4d2cb11a..4d4991f161f6e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; +import { IStickyProperty } from '../../../shared/StickyProperties'; import { StickyErrorProperties } from './StickyErrorProperties'; +import { + ERROR_PAGE_URL, + URL_FULL +} from '../../../../../common/elasticsearch_fieldnames'; describe('StickyErrorProperties', () => { it('should render StickyProperties', () => { @@ -27,6 +32,7 @@ describe('StickyErrorProperties', () => { const error = { '@timestamp': 'myTimestamp', + agent: { name: 'nodejs' }, http: { request: { method: 'GET' } }, url: { full: 'myUrl' }, service: { name: 'myService' }, @@ -41,4 +47,71 @@ describe('StickyErrorProperties', () => { expect(wrapper).toMatchSnapshot(); }); + + it('url.full', () => { + const error = { + agent: { name: 'nodejs' }, + url: { full: 'myFullUrl' } + } as APMError; + + const wrapper = shallow( + + ); + const urlValue = getValueByFieldName(wrapper, URL_FULL); + expect(urlValue).toBe('myFullUrl'); + }); + + it('error.page.url', () => { + const error = { + agent: { name: 'rum-js' }, + error: { page: { url: 'myPageUrl' } } + } as APMError; + + const wrapper = shallow( + + ); + const urlValue = getValueByFieldName(wrapper, ERROR_PAGE_URL); + expect(urlValue).toBe('myPageUrl'); + }); + + describe('error.exception.handled', () => { + it('should should render "true"', () => { + const error = { + agent: { name: 'nodejs' }, + error: { exception: [{ handled: true }] } + } as APMError; + const wrapper = shallow( + + ); + const value = getValueByFieldName(wrapper, 'error.exception.handled'); + expect(value).toBe('true'); + }); + + it('should should render "false"', () => { + const error = { + agent: { name: 'nodejs' }, + error: { exception: [{ handled: false }] } + } as APMError; + const wrapper = shallow( + + ); + const value = getValueByFieldName(wrapper, 'error.exception.handled'); + expect(value).toBe('false'); + }); + + it('should should render "N/A"', () => { + const error = { agent: { name: 'nodejs' } } as APMError; + const wrapper = shallow( + + ); + const value = getValueByFieldName(wrapper, 'error.exception.handled'); + expect(value).toBe('N/A'); + }); + }); }); + +function getValueByFieldName(wrapper: ShallowWrapper, fieldName: string) { + const stickyProps = wrapper.prop('stickyProperties') as IStickyProperty[]; + const prop = stickyProps.find(p => p.fieldName === fieldName); + return prop && prop.val; +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx index f523b17afc857..bdf122ca52c49 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { isBoolean } from 'lodash'; import React, { Fragment } from 'react'; import { idx } from '@kbn/elastic-idx'; import { @@ -12,13 +13,15 @@ import { HTTP_REQUEST_METHOD, TRANSACTION_ID, URL_FULL, - USER_ID + USER_ID, + ERROR_PAGE_URL } from '../../../../../common/elasticsearch_fieldnames'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { StickyProperties } from '../../../shared/StickyProperties'; import { TransactionLink } from '../../../shared/Links/apm/TransactionLink'; +import { isRumAgentName } from '../../../../../common/agent_name'; interface Props { error: APMError; @@ -47,6 +50,19 @@ function TransactionLinkWrapper({ } export function StickyErrorProperties({ error, transaction }: Props) { + const isHandled = idx(error, _ => _.error.exception[0].handled); + const isRumAgent = isRumAgentName(error.agent.name); + + const { urlFieldName, urlValue } = isRumAgent + ? { + urlFieldName: ERROR_PAGE_URL, + urlValue: idx(error, _ => _.error.page.url) + } + : { + urlFieldName: URL_FULL, + urlValue: idx(error, _ => _.url.full) + }; + const stickyProperties = [ { fieldName: '@timestamp', @@ -57,12 +73,9 @@ export function StickyErrorProperties({ error, transaction }: Props) { width: '50%' }, { - fieldName: URL_FULL, + fieldName: urlFieldName, label: 'URL', - val: - idx(error, _ => _.context.page.url) || - idx(error, _ => _.url.full) || - NOT_AVAILABLE_LABEL, + val: urlValue || NOT_AVAILABLE_LABEL, truncated: true, width: '50%' }, @@ -79,9 +92,7 @@ export function StickyErrorProperties({ error, transaction }: Props) { label: i18n.translate('xpack.apm.errorGroupDetails.handledLabel', { defaultMessage: 'Handled' }), - val: - String(idx(error, _ => _.error.exception[0].handled)) || - NOT_AVAILABLE_LABEL, + val: isBoolean(isHandled) ? String(isHandled) : NOT_AVAILABLE_LABEL, width: '25%' }, { diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx index e533d7c026bbf..a11086ef23734 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx @@ -12,7 +12,8 @@ import { TRANSACTION_DURATION, TRANSACTION_RESULT, URL_FULL, - USER_ID + USER_ID, + TRANSACTION_PAGE_URL } from '../../../../../common/elasticsearch_fieldnames'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; @@ -22,6 +23,7 @@ import { StickyProperties } from '../../../shared/StickyProperties'; import { ErrorCountBadge } from './ErrorCountBadge'; +import { isRumAgentName } from '../../../../../common/agent_name'; interface Props { transaction: Transaction; @@ -35,10 +37,18 @@ export function StickyTransactionProperties({ errorCount }: Props) { const timestamp = transaction['@timestamp']; - const url = - idx(transaction, _ => _.context.page.url) || - idx(transaction, _ => _.url.full) || - NOT_AVAILABLE_LABEL; + + const isRumAgent = isRumAgentName(transaction.agent.name); + const { urlFieldName, urlValue } = isRumAgent + ? { + urlFieldName: TRANSACTION_PAGE_URL, + urlValue: idx(transaction, _ => _.transaction.page.url) + } + : { + urlFieldName: URL_FULL, + urlValue: idx(transaction, _ => _.url.full) + }; + const duration = transaction.transaction.duration.us; const noErrorsText = i18n.translate( @@ -59,9 +69,9 @@ export function StickyTransactionProperties({ width: '50%' }, { - fieldName: URL_FULL, + fieldName: urlFieldName, label: 'URL', - val: url, + val: urlValue || NOT_AVAILABLE_LABEL, truncated: true, width: '50%' }, diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts index fadd1829112ce..db2714d3d9670 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts @@ -6,10 +6,10 @@ import { APMBaseDoc } from './APMBaseDoc'; import { Container } from './fields/Container'; -import { Context } from './fields/Context'; import { Host } from './fields/Host'; import { Http } from './fields/Http'; import { Kubernetes } from './fields/Kubernetes'; +import { Page } from './fields/Page'; import { Process } from './fields/Process'; import { Service } from './fields/Service'; import { IStackframe } from './fields/Stackframe'; @@ -49,6 +49,7 @@ export interface ErrorRaw extends APMBaseDoc { grouping_key: string; // either exception or log are given exception?: Exception[]; + page?: Page; // special property for RUM: shared by error and transaction log?: Log; [key: string]: unknown; }; @@ -56,7 +57,6 @@ export interface ErrorRaw extends APMBaseDoc { // Shared by errors and transactions container?: Container; - context?: Context; host?: Host; http?: Http; kubernetes?: Kubernetes; diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts index 1333bacb040ab..25eee38036784 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts @@ -6,10 +6,10 @@ import { APMBaseDoc } from './APMBaseDoc'; import { Container } from './fields/Container'; -import { Context } from './fields/Context'; import { Host } from './fields/Host'; import { Http } from './fields/Http'; import { Kubernetes } from './fields/Kubernetes'; +import { Page } from './fields/Page'; import { Process } from './fields/Process'; import { Service } from './fields/Service'; import { Url } from './fields/Url'; @@ -35,6 +35,7 @@ export interface TransactionRaw extends APMBaseDoc { [key: string]: unknown; }; name?: string; + page?: Page; // special property for RUM: shared by error and transaction result?: string; sampled: boolean; span_count?: { @@ -48,7 +49,6 @@ export interface TransactionRaw extends APMBaseDoc { // Shared by errors and transactions container?: Container; - context?: Context; host?: Host; http?: Http; kubernetes?: Kubernetes; diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Context.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Page.ts similarity index 72% rename from x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Context.ts rename to x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Page.ts index eed626865868a..02498dd731de5 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Context.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Page.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface Context { - page?: { url: string }; // only for RUM agent +// only for RUM agent: shared by error and transaction +export interface Page { + url: string; }