From d2e16b5da5ca7fe3a061c6319afe4522d2328c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 8 Mar 2019 10:15:52 +0100 Subject: [PATCH] [APM] Improve types for tabs and properties table (#32462) * [APM] Improve types for tabs * Update translations * Rename Tab to PropertyTab * Add comment about agent names --- .../elasticsearch_fieldnames.test.ts.snap | 6 +- .../common/elasticsearch_fieldnames.test.ts | 6 +- .../DetailView/ErrorTabs.tsx | 52 ++++++ .../DetailView/StickyErrorProperties.test.tsx | 44 +++++ .../DetailView/StickyErrorProperties.tsx | 118 +++++++++++++ .../StickyErrorProperties.test.tsx.snap} | 101 +---------- .../__snapshots__/index.test.tsx.snap | 100 +++++++++++ .../DetailView.test.tsx => index.test.tsx} | 31 +--- .../ErrorGroupDetails/DetailView/index.tsx | 165 ++---------------- .../Main/__test__/UpdateBreadcrumbs.test.js | 4 +- .../TransactionPropertiesTableForFlyout.tsx | 22 +-- ...ropertiesTable.tsx => TransactionTabs.tsx} | 35 ++-- .../TransactionDetails/Transaction/index.tsx | 4 +- .../PropertiesTable/NestedKeyValueTable.tsx | 19 +- .../__test__/PropertiesTable.test.tsx | 130 ++------------ .../NestedKeyValueTable.test.tsx.snap | 4 - .../PropertiesTable.test.tsx.snap | 66 ++++--- .../__test__/tabConfig.test.tsx | 94 ++++++++++ .../shared/PropertiesTable/index.tsx | 66 +++---- .../shared/PropertiesTable/tabConfig.ts | 148 ++++++++++++++++ .../shared/Stacktrace/Variables.tsx | 2 +- .../__test__/TransactionActionMenu.test.tsx | 5 +- ...nsactionActionMenuProps.ts => mockData.ts} | 11 +- .../apm/public/utils/documentation/agents.ts | 57 +++--- .../plugins/apm/typings/es_schemas/APMDoc.ts | 12 +- .../translations/translations/zh-CN.json | 3 +- 26 files changed, 731 insertions(+), 574 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx rename x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/{__test__/__snapshots__/DetailView.test.tsx.snap => __snapshots__/StickyErrorProperties.test.tsx.snap} (59%) create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap rename x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/{__test__/DetailView.test.tsx => index.test.tsx} (76%) rename x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/{TransactionPropertiesTable.tsx => TransactionTabs.tsx} (75%) create mode 100644 x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/tabConfig.test.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts rename x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/{transactionActionMenuProps.ts => mockData.ts} (92%) diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index a3c93c1db02bf5..f9b1283d0f168d 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -28,7 +28,7 @@ exports[`Error PARENT_ID 1`] = `"parentId"`; exports[`Error PROCESSOR_EVENT 1`] = `"error"`; -exports[`Error SERVICE_AGENT_NAME 1`] = `"agent name"`; +exports[`Error SERVICE_AGENT_NAME 1`] = `"java"`; exports[`Error SERVICE_NAME 1`] = `"service name"`; @@ -90,7 +90,7 @@ exports[`Span PARENT_ID 1`] = `"parentId"`; exports[`Span PROCESSOR_EVENT 1`] = `"span"`; -exports[`Span SERVICE_AGENT_NAME 1`] = `"agent name"`; +exports[`Span SERVICE_AGENT_NAME 1`] = `"java"`; exports[`Span SERVICE_NAME 1`] = `"service name"`; @@ -152,7 +152,7 @@ exports[`Transaction PARENT_ID 1`] = `"parentId"`; exports[`Transaction PROCESSOR_EVENT 1`] = `"transaction"`; -exports[`Transaction SERVICE_AGENT_NAME 1`] = `"agent name"`; +exports[`Transaction SERVICE_AGENT_NAME 1`] = `"java"`; exports[`Transaction SERVICE_NAME 1`] = `"service name"`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.test.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.test.ts index a63b3cb9ec854a..a8c19b2ee4cc64 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.test.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.test.ts @@ -14,7 +14,7 @@ describe('Transaction', () => { const transaction: Transaction = { '@timestamp': new Date().toString(), agent: { - name: 'agent name', + name: 'java', version: 'agent version' }, http: { @@ -59,7 +59,7 @@ describe('Span', () => { const span: Span = { '@timestamp': new Date().toString(), agent: { - name: 'agent name', + name: 'java', version: 'agent version' }, processor: { @@ -101,7 +101,7 @@ describe('Span', () => { describe('Error', () => { const errorDoc: APMError = { agent: { - name: 'agent name', + name: 'java', version: 'agent version' }, error: { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx new file mode 100644 index 00000000000000..7e816196d07bad --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import { idx } from 'x-pack/plugins/apm/common/idx'; +import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; +import { + getTabsFromObject, + PropertyTab +} from '../../../shared/PropertiesTable/tabConfig'; + +export type ErrorTab = PropertyTab | ExceptionTab | LogTab; + +interface LogTab { + key: 'log_stacktrace'; + label: string; +} + +export const logStacktraceTab: LogTab = { + key: 'log_stacktrace', + label: i18n.translate('xpack.apm.propertiesTable.tabs.logStacktraceLabel', { + defaultMessage: 'Log stacktrace' + }) +}; + +interface ExceptionTab { + key: 'exception_stacktrace'; + label: string; +} + +export const exceptionStacktraceTab: ExceptionTab = { + key: 'exception_stacktrace', + label: i18n.translate( + 'xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel', + { + defaultMessage: 'Exception stacktrace' + } + ) +}; + +export function getTabs(error: APMError) { + const hasLogStacktrace = !isEmpty(idx(error, _ => _.error.log.stacktrace)); + return [ + ...(hasLogStacktrace ? [logStacktraceTab] : []), + exceptionStacktraceTab, + ...getTabsFromObject(error) + ]; +} diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx new file mode 100644 index 00000000000000..cbab4f9aca372f --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow } from 'enzyme'; +import React from 'react'; +import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { StickyErrorProperties } from './StickyErrorProperties'; + +describe('StickyErrorProperties', () => { + it('should render StickyProperties', () => { + const transaction = { + http: { request: { method: 'GET' } }, + url: { full: 'myUrl' }, + trace: { id: 'traceId' }, + transaction: { + type: 'myTransactionType', + name: 'myTransactionName', + id: 'myTransactionName' + }, + service: { name: 'myService' }, + user: { id: 'myUserId' } + } as Transaction; + + const error = { + '@timestamp': 'myTimestamp', + http: { request: { method: 'GET' } }, + url: { full: 'myUrl' }, + service: { name: 'myService' }, + user: { id: 'myUserId' }, + error: { exception: [{ handled: true }] }, + transaction: { id: 'myTransactionId', sampled: true } + } as APMError; + + const wrapper = shallow( + + ); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx new file mode 100644 index 00000000000000..512d2e57f5540f --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/StickyErrorProperties.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + ERROR_EXC_HANDLED, + HTTP_REQUEST_METHOD, + TRANSACTION_ID, + URL_FULL, + USER_ID +} from 'x-pack/plugins/apm/common/elasticsearch_fieldnames'; +import { NOT_AVAILABLE_LABEL } from 'x-pack/plugins/apm/common/i18n'; +import { idx } from 'x-pack/plugins/apm/common/idx'; +import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { KibanaLink } from '../../../shared/Links/KibanaLink'; +import { legacyEncodeURIComponent } from '../../../shared/Links/url_helpers'; +import { StickyProperties } from '../../../shared/StickyProperties'; + +interface Props { + error: APMError; + transaction: Transaction | undefined; +} + +function TransactionLink({ error, transaction }: Props) { + if (!transaction) { + return {NOT_AVAILABLE_LABEL}; + } + + const isSampled = error.transaction.sampled; + if (!isSampled) { + return {transaction.transaction.id}; + } + + const path = `/${ + transaction.service.name + }/transactions/${legacyEncodeURIComponent( + transaction.transaction.type + )}/${legacyEncodeURIComponent(transaction.transaction.name)}`; + + return ( + + {transaction.transaction.id} + + ); +} + +export function StickyErrorProperties({ error, transaction }: Props) { + const stickyProperties = [ + { + fieldName: '@timestamp', + label: i18n.translate('xpack.apm.errorGroupDetails.timestampLabel', { + defaultMessage: 'Timestamp' + }), + val: error['@timestamp'], + width: '50%' + }, + { + fieldName: URL_FULL, + label: 'URL', + val: + idx(error, _ => _.context.page.url) || + idx(error, _ => _.url.full) || + NOT_AVAILABLE_LABEL, + truncated: true, + width: '50%' + }, + { + fieldName: HTTP_REQUEST_METHOD, + label: i18n.translate('xpack.apm.errorGroupDetails.requestMethodLabel', { + defaultMessage: 'Request method' + }), + val: idx(error, _ => _.http.request.method) || NOT_AVAILABLE_LABEL, + width: '25%' + }, + { + fieldName: ERROR_EXC_HANDLED, + label: i18n.translate('xpack.apm.errorGroupDetails.handledLabel', { + defaultMessage: 'Handled' + }), + val: + String(idx(error, _ => _.error.exception[0].handled)) || + NOT_AVAILABLE_LABEL, + width: '25%' + }, + { + fieldName: TRANSACTION_ID, + label: i18n.translate( + 'xpack.apm.errorGroupDetails.transactionSampleIdLabel', + { + defaultMessage: 'Transaction sample ID' + } + ), + val: , + width: '25%' + }, + { + fieldName: USER_ID, + label: i18n.translate('xpack.apm.errorGroupDetails.userIdLabel', { + defaultMessage: 'User ID' + }), + val: idx(error, _ => _.user.id) || NOT_AVAILABLE_LABEL, + width: '25%' + } + ]; + + return ; +} diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/StickyErrorProperties.test.tsx.snap similarity index 59% rename from x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.tsx.snap rename to x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/StickyErrorProperties.test.tsx.snap index 0355c2231251e6..714bfccf5c745f 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/StickyErrorProperties.test.tsx.snap @@ -1,48 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DetailView should render Discover button 1`] = ` - - - View 10 occurrences in Discover - - -`; - -exports[`DetailView should render StickyProperties 1`] = ` +exports[`StickyErrorProperties should render StickyProperties 1`] = ` `; - -exports[`DetailView should render TabContent 1`] = ` - -`; - -exports[`DetailView should render tabs 1`] = ` - - - Exception stacktrace - - - Service - - - User - - - Labels - - -`; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000000..751da8316280ba --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DetailView should render Discover button 1`] = ` + + + View 10 occurrences in Discover + + +`; + +exports[`DetailView should render TabContent 1`] = ` + +`; + +exports[`DetailView should render tabs 1`] = ` + + + Exception stacktrace + + + Service + + + User + + + Labels + + +`; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx similarity index 76% rename from x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.tsx rename to x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx index 0ec410dfece2e3..e85a49d76dc182 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx @@ -10,10 +10,8 @@ import React from 'react'; import { RRRRenderResponse } from 'react-redux-request'; import { ErrorGroupAPIResponse } from 'x-pack/plugins/apm/server/lib/errors/get_error_group'; import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; -import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; -// @ts-ignore -import { mockMoment } from '../../../../../utils/testHelpers'; -import { DetailView } from '../index'; +import { mockMoment } from '../../../../utils/testHelpers'; +import { DetailView } from './index'; describe('DetailView', () => { beforeEach(() => { @@ -67,27 +65,7 @@ describe('DetailView', () => { status: 'SUCCESS', data: { occurrencesCount: 10, - transaction: { - http: { request: { method: 'GET' } }, - url: { full: 'myUrl' }, - trace: { id: 'traceId' }, - transaction: { - type: 'myTransactionType', - name: 'myTransactionName', - id: 'myTransactionName' - }, - service: { name: 'myService' }, - user: { id: 'myUserId' } - } as Transaction, - error: { - '@timestamp': 'myTimestamp', - http: { request: { method: 'GET' } }, - url: { full: 'myUrl' }, - service: { name: 'myService' }, - user: { id: 'myUserId' }, - error: { exception: [{ handled: true }] }, - transaction: { id: 'myTransactionId', sampled: true } - } as APMError + error: {} as APMError } }; const wrapper = shallow( @@ -96,10 +74,9 @@ describe('DetailView', () => { urlParams={{}} location={{} as Location} /> - ).find('StickyProperties'); + ).find('StickyErrorProperties'); expect(wrapper.exists()).toBe(true); - expect(wrapper).toMatchSnapshot(); }); it('should render tabs', () => { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 89f81c714bec51..b1158359488097 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -14,40 +14,36 @@ import { import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import { first, get, isEmpty } from 'lodash'; -import React, { Fragment } from 'react'; +import { get } from 'lodash'; +import React from 'react'; import { RRRRenderResponse } from 'react-redux-request'; import styled from 'styled-components'; -import { - ERROR_EXC_HANDLED, - HTTP_REQUEST_METHOD, - TRANSACTION_ID, - URL_FULL, - USER_ID -} from 'x-pack/plugins/apm/common/elasticsearch_fieldnames'; -import { NOT_AVAILABLE_LABEL } from 'x-pack/plugins/apm/common/i18n'; import { idx } from 'x-pack/plugins/apm/common/idx'; -import { KibanaLink } from 'x-pack/plugins/apm/public/components/shared/Links/KibanaLink'; import { fromQuery, history, toQuery } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers'; -import { legacyEncodeURIComponent } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers'; import { STATUS } from 'x-pack/plugins/apm/public/constants'; import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; import { ErrorGroupAPIResponse } from 'x-pack/plugins/apm/server/lib/errors/get_error_group'; import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; -import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; import { borderRadius, px, unit, units } from '../../../../style/variables'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; -import { - getPropertyTabNames, - PropertiesTable -} from '../../../shared/PropertiesTable'; -import { Tab } from '../../../shared/PropertiesTable/propertyConfig'; +import { PropertiesTable } from '../../../shared/PropertiesTable'; +import { getCurrentTab } from '../../../shared/PropertiesTable/tabConfig'; import { Stacktrace } from '../../../shared/Stacktrace'; -import { StickyProperties } from '../../../shared/StickyProperties'; +import { + ErrorTab, + exceptionStacktraceTab, + getTabs, + logStacktraceTab +} from './ErrorTabs'; +import { StickyErrorProperties } from './StickyErrorProperties'; + +const PaddedContainer = styled.div` + padding: ${px(units.plus)} ${px(units.plus)} 0; +`; const Container = styled.div` position: relative; @@ -64,26 +60,6 @@ const HeaderContainer = styled.div` margin-bottom: ${px(unit)}; `; -const PaddedContainer = styled.div` - padding: ${px(units.plus)} ${px(units.plus)} 0; -`; - -const logStacktraceTab = { - key: 'log_stacktrace', - label: i18n.translate('xpack.apm.propertiesTable.tabs.logStacktraceLabel', { - defaultMessage: 'Log stacktrace' - }) -}; -const exceptionStacktraceTab = { - key: 'exception_stacktrace', - label: i18n.translate( - 'xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel', - { - defaultMessage: 'Exception stacktrace' - } - ) -}; - interface Props { errorGroup: RRRRenderResponse; urlParams: IUrlParams; @@ -100,64 +76,6 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { return null; } - const stickyProperties = [ - { - fieldName: '@timestamp', - label: i18n.translate('xpack.apm.errorGroupDetails.timestampLabel', { - defaultMessage: 'Timestamp' - }), - val: error['@timestamp'], - width: '50%' - }, - { - fieldName: URL_FULL, - label: 'URL', - val: - idx(error, _ => _.context.page.url) || - idx(error, _ => _.url.full) || - NOT_AVAILABLE_LABEL, - truncated: true, - width: '50%' - }, - { - fieldName: HTTP_REQUEST_METHOD, - label: i18n.translate('xpack.apm.errorGroupDetails.requestMethodLabel', { - defaultMessage: 'Request method' - }), - val: idx(error, _ => _.http.request.method) || NOT_AVAILABLE_LABEL, - width: '25%' - }, - { - fieldName: ERROR_EXC_HANDLED, - label: i18n.translate('xpack.apm.errorGroupDetails.handledLabel', { - defaultMessage: 'Handled' - }), - val: - String(idx(error, _ => _.error.exception[0].handled)) || - NOT_AVAILABLE_LABEL, - width: '25%' - }, - { - fieldName: TRANSACTION_ID, - label: i18n.translate( - 'xpack.apm.errorGroupDetails.transactionSampleIdLabel', - { - defaultMessage: 'Transaction sample ID' - } - ), - val: , - width: '25%' - }, - { - fieldName: USER_ID, - label: i18n.translate('xpack.apm.errorGroupDetails.userIdLabel', { - defaultMessage: 'User ID' - }), - val: idx(error, _ => _.user.id) || NOT_AVAILABLE_LABEL, - width: '25%' - } - ]; - const tabs = getTabs(error); const currentTab = getCurrentTab(tabs, urlParams.detailTab); @@ -189,7 +107,7 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { - + @@ -223,46 +141,12 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { ); } -interface TransactionLinkProps { - error: APMError; - transaction?: Transaction; -} - -function TransactionLink({ error, transaction }: TransactionLinkProps) { - if (!transaction) { - return {NOT_AVAILABLE_LABEL}; - } - - const isSampled = idx(error, _ => _.transaction.sampled); - if (!isSampled) { - return {transaction.transaction.id}; - } - - const path = `/${ - transaction.service.name - }/transactions/${legacyEncodeURIComponent( - transaction.transaction.type - )}/${legacyEncodeURIComponent(transaction.transaction.name)}`; - - return ( - _.trace.id) - }} - > - {transaction.transaction.id} - - ); -} - export function TabContent({ error, currentTab }: { error: APMError; - currentTab: Tab; + currentTab: ErrorTab; }) { const codeLanguage = error.service.name; const agentName = error.agent.name; @@ -289,18 +173,3 @@ export function TabContent({ ); } } - -// Ensure the selected tab exists or use the first -export function getCurrentTab(tabs: Tab[] = [], selectedTabKey?: string) { - const selectedTab = tabs.find(({ key }) => key === selectedTabKey); - return selectedTab ? selectedTab : first(tabs) || {}; -} - -export function getTabs(error: APMError) { - const hasLogStacktrace = !isEmpty(idx(error, _ => _.error.log.stacktrace)); - return [ - ...(hasLogStacktrace ? [logStacktraceTab] : []), - exceptionStacktraceTab, - ...getPropertyTabNames(error) - ]; -} diff --git a/x-pack/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js b/x-pack/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js index 2140aec71828fa..7e2508a7dfd0f9 100644 --- a/x-pack/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js +++ b/x-pack/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs'; import chrome from 'ui/chrome'; +import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs'; jest.mock( 'ui/chrome', diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTableForFlyout.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTableForFlyout.tsx index 8fc1918f016a3f..6e35b123075505 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTableForFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTableForFlyout.tsx @@ -6,7 +6,7 @@ import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import { Location } from 'history'; -import { first, get } from 'lodash'; +import { get } from 'lodash'; import React from 'react'; import { fromQuery, @@ -15,21 +15,11 @@ import { } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers'; import { Transaction } from '../../../../../typings/es_schemas/Transaction'; import { IUrlParams } from '../../../../store/urlParams'; +import { PropertiesTable } from '../../../shared/PropertiesTable'; import { - getPropertyTabNames, - PropertiesTable -} from '../../../shared/PropertiesTable'; -import { Tab } from '../../../shared/PropertiesTable/propertyConfig'; - -// Ensure the selected tab exists or use the first -function getCurrentTab(tabs: Tab[] = [], selectedTabKey?: string) { - const selectedTab = tabs.find(({ key }) => key === selectedTabKey); - return selectedTab ? selectedTab : first(tabs) || {}; -} - -function getTabs(transaction: Transaction) { - return getPropertyTabNames(transaction); -} + getCurrentTab, + getTabsFromObject +} from '../../../shared/PropertiesTable/tabConfig'; interface Props { location: Location; @@ -42,7 +32,7 @@ export const TransactionPropertiesTableForFlyout: React.SFC = ({ transaction, urlParams }) => { - const tabs = getTabs(transaction); + const tabs = getTabsFromObject(transaction); const currentTab = getCurrentTab(tabs, urlParams.flyoutDetailTab); const agentName = transaction.agent.name; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx similarity index 75% rename from x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx rename to x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx index 8d42d4fbb07c98..43056d129ae78a 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx @@ -7,7 +7,7 @@ import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import { first, get } from 'lodash'; +import { get } from 'lodash'; import React from 'react'; import styled from 'styled-components'; import { @@ -19,11 +19,11 @@ import { Transaction } from '../../../../../typings/es_schemas/Transaction'; import { IUrlParams } from '../../../../store/urlParams'; import { px, units } from '../../../../style/variables'; import { HeightRetainer } from '../../../shared/HeightRetainer'; +import { PropertiesTable } from '../../../shared/PropertiesTable'; import { - getPropertyTabNames, - PropertiesTable -} from '../../../shared/PropertiesTable'; -import { Tab } from '../../../shared/PropertiesTable/propertyConfig'; + getCurrentTab, + getTabsFromObject +} from '../../../shared/PropertiesTable/tabConfig'; import { WaterfallContainer } from './WaterfallContainer'; import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; @@ -31,41 +31,34 @@ const TableContainer = styled.div` padding: ${px(units.plus)} ${px(units.plus)} 0; `; -// Ensure the selected tab exists or use the first -function getCurrentTab(tabs: Tab[] = [], selectedTabKey?: string) { - const selectedTab = tabs.find(({ key }) => key === selectedTabKey); - - return selectedTab ? selectedTab : first(tabs) || {}; +interface TimelineTab { + key: 'timeline'; + label: string; } -const timelineTab = { +const timelineTab: TimelineTab = { key: 'timeline', label: i18n.translate('xpack.apm.propertiesTable.tabs.timelineLabel', { defaultMessage: 'Timeline' }) }; -function getTabs(transaction: Transaction) { - return [timelineTab, ...getPropertyTabNames(transaction)]; -} - -interface TransactionPropertiesTableProps { +interface Props { location: Location; transaction: Transaction; urlParams: IUrlParams; waterfall: IWaterfall; } -export function TransactionPropertiesTable({ +export function TransactionTabs({ location, transaction, urlParams, waterfall -}: TransactionPropertiesTableProps) { - const tabs = getTabs(transaction); +}: Props) { + const tabs = [timelineTab, ...getTabsFromObject(transaction)]; const currentTab = getCurrentTab(tabs, urlParams.detailTab); const agentName = transaction.agent.name; - const isTimelineTab = currentTab.key === timelineTab.key; return ( @@ -93,7 +86,7 @@ export function TransactionPropertiesTable({ - {isTimelineTab ? ( + {currentTab.key === timelineTab.key ? ( = ({ - string[]; +import { sortKeysByConfig } from './tabConfig'; const Table = styled.table` font-family: ${fontFamilyCode}; @@ -79,13 +78,11 @@ export function FormattedValue({ value }: { value: any }): JSX.Element { export function NestedValue({ parentKey, value, - depth, - keySorter + depth }: { value: unknown; depth: number; parentKey?: string; - keySorter?: KeySorter; }): JSX.Element { const MAX_LEVEL = 3; if (depth < MAX_LEVEL && isObject(value)) { @@ -93,7 +90,6 @@ export function NestedValue({ ); @@ -105,29 +101,22 @@ export function NestedValue({ export function NestedKeyValueTable({ data, parentKey, - keySorter = Object.keys, depth }: { data: StringMap; parentKey?: string; - keySorter?: KeySorter; depth: number; }): JSX.Element { return ( - {keySorter(data, parentKey).map(key => ( + {sortKeysByConfig(data, parentKey).map(key => ( - + ))} diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/PropertiesTable.test.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/PropertiesTable.test.tsx index f05ac607f5db35..0549bd3145e29e 100644 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/PropertiesTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/PropertiesTable.test.tsx @@ -6,32 +6,18 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { - AgentFeatureTipMessage, - getPropertyTabNames, - PropertiesTable, - sortKeysByConfig -} from '..'; +import { PropertiesTable, TabHelpMessage } from '..'; import * as agentDocs from '../../../../utils/documentation/agents'; -import * as propertyConfig from '../propertyConfig'; describe('PropertiesTable', () => { - beforeEach(() => { - mockPropertyConfig(); - }); - - afterEach(() => { - unMockPropertyConfig(); - }); - describe('PropertiesTable component', () => { it('should render with data', () => { expect( shallow( ) ).toMatchSnapshot(); @@ -39,9 +25,7 @@ describe('PropertiesTable', () => { it("should render empty when data isn't present", () => { expect( - shallow( - - ) + shallow() ).toMatchSnapshot(); }); @@ -50,126 +34,40 @@ describe('PropertiesTable', () => { shallow( ) ).toMatchSnapshot(); }); }); - describe('sortKeysByConfig', () => { - const testData = { - color: 'blue', - name: 'Jess', - age: '39', - numbers: [1, 2, 3], - _id: '44x099z' - }; - - it('should sort with presorted keys first', () => { - expect(sortKeysByConfig(testData, 'testProperty')).toEqual([ - 'name', - 'age', - '_id', - 'color', - 'numbers' - ]); - }); - - it('should alpha-sort keys when there is no config value found', () => { - expect(sortKeysByConfig(testData, 'nonExistentKey')).toEqual([ - '_id', - 'age', - 'color', - 'name', - 'numbers' - ]); - }); - }); - - describe('getPropertyTabNames', () => { - it('should return selected and required keys only', () => { - const expectedTabsConfig = [ - { - key: 'testProperty', - label: 'testPropertyLabel' - }, - { - key: 'requiredProperty', - label: 'requiredPropertyLabel' - } - ]; - expect(getPropertyTabNames({ testProperty: {} } as any)).toEqual( - expectedTabsConfig - ); - }); - }); - - describe('AgentFeatureTipMessage component', () => { - const featureName = 'user'; + describe('TabHelpMessage component', () => { + const tabKey = 'user'; const agentName = 'nodejs'; it('should render when docs are returned', () => { jest - .spyOn(agentDocs, 'getAgentFeatureDocsUrl') + .spyOn(agentDocs, 'getAgentDocUrlForTab') .mockImplementation(() => 'mock-url'); expect( - shallow( - - ) + shallow() ).toMatchSnapshot(); - expect(agentDocs.getAgentFeatureDocsUrl).toHaveBeenCalledWith( - featureName, + expect(agentDocs.getAgentDocUrlForTab).toHaveBeenCalledWith( + tabKey, agentName ); }); it('should render null empty string when no docs are returned', () => { jest - .spyOn(agentDocs, 'getAgentFeatureDocsUrl') + .spyOn(agentDocs, 'getAgentDocUrlForTab') .mockImplementation(() => undefined); expect( - shallow( - - ) + shallow() ).toMatchSnapshot(); }); }); }); - -function mockPropertyConfig() { - // @ts-ignore - propertyConfig.PROPERTY_CONFIG = [ - { - key: 'testProperty', - label: 'testPropertyLabel', - required: false, - presortedKeys: ['name', 'age'] - }, - { - key: 'optionalProperty', - label: 'optionalPropertyLabel', - required: false - }, - { - key: 'requiredProperty', - label: 'requiredPropertyLabel', - required: true - } - ]; -} - -const originalPropertyConfig = propertyConfig.PROPERTY_CONFIG; -function unMockPropertyConfig() { - // @ts-ignore - propertyConfig.PROPERTY_CONFIG = originalPropertyConfig; -} diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/NestedKeyValueTable.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/NestedKeyValueTable.test.tsx.snap index 3454ba5ea24fb2..55f330b9b9322f 100644 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/NestedKeyValueTable.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/NestedKeyValueTable.test.tsx.snap @@ -187,7 +187,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = ` @@ -205,7 +204,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = ` @@ -229,7 +227,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = ` - - You can configure your agent to add contextual information about your users. - - - Learn more in the documentation. - - -`; - exports[`PropertiesTable PropertiesTable component should render empty when data isn't present 1`] = ` No data available - `; @@ -43,12 +22,11 @@ exports[`PropertiesTable PropertiesTable component should render with data 1`] = } } depth={1} - keySorter={[Function]} - parentKey="testPropKey" + parentKey="kubernetes" /> - `; @@ -58,12 +36,32 @@ exports[`PropertiesTable PropertiesTable component should still render NestedKey - `; + +exports[`PropertiesTable TabHelpMessage component should render null empty string when no docs are returned 1`] = `""`; + +exports[`PropertiesTable TabHelpMessage component should render when docs are returned 1`] = ` + + + You can configure your agent to add contextual information about your users. + + + Learn more in the documentation. + + +`; diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/tabConfig.test.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/tabConfig.test.tsx new file mode 100644 index 00000000000000..47cffde62612e0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/tabConfig.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as propertyConfig from '../tabConfig'; +const { getTabsFromObject, sortKeysByConfig } = propertyConfig; + +describe('tabConfig', () => { + beforeEach(() => { + mockPropertyConfig(); + }); + + afterEach(() => { + unMockPropertyConfig(); + }); + + describe('getTabsFromObject', () => { + it('should return selected and required keys only', () => { + const expectedTabs = [ + { + key: 'testProperty', + label: 'testPropertyLabel' + }, + { + key: 'requiredProperty', + label: 'requiredPropertyLabel' + } + ]; + expect(getTabsFromObject({ testProperty: {} } as any)).toEqual( + expectedTabs + ); + }); + }); + + describe('sortKeysByConfig', () => { + const testData = { + color: 'blue', + name: 'Jess', + age: '39', + numbers: [1, 2, 3], + _id: '44x099z' + }; + + it('should sort with presorted keys first', () => { + expect(sortKeysByConfig(testData, 'testProperty')).toEqual([ + 'name', + 'age', + '_id', + 'color', + 'numbers' + ]); + }); + + it('should alpha-sort keys when there is no config value found', () => { + expect(sortKeysByConfig(testData, 'nonExistentKey')).toEqual([ + '_id', + 'age', + 'color', + 'name', + 'numbers' + ]); + }); + }); +}); + +function mockPropertyConfig() { + // @ts-ignore + propertyConfig.TAB_CONFIG = [ + { + key: 'testProperty', + label: 'testPropertyLabel', + required: false, + presortedKeys: ['name', 'age'] + }, + { + key: 'optionalProperty', + label: 'optionalPropertyLabel', + required: false + }, + { + key: 'requiredProperty', + label: 'requiredPropertyLabel', + required: true + } + ]; +} + +const originalPropertyConfig = propertyConfig.TAB_CONFIG; +function unMockPropertyConfig() { + // @ts-ignore + propertyConfig.TAB_CONFIG = originalPropertyConfig; +} diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx index 9c42c1bf4257af..375b4b85057949 100644 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx @@ -8,16 +8,14 @@ import { EuiIcon } from '@elastic/eui'; import { EuiLink } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; -import { get, has, indexBy, uniq } from 'lodash'; import React from 'react'; import styled from 'styled-components'; -import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; -import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { AgentName } from 'x-pack/plugins/apm/typings/es_schemas/APMDoc'; import { StringMap } from '../../../../typings/common'; import { fontSize, fontSizes, px, unit, units } from '../../../style/variables'; -import { getAgentFeatureDocsUrl } from '../../../utils/documentation/agents'; -import { KeySorter, NestedKeyValueTable } from './NestedKeyValueTable'; -import { PROPERTY_CONFIG } from './propertyConfig'; +import { getAgentDocUrlForTab } from '../../../utils/documentation/agents'; +import { NestedKeyValueTable } from './NestedKeyValueTable'; +import { PropertyTabKey } from './tabConfig'; const TableContainer = styled.div` padding-bottom: ${px(units.double)}; @@ -40,14 +38,8 @@ const EuiIconWithSpace = styled(EuiIcon)` margin-right: ${px(units.half)}; `; -export function getPropertyTabNames(obj: Transaction | APMError) { - return PROPERTY_CONFIG.filter( - ({ key, required }) => required || has(obj, key) - ).map(({ key, label }) => ({ key, label })); -} - -function getAgentFeatureText(featureName: string) { - switch (featureName) { +function getTabHelpText(tabKey: PropertyTabKey) { + switch (tabKey) { case 'user': return i18n.translate( 'xpack.apm.propertiesTable.userTab.agentFeatureText', @@ -56,15 +48,16 @@ function getAgentFeatureText(featureName: string) { 'You can configure your agent to add contextual information about your users.' } ); - case 'tags': + case 'labels': return i18n.translate( - 'xpack.apm.propertiesTable.tagsTab.agentFeatureText', + 'xpack.apm.propertiesTable.labelsTab.agentFeatureText', { defaultMessage: 'You can configure your agent to add filterable tags on transactions.' } ); - case 'custom': + case 'transaction.custom': + case 'error.custom': return i18n.translate( 'xpack.apm.propertiesTable.customTab.agentFeatureText', { @@ -75,14 +68,17 @@ function getAgentFeatureText(featureName: string) { } } -export function AgentFeatureTipMessage({ - featureName, +export function TabHelpMessage({ + tabKey, agentName }: { - featureName: string; - agentName?: string; + tabKey?: PropertyTabKey; + agentName?: AgentName; }) { - const docsUrl = getAgentFeatureDocsUrl(featureName, agentName); + if (!tabKey) { + return null; + } + const docsUrl = getAgentDocUrlForTab(tabKey, agentName); if (!docsUrl) { return null; } @@ -90,7 +86,7 @@ export function AgentFeatureTipMessage({ return ( - {getAgentFeatureText(featureName)}{' '} + {getTabHelpText(tabKey)}{' '} {i18n.translate( 'xpack.apm.propertiesTable.agentFeature.learnMoreLinkLabel', @@ -101,34 +97,19 @@ export function AgentFeatureTipMessage({ ); } -export const sortKeysByConfig: KeySorter = (object, currentKey) => { - const indexedPropertyConfig = indexBy(PROPERTY_CONFIG, 'key'); - const presorted = get( - indexedPropertyConfig, - `${currentKey}.presortedKeys`, - [] - ); - return uniq([...presorted, ...Object.keys(object).sort()]); -}; - export function PropertiesTable({ propData, propKey, agentName }: { propData?: StringMap; - propKey: string; - agentName?: string; + propKey?: PropertyTabKey; + agentName?: AgentName; }) { return ( {propData ? ( - + ) : ( {i18n.translate( @@ -137,7 +118,8 @@ export function PropertiesTable({ )} )} - + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts b/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts new file mode 100644 index 00000000000000..f4d843d96f34eb --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { get, indexBy, uniq } from 'lodash'; +import { first, has } from 'lodash'; +import { StringMap } from 'x-pack/plugins/apm/typings/common'; +import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; + +export type PropertyTabKey = + | keyof Transaction + | keyof APMError + | 'transaction.custom' + | 'error.custom'; + +export interface PropertyTab { + key: PropertyTabKey; + label: string; +} + +interface TabConfig extends PropertyTab { + required: boolean; + presortedKeys: string[]; +} + +export function getTabsFromObject(obj: Transaction | APMError): PropertyTab[] { + return TAB_CONFIG.filter( + ({ key, required }) => required || has(obj, key) + ).map(({ key, label }) => ({ key, label })); +} + +export type KeySorter = (data: StringMap, parentKey?: string) => string[]; + +export const sortKeysByConfig: KeySorter = (object, currentKey) => { + const indexedPropertyConfig = indexBy(TAB_CONFIG, 'key'); + const presorted = get( + indexedPropertyConfig, + `${currentKey}.presortedKeys`, + [] + ); + return uniq([...presorted, ...Object.keys(object).sort()]); +}; + +export function getCurrentTab( + tabs: T[] = [], + currentTabKey: string | undefined +): T { + const selectedTab = tabs.find(({ key }) => key === currentTabKey); + return selectedTab ? selectedTab : first(tabs) || {}; +} + +export const TAB_CONFIG: TabConfig[] = [ + { + key: 'http', + label: i18n.translate('xpack.apm.propertiesTable.tabs.httpLabel', { + defaultMessage: 'HTTP' + }), + required: false, + presortedKeys: [] + }, + { + key: 'host', + label: i18n.translate('xpack.apm.propertiesTable.tabs.hostLabel', { + defaultMessage: 'Host' + }), + required: false, + presortedKeys: ['hostname', 'architecture', 'platform'] + }, + { + key: 'service', + label: i18n.translate('xpack.apm.propertiesTable.tabs.serviceLabel', { + defaultMessage: 'Service' + }), + required: false, + presortedKeys: ['runtime', 'framework', 'version'] + }, + { + key: 'process', + label: i18n.translate('xpack.apm.propertiesTable.tabs.processLabel', { + defaultMessage: 'Process' + }), + required: false, + presortedKeys: ['pid', 'title', 'args'] + }, + { + key: 'agent', + label: i18n.translate('xpack.apm.propertiesTable.tabs.agentLabel', { + defaultMessage: 'Agent' + }), + required: false, + presortedKeys: [] + }, + { + key: 'url', + label: i18n.translate('xpack.apm.propertiesTable.tabs.urlLabel', { + defaultMessage: 'URL' + }), + required: false, + presortedKeys: [] + }, + { + key: 'container', + label: i18n.translate('xpack.apm.propertiesTable.tabs.containerLabel', { + defaultMessage: 'Container' + }), + required: false, + presortedKeys: [] + }, + { + key: 'user', + label: i18n.translate('xpack.apm.propertiesTable.tabs.userLabel', { + defaultMessage: 'User' + }), + required: true, + presortedKeys: ['id', 'username', 'email'] + }, + { + key: 'labels', + label: i18n.translate('xpack.apm.propertiesTable.tabs.labelsLabel', { + defaultMessage: 'Labels' + }), + required: true, + presortedKeys: [] + }, + { + key: 'transaction.custom', + label: i18n.translate( + 'xpack.apm.propertiesTable.tabs.transactionCustomLabel', + { + defaultMessage: 'Custom' + } + ), + required: false, + presortedKeys: [] + }, + { + key: 'error.custom', + label: i18n.translate('xpack.apm.propertiesTable.tabs.errorCustomLabel', { + defaultMessage: 'Custom' + }), + required: false, + presortedKeys: [] + } +]; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index ca764a873e312d..5ff65ed0ab545b 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -66,7 +66,7 @@ export class Variables extends React.Component { {this.state.isVisible && ( - + )} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 10b5216544962a..9944385a70b190 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -8,13 +8,10 @@ import { shallow } from 'enzyme'; import 'jest-styled-components'; import React from 'react'; import { TransactionActionMenu } from '../TransactionActionMenu'; -import { props } from './transactionActionMenuProps'; +import { location, transaction } from './mockData'; describe('TransactionActionMenu component', () => { it('should render with data', () => { - const transaction = props.transaction; - const location = props.location; - expect( shallow( diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/mockData.ts similarity index 92% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.ts rename to x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/mockData.ts index 1a0e576ede23a5..4d6b62bebe0847 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.ts +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/mockData.ts @@ -7,9 +7,9 @@ import { Location } from 'history'; import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; -const transaction: Transaction = { +export const transaction: Transaction = { agent: { - name: '227453131a17', + name: 'java', version: '7.0.0' }, processor: { @@ -77,15 +77,10 @@ const transaction: Transaction = { } }; -const location: Location = { +export const location: Location = { state: '', pathname: '/opbeans-go/transactions/request/GET~20~2Fapi~2Fproducts~2F~3Aid~2Fcustomers', search: '?_g=()&flyoutDetailTab=undefined&waterfallItemId=8b60bd32ecc6e150', hash: '' }; - -export const props = { - transaction, - location -}; diff --git a/x-pack/plugins/apm/public/utils/documentation/agents.ts b/x-pack/plugins/apm/public/utils/documentation/agents.ts index 1ddc0aa76b3983..34de23e14e9bd0 100644 --- a/x-pack/plugins/apm/public/utils/documentation/agents.ts +++ b/x-pack/plugins/apm/public/utils/documentation/agents.ts @@ -4,45 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APMDoc } from 'x-pack/plugins/apm/typings/es_schemas/APMDoc'; +import { PropertyTabKey } from '../../components/shared/PropertiesTable/tabConfig'; + const AGENT_URL_ROOT = 'https://www.elastic.co/guide/en/apm/agent'; -interface AgentNamedValues { - [agentName: string]: string; -} +type AgentName = APMDoc['agent']['name']; +type DocUrls = { + [tabKey in PropertyTabKey]?: { [agentName in AgentName]: string | undefined } +}; + +const customUrls = { + 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-custom-context`, + 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-custom-context`, + java: undefined, + nodejs: `${AGENT_URL_ROOT}/nodejs/1.x/agent-api.html#apm-set-custom-context`, + python: `${AGENT_URL_ROOT}/python/2.x/api.html#api-set-custom-context`, + ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_adding_custom_context` +}; -const APM_AGENT_FEATURE_DOCS: { - [featureName: string]: AgentNamedValues; -} = { +const AGENT_DOC_URLS: DocUrls = { user: { + 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-user-context`, + 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-user-context`, java: `${AGENT_URL_ROOT}/java/0.7/public-api.html#api-transaction-set-user`, nodejs: `${AGENT_URL_ROOT}/nodejs/1.x/agent-api.html#apm-set-user-context`, python: `${AGENT_URL_ROOT}/python/2.x/api.html#api-set-user-context`, - ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_providing_info_about_the_user`, - 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-user-context`, - 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-user-context` + ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_providing_info_about_the_user` }, - tags: { + labels: { + 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-tags`, + 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-tags`, java: `${AGENT_URL_ROOT}/java/0.7/public-api.html#api-transaction-add-tag`, nodejs: `${AGENT_URL_ROOT}/nodejs/1.x/agent-api.html#apm-set-tag`, python: `${AGENT_URL_ROOT}/python/2.x/api.html#api-tag`, - ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_adding_tags`, - 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-tags`, - 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-tags` + ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_adding_tags` }, - custom: { - nodejs: `${AGENT_URL_ROOT}/nodejs/1.x/agent-api.html#apm-set-custom-context`, - python: `${AGENT_URL_ROOT}/python/2.x/api.html#api-set-custom-context`, - ruby: `${AGENT_URL_ROOT}/ruby/1.x/advanced.html#_adding_custom_context`, - 'js-react': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-custom-context`, - 'js-base': `${AGENT_URL_ROOT}/js-base/0.x/api.html#apm-set-custom-context` - } + 'transaction.custom': customUrls, + 'error.custom': customUrls }; -export function getAgentFeatureDocsUrl( - featureName: string, - agentName?: string +export function getAgentDocUrlForTab( + tabKey: PropertyTabKey, + agentName?: AgentName ) { - if (APM_AGENT_FEATURE_DOCS[featureName] && agentName) { - return APM_AGENT_FEATURE_DOCS[featureName][agentName]; + const agentUrls = AGENT_DOC_URLS[tabKey]; + if (agentUrls && agentName) { + return agentUrls[agentName]; } } diff --git a/x-pack/plugins/apm/typings/es_schemas/APMDoc.ts b/x-pack/plugins/apm/typings/es_schemas/APMDoc.ts index 046f6faa177b24..98b537dbf41f15 100644 --- a/x-pack/plugins/apm/typings/es_schemas/APMDoc.ts +++ b/x-pack/plugins/apm/typings/es_schemas/APMDoc.ts @@ -4,11 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +// agent names can be any string. This list only defines the official agents that we might want to +// target specifically eg. linking to their documentation +export type AgentName = + | 'java' + | 'nodejs' + | 'python' + | 'ruby' + | 'js-react' + | 'js-base'; + // all documents types extend APMDoc and inherit all properties export interface APMDoc { '@timestamp': string; agent: { - name: string; + name: AgentName; version: string; }; timestamp: { us: number }; diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 557603eb7d11a5..59551144200e46 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3344,7 +3344,6 @@ "xpack.apm.propertiesTable.tabs.serviceLabel": "服务", "xpack.apm.propertiesTable.tabs.timelineLabel": "时间线", "xpack.apm.propertiesTable.tabs.userLabel": "用户", - "xpack.apm.propertiesTable.tagsTab.agentFeatureText": "您可以配置代理以添加有关事务上的可筛选标记。", "xpack.apm.propertiesTable.userTab.agentFeatureText": "您可以配置代理以添加有关用户的上下文信息。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription": "当前有 {serviceName}({transactionType})的作业正在运行。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText": "查看现有作业", @@ -8204,4 +8203,4 @@ "xpack.watcher.watchActionsTitle": "满足后将执行 {watchActionsCount, plural, one{# 个操作} other {# 个操作}}", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file