diff --git a/package.json b/package.json index 2df6eb0f01d00b..0d475a38eae10d 100644 --- a/package.json +++ b/package.json @@ -378,6 +378,7 @@ "istanbul-instrumenter-loader": "3.0.1", "jest": "^24.1.0", "jest-cli": "^24.1.0", + "jest-dom": "^3.1.3", "jest-raw-loader": "^1.0.1", "jimp": "0.2.28", "json5": "^1.0.1", 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 index f95e4e2c69c822..a6482709fd0f5c 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx @@ -8,31 +8,20 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { idx } from '../../../../../common/idx'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; -import { - getTabsFromObject, - PropertyTab -} from '../../../shared/PropertiesTable/tabConfig'; -export type ErrorTab = PropertyTab | ExceptionTab | LogTab; - -interface LogTab { - key: 'log_stacktrace'; +export interface ErrorTab { + key: 'log_stacktrace' | 'exception_stacktrace' | 'metadata'; label: string; } -export const logStacktraceTab: LogTab = { +export const logStacktraceTab: ErrorTab = { 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 = { +export const exceptionStacktraceTab: ErrorTab = { key: 'exception_stacktrace', label: i18n.translate( 'xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel', @@ -42,11 +31,18 @@ export const exceptionStacktraceTab: ExceptionTab = { ) }; +export const metadataTab: ErrorTab = { + key: 'metadata', + label: i18n.translate('xpack.apm.propertiesTable.tabs.metadataLabel', { + defaultMessage: 'Metadata' + }) +}; + export function getTabs(error: APMError) { const hasLogStacktrace = !isEmpty(idx(error, _ => _.error.log.stacktrace)); return [ ...(hasLogStacktrace ? [logStacktraceTab] : []), exceptionStacktraceTab, - ...getTabsFromObject(error) + metadataTab ]; } 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 index 751da8316280ba..9f757aa9dded53 100644 --- 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 @@ -75,26 +75,10 @@ exports[`DetailView should render tabs 1`] = ` - Service - - - User - - - Labels + Metadata `; 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 44b0ae4ea84ddb..404a9fd074d05e 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,9 +14,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import { get } from 'lodash'; import React from 'react'; import styled from 'styled-components'; +import { first } from 'lodash'; import { idx } from '../../../../../common/idx'; import { ErrorGroupAPIResponse } from '../../../../../server/lib/errors/get_error_group'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; @@ -24,8 +24,7 @@ import { IUrlParams } from '../../../../store/urlParams'; import { px, unit } from '../../../../style/variables'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; import { fromQuery, history, toQuery } from '../../../shared/Links/url_helpers'; -import { PropertiesTable } from '../../../shared/PropertiesTable'; -import { getCurrentTab } from '../../../shared/PropertiesTable/tabConfig'; +import { ErrorMetadata } from '../../../shared/MetadataTable/ErrorMetadata'; import { Stacktrace } from '../../../shared/Stacktrace'; import { ErrorTab, @@ -48,6 +47,15 @@ interface Props { location: Location; } +// TODO: Move query-string-based tabs into a re-usable component? +function getCurrentTab( + tabs: T[] = [], + currentTabKey: string | undefined +): T { + const selectedTab = tabs.find(({ key }) => key === currentTabKey); + return selectedTab ? selectedTab : first(tabs) || {}; +} + export function DetailView({ errorGroup, urlParams, location }: Props) { const { transaction, error, occurrencesCount } = errorGroup; @@ -124,7 +132,6 @@ export function TabContent({ currentTab: ErrorTab; }) { const codeLanguage = error.service.name; - const agentName = error.agent.name; const excStackframes = idx(error, _ => _.error.exception[0].stacktrace); const logStackframes = idx(error, _ => _.error.exception[0].stacktrace); @@ -138,13 +145,6 @@ export function TabContent({ ); default: - const propData = get(error, currentTab.key); - return ( - - ); + return ; } } diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index 3396cb6d4a2679..4c7d605c6a03f1 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -33,9 +33,9 @@ import styled from 'styled-components'; import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; import { IUrlParams } from '../../../../store/urlParams'; -import { XPACK_DOCS } from '../../../../utils/documentation/xpack'; import { KibanaLink } from '../../../shared/Links/KibanaLink'; import { createErrorGroupWatch, Schedule } from './createErrorGroupWatch'; +import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; type ScheduleKey = keyof Schedule; @@ -297,14 +297,18 @@ export class WatcherFlyout extends Component< To learn more about Watcher, please read our {documentationLink}." values={{ documentationLink: ( - + {i18n.translate( 'xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription.documentationLinkText', { defaultMessage: 'documentation' } )} - + ) }} /> @@ -499,14 +503,18 @@ export class WatcherFlyout extends Component< defaultMessage="If you have not configured email, please see the {documentationLink}." values={{ documentationLink: ( - + {i18n.translate( 'xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText.documentationLinkText', { defaultMessage: 'documentation' } )} - + ) }} /> 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 deleted file mode 100644 index 9b3a592626cc0b..00000000000000 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTableForFlyout.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; -import { Location } from 'history'; -import { get } from 'lodash'; -import React from 'react'; -import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; -import { IUrlParams } from '../../../../store/urlParams'; -import { fromQuery, history, toQuery } from '../../../shared/Links/url_helpers'; -import { PropertiesTable } from '../../../shared/PropertiesTable'; -import { - getCurrentTab, - getTabsFromObject -} from '../../../shared/PropertiesTable/tabConfig'; - -interface Props { - location: Location; - transaction: Transaction; - urlParams: IUrlParams; -} - -export const TransactionPropertiesTableForFlyout: React.SFC = ({ - location, - transaction, - urlParams -}) => { - const tabs = getTabsFromObject(transaction); - const currentTab = getCurrentTab(tabs, urlParams.flyoutDetailTab); - const agentName = transaction.agent.name; - - return ( -
- - {tabs.map(({ key, label }) => { - return ( - { - history.replace({ - ...location, - search: fromQuery({ - ...toQuery(location.search), - flyoutDetailTab: key - }) - }); - }} - isSelected={currentTab.key === key} - key={key} - > - {label} - - ); - })} - - - -
- ); -}; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx index 66b5b6e0fb2ebf..8801950973efe7 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx @@ -7,38 +7,28 @@ import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import { get } from 'lodash'; import React from 'react'; -import styled from 'styled-components'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { IUrlParams } from '../../../../store/urlParams'; -import { px, units } from '../../../../style/variables'; -import { HeightRetainer } from '../../../shared/HeightRetainer'; import { fromQuery, history, toQuery } from '../../../shared/Links/url_helpers'; -import { PropertiesTable } from '../../../shared/PropertiesTable'; -import { - getCurrentTab, - getTabsFromObject -} from '../../../shared/PropertiesTable/tabConfig'; +import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; -const TableContainer = styled.div` - padding: ${px(units.plus)} ${px(units.plus)} 0; -`; - -interface TimelineTab { - key: 'timeline'; - label: string; -} - -const timelineTab: TimelineTab = { +const timelineTab = { key: 'timeline', label: i18n.translate('xpack.apm.propertiesTable.tabs.timelineLabel', { defaultMessage: 'Timeline' }) }; +const metadataTab = { + key: 'metadata', + label: i18n.translate('xpack.apm.propertiesTable.tabs.metadataLabel', { + defaultMessage: 'Metadata' + }) +}; + interface Props { location: Location; transaction: Transaction; @@ -52,14 +42,12 @@ export function TransactionTabs({ urlParams, waterfall }: Props) { - const tabs = [timelineTab, ...getTabsFromObject(transaction)]; - const currentTab = getCurrentTab(tabs, urlParams.detailTab); - const agentName = transaction.agent.name; + const tabs = [timelineTab, metadataTab]; + const currentTab = + urlParams.detailTab === metadataTab.key ? metadataTab : timelineTab; return ( - + {tabs.map(({ key, label }) => { return ( @@ -92,14 +80,8 @@ export function TransactionTabs({ waterfall={waterfall} /> ) : ( - - - + )} - + ); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx new file mode 100644 index 00000000000000..605429eddc6e7a --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiCallOut, EuiHorizontalRule } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { idx } from '../../../../../../../../common/idx'; +import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction'; +import { ElasticDocsLink } from '../../../../../../shared/Links/ElasticDocsLink'; + +export function DroppedSpansWarning({ + transactionDoc +}: { + transactionDoc: Transaction; +}) { + const dropped = idx(transactionDoc, _ => _.transaction.span_count.dropped); + if (!dropped) { + return null; + } + + return ( + + + {i18n.translate( + 'xpack.apm.transactionDetails.transFlyout.callout.agentDroppedSpansMessage', + { + defaultMessage: + 'The APM agent that reported this transaction dropped {dropped} spans or more based on its configuration.', + values: { dropped } + } + )}{' '} + + {i18n.translate( + 'xpack.apm.transactionDetails.transFlyout.callout.learnMoreAboutDroppedSpansLinkText', + { + defaultMessage: 'Learn more about dropped spans.' + } + )} + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx index 57104c021f4039..a857b331065c21 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx @@ -5,78 +5,51 @@ */ import { - EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFlyoutBody, EuiFlyoutHeader, EuiHorizontalRule, - EuiLink, EuiPortal, + EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Location } from 'history'; import React from 'react'; -import { idx } from '../../../../../../../../common/idx'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction'; -import { IUrlParams } from '../../../../../../../store/urlParams'; -import { DROPPED_SPANS_DOCS } from '../../../../../../../utils/documentation/apm-get-started'; import { TransactionActionMenu } from '../../../../../../shared/TransactionActionMenu/TransactionActionMenu'; import { StickyTransactionProperties } from '../../../StickyTransactionProperties'; -import { TransactionPropertiesTableForFlyout } from '../../../TransactionPropertiesTableForFlyout'; import { FlyoutTopLevelProperties } from '../FlyoutTopLevelProperties'; import { ResponsiveFlyout } from '../ResponsiveFlyout'; +import { TransactionMetadata } from '../../../../../../shared/MetadataTable/TransactionMetadata'; +import { DroppedSpansWarning } from './DroppedSpansWarning'; interface Props { onClose: () => void; transaction?: Transaction; - location: Location; - urlParams: IUrlParams; errorCount: number; traceRootDuration?: number; } -function DroppedSpansWarning({ - transactionDoc +function TransactionPropertiesTable({ + transaction }: { - transactionDoc: Transaction; + transaction: Transaction; }) { - const dropped = idx(transactionDoc, _ => _.transaction.span_count.dropped); - if (!dropped) { - return null; - } - return ( - - - {i18n.translate( - 'xpack.apm.transactionDetails.transFlyout.callout.agentDroppedSpansMessage', - { - defaultMessage: - 'The APM agent that reported this transaction dropped {dropped} spans or more based on its configuration.', - values: { dropped } - } - )}{' '} - - {i18n.translate( - 'xpack.apm.transactionDetails.transFlyout.callout.learnMoreAboutDroppedSpansLinkText', - { - defaultMessage: 'Learn more about dropped spans.' - } - )} - - - - +
+ +

Metadata

+
+ + +
); } export function TransactionFlyout({ transaction: transactionDoc, onClose, - location, - urlParams, errorCount, traceRootDuration }: Props) { @@ -117,11 +90,7 @@ export function TransactionFlyout({ /> - + diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx index e7592d86907355..cc401c3f1007d2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx @@ -87,7 +87,7 @@ export class Waterfall extends Component { }; public getFlyOut = () => { - const { waterfall, location, urlParams } = this.props; + const { waterfall, urlParams } = this.props; const currentItem = urlParams.waterfallItemId && @@ -116,8 +116,6 @@ export class Waterfall extends Component { diff --git a/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/FormattedValue.tsx b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/FormattedValue.tsx new file mode 100644 index 00000000000000..d33960fe5196bd --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/FormattedValue.tsx @@ -0,0 +1,42 @@ +/* + * 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 theme from '@elastic/eui/dist/eui_theme_light.json'; +import { isBoolean, isNumber, isObject } from 'lodash'; +import React from 'react'; +import styled from 'styled-components'; +import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; + +const EmptyValue = styled.span` + color: ${theme.euiColorMediumShade}; + text-align: left; +`; + +export function FormattedKey({ + k, + value +}: { + k: string; + value: unknown; +}): JSX.Element { + if (value == null) { + return {k}; + } + + return {k}; +} + +export function FormattedValue({ value }: { value: any }): JSX.Element { + if (isObject(value)) { + return
{JSON.stringify(value, null, 4)}
; + } else if (isBoolean(value) || isNumber(value)) { + return {String(value)}; + } else if (!value) { + return {NOT_AVAILABLE_LABEL}; + } + + return {value}; +} diff --git a/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx new file mode 100644 index 00000000000000..deecd955293bb4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx @@ -0,0 +1,107 @@ +/* + * 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 React from 'react'; +import { DottedKeyValueTable } from '..'; +import { cleanup, render } from 'react-testing-library'; + +function getKeys(output: ReturnType) { + const keys = output.getAllByTestId('dot-key'); + return Array.isArray(keys) ? keys.map(node => node.textContent) : []; +} + +function getValues(output: ReturnType) { + const values = output.getAllByTestId('value'); + return Array.isArray(values) ? values.map(node => node.textContent) : []; +} + +describe('DottedKeyValueTable', () => { + afterEach(cleanup); + + it('should display a nested object with alpha-ordered, dot notation keys and values', () => { + const data = { + name: { + first: 'Jo', + last: 'Smith' + }, + age: 29, + active: true, + useless: false, + start: null, + end: undefined, + nested: { + b: { + c: 'ccc' + }, + a: 'aaa' + } + }; + const output = render(); + const rows = output.container.querySelectorAll('tr'); + expect(rows.length).toEqual(9); + + expect(getKeys(output)).toEqual([ + 'active', + 'age', + 'end', + 'name.first', + 'name.last', + 'nested.a', + 'nested.b.c', + 'start', + 'useless' + ]); + + expect(getValues(output)).toEqual([ + 'true', + '29', + 'N/A', + 'Jo', + 'Smith', + 'aaa', + 'ccc', + 'N/A', + 'false' + ]); + }); + + it('should respect max depth', () => { + const data = { + nested: { b: { c: 'ccc' }, a: 'aaa' } + }; + const output = render(); + const rows = output.container.querySelectorAll('tr'); + expect(rows.length).toEqual(2); + + expect(getKeys(output)).toEqual(['nested.a', 'nested.b']); + + expect(getValues(output)).toEqual([ + 'aaa', + JSON.stringify({ c: 'ccc' }, null, 4) + ]); + }); + + it('should prepend a provided parent key to all of the dot-notation keys', () => { + const data = { + name: { + first: 'Jo', + last: 'Smith' + }, + age: 29, + active: true + }; + const output = render(); + const rows = output.container.querySelectorAll('tr'); + expect(rows.length).toEqual(4); + + expect(getKeys(output)).toEqual([ + 'top.active', + 'top.age', + 'top.name.first', + 'top.name.last' + ]); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx new file mode 100644 index 00000000000000..baeea829f401f2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx @@ -0,0 +1,87 @@ +/* + * 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 React, { TableHTMLAttributes } from 'react'; +import { compact, isObject } from 'lodash'; +import { + EuiTable, + EuiTableProps, + EuiTableBody, + EuiTableRow, + EuiTableRowCell +} from '@elastic/eui'; +import { StringMap } from '../../../../typings/common'; +import { FormattedValue } from './FormattedValue'; + +interface PathifyOptions { + maxDepth?: number; + parentKey?: string; + depth?: number; +} + +interface PathifyResult { + [key: string]: any; +} + +/** + * Converts a deeply-nested object into a one-level object + * with dot-notation paths as keys. + */ +export function pathify( + item: StringMap, + { maxDepth, parentKey = '', depth = 0 }: PathifyOptions +): PathifyResult { + return Object.keys(item) + .sort() + .reduce((pathified, key) => { + const currentKey = compact([parentKey, key]).join('.'); + if ((!maxDepth || depth + 1 <= maxDepth) && isObject(item[key])) { + return { + ...pathified, + ...pathify(item[key], { + maxDepth, + parentKey: currentKey, + depth: depth + 1 + }) + }; + } else { + return { ...pathified, [currentKey]: item[key] }; + } + }, {}); +} + +export function DottedKeyValueTable({ + data, + parentKey, + maxDepth, + tableProps = {} +}: { + data: StringMap; + parentKey?: string; + maxDepth?: number; + tableProps?: EuiTableProps & TableHTMLAttributes; +}) { + const pathified = pathify(data, { maxDepth, parentKey }); + const rows = Object.keys(pathified) + .sort() + .map(k => [k, pathified[k]]); + return ( + + + {rows.map(([key, value]) => ( + + + {key} + + + + + + ))} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/HeightRetainer/index.tsx b/x-pack/plugins/apm/public/components/shared/HeightRetainer/index.tsx deleted file mode 100644 index e6f4487312429f..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/HeightRetainer/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 React, { useEffect, useRef } from 'react'; - -export const HeightRetainer: React.SFC = props => { - const containerElement = useRef(null); - const minHeight = useRef(0); - - useEffect(() => { - if (containerElement.current) { - const currentHeight = containerElement.current.clientHeight; - if (minHeight.current < currentHeight) { - minHeight.current = currentHeight; - } - } - }); - - return ( -
- ); -}; diff --git a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx new file mode 100644 index 00000000000000..b63671f63a1e7f --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; +import { metadata } from 'ui/metadata'; + +// TODO: metadata should be read from a useContext hook in new platform +const STACK_VERSION = metadata.branch; + +// union type constisting of valid guide sections that we link to +type DocsSection = '/apm/get-started' | '/x-pack'; + +interface Props extends EuiLinkAnchorProps { + section: DocsSection; + path: string; +} + +export function ElasticDocsLink({ section, path, ...rest }: Props) { + const href = `https://www.elastic.co/guide/en${section}/${STACK_VERSION}${path}`; + return ; +} diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx new file mode 100644 index 00000000000000..0ecf133f23e730 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 React from 'react'; +import { ErrorMetadata } from '..'; +import { render, cleanup } from 'react-testing-library'; +import { APMError } from '../../../../../../typings/es_schemas/ui/APMError'; +import 'jest-dom/extend-expect'; +import { + expectTextsInDocument, + expectTextsNotInDocument +} from '../../../../../utils/testHelpers'; + +function getError() { + return ({ + labels: { someKey: 'labels value' }, + http: { someKey: 'http value' }, + host: { someKey: 'host value' }, + container: { someKey: 'container value' }, + service: { someKey: 'service value' }, + process: { someKey: 'process value' }, + agent: { someKey: 'agent value' }, + url: { someKey: 'url value' }, + user: { someKey: 'user value' }, + notIncluded: 'not included value', + error: { + notIncluded: 'error not included value', + custom: { + someKey: 'custom value' + } + } + } as unknown) as APMError; +} + +describe('ErrorMetadata', () => { + afterEach(cleanup); + + it('should render a error with all sections', () => { + const error = getError(); + const output = render(); + + // sections + expectTextsInDocument(output, [ + 'Labels', + 'HTTP', + 'Host', + 'Container', + 'Service', + 'Process', + 'Agent', + 'URL', + 'User', + 'Custom' + ]); + }); + + it('should render a error with all included dot notation keys', () => { + const error = getError(); + const output = render(); + + // included keys + expectTextsInDocument(output, [ + 'labels.someKey', + 'http.someKey', + 'host.someKey', + 'container.someKey', + 'service.someKey', + 'process.someKey', + 'agent.someKey', + 'url.someKey', + 'user.someKey', + 'error.custom.someKey' + ]); + + // excluded keys + expectTextsNotInDocument(output, ['notIncluded', 'error.notIncluded']); + }); + + it('should render a error with all included values', () => { + const error = getError(); + const output = render(); + + // included values + expectTextsInDocument(output, [ + 'labels value', + 'http value', + 'host value', + 'container value', + 'service value', + 'process value', + 'agent value', + 'url value', + 'user value', + 'custom value' + ]); + + // excluded values + expectTextsNotInDocument(output, [ + 'not included value', + 'error not included value' + ]); + }); + + it('should render a error with only the required sections', () => { + const error = {} as APMError; + const output = render(); + + // required sections should be found + expectTextsInDocument(output, ['Labels', 'User']); + + // optional sections should NOT be found + expectTextsNotInDocument(output, [ + 'HTTP', + 'Host', + 'Container', + 'Service', + 'Process', + 'Agent', + 'URL', + 'Custom' + ]); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx new file mode 100644 index 00000000000000..4891dbcfa921db --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; +import { MetadataTable } from '..'; +import { ERROR_METADATA_SECTIONS } from './sections'; +import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; + +interface Props { + error: APMError; +} + +export function ErrorMetadata({ error }: Props) { + return ; +} diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts new file mode 100644 index 00000000000000..de733579b1d34d --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts @@ -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 * as SECTION_LABELS from '../sectionLabels'; + +export const ERROR_METADATA_SECTIONS = [ + { + key: 'labels', + label: SECTION_LABELS.LABELS, + required: true + }, + { + key: 'http', + label: SECTION_LABELS.HTTP + }, + { + key: 'host', + label: SECTION_LABELS.HOST + }, + { + key: 'container', + label: SECTION_LABELS.CONTAINER + }, + { + key: 'service', + label: SECTION_LABELS.SERVICE + }, + { + key: 'process', + label: SECTION_LABELS.PROCESS + }, + { + key: 'agent', + label: SECTION_LABELS.AGENT + }, + { + key: 'url', + label: SECTION_LABELS.URL + }, + { + key: 'user', + label: SECTION_LABELS.USER, + required: true + }, + { + key: 'error.custom', + label: SECTION_LABELS.CUSTOM + } +]; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx new file mode 100644 index 00000000000000..8fa6424a7f9fdf --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -0,0 +1,129 @@ +/* + * 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 React from 'react'; +import { TransactionMetadata } from '..'; +import { render, cleanup } from 'react-testing-library'; +import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; +import 'jest-dom/extend-expect'; +import { + expectTextsInDocument, + expectTextsNotInDocument +} from '../../../../../utils/testHelpers'; + +function getTransaction() { + return ({ + labels: { someKey: 'labels value' }, + http: { someKey: 'http value' }, + host: { someKey: 'host value' }, + container: { someKey: 'container value' }, + service: { someKey: 'service value' }, + process: { someKey: 'process value' }, + agent: { someKey: 'agent value' }, + url: { someKey: 'url value' }, + user: { someKey: 'user value' }, + notIncluded: 'not included value', + transaction: { + notIncluded: 'transaction not included value', + custom: { + someKey: 'custom value' + } + } + } as unknown) as Transaction; +} + +describe('TransactionMetadata', () => { + afterEach(cleanup); + + it('should render a transaction with all sections', () => { + const transaction = getTransaction(); + const output = render(); + + // sections + expectTextsInDocument(output, [ + 'Labels', + 'HTTP', + 'Host', + 'Container', + 'Service', + 'Process', + 'Agent', + 'URL', + 'User', + 'Custom' + ]); + }); + + it('should render a transaction with all included dot notation keys', () => { + const transaction = getTransaction(); + const output = render(); + + // included keys + expectTextsInDocument(output, [ + 'labels.someKey', + 'http.someKey', + 'host.someKey', + 'container.someKey', + 'service.someKey', + 'process.someKey', + 'agent.someKey', + 'url.someKey', + 'user.someKey', + 'transaction.custom.someKey' + ]); + + // excluded keys + expectTextsNotInDocument(output, [ + 'notIncluded', + 'transaction.notIncluded' + ]); + }); + + it('should render a transaction with all included values', () => { + const transaction = getTransaction(); + const output = render(); + + // included values + expectTextsInDocument(output, [ + 'labels value', + 'http value', + 'host value', + 'container value', + 'service value', + 'process value', + 'agent value', + 'url value', + 'user value', + 'custom value' + ]); + + // excluded values + expectTextsNotInDocument(output, [ + 'not included value', + 'transaction not included value' + ]); + }); + + it('should render a transaction with only the required sections', () => { + const transaction = {} as Transaction; + const output = render(); + + // required sections should be found + expectTextsInDocument(output, ['Labels', 'User']); + + // optional sections should NOT be found + expectTextsNotInDocument(output, [ + 'HTTP', + 'Host', + 'Container', + 'Service', + 'Process', + 'Agent', + 'URL', + 'Custom' + ]); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx new file mode 100644 index 00000000000000..95072490b0f380 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx @@ -0,0 +1,23 @@ +/* + * 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 React from 'react'; +import { MetadataTable } from '..'; +import { TRANSACTION_METADATA_SECTIONS } from './sections'; +import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; + +interface Props { + transaction: Transaction; +} + +export function TransactionMetadata({ transaction }: Props) { + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts new file mode 100644 index 00000000000000..d299838a6c0070 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts @@ -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 * as SECTION_LABELS from '../sectionLabels'; + +export const TRANSACTION_METADATA_SECTIONS = [ + { + key: 'labels', + label: SECTION_LABELS.LABELS, + required: true + }, + { + key: 'http', + label: SECTION_LABELS.HTTP + }, + { + key: 'host', + label: SECTION_LABELS.HOST + }, + { + key: 'container', + label: SECTION_LABELS.CONTAINER + }, + { + key: 'service', + label: SECTION_LABELS.SERVICE + }, + { + key: 'process', + label: SECTION_LABELS.PROCESS + }, + { + key: 'agent', + label: SECTION_LABELS.AGENT + }, + { + key: 'url', + label: SECTION_LABELS.URL + }, + { + key: 'user', + label: SECTION_LABELS.USER, + required: true + }, + { + key: 'transaction.custom', + label: SECTION_LABELS.CUSTOM + } +]; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx new file mode 100644 index 00000000000000..216a7a5997f100 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -0,0 +1,87 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiTitle +} from '@elastic/eui'; +import React from 'react'; +import { get, has } from 'lodash'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; +import { APMError } from '../../../../typings/es_schemas/ui/APMError'; +import { StringMap } from '../../../../typings/common'; +import { DottedKeyValueTable } from '../DottedKeyValueTable'; +import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; + +type MetadataItem = Transaction | APMError; + +interface Props { + item: MetadataItem; + sections: MetadataSection[]; +} + +export interface MetadataSection { + key: string; + label: string; + required?: boolean; +} + +export function MetadataTable({ item, sections }: Props) { + const filteredSections = sections.filter( + ({ key, required }) => required || has(item, key) + ); + return ( + + + + + + How to add labels and other data + + + + + {filteredSections.map(section => ( +
+ +
{section.label}
+
+ +
+ +
+ ))} +
+ ); +} + +function Section({ + propData, + propKey +}: { + propData?: StringMap; + propKey?: string; +}) { + return ( + + {propData ? ( + + ) : ( + + {i18n.translate( + 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel', + { defaultMessage: 'No data available' } + )} + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/sectionLabels.ts b/x-pack/plugins/apm/public/components/shared/MetadataTable/sectionLabels.ts new file mode 100644 index 00000000000000..4c892bb36623d3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/sectionLabels.ts @@ -0,0 +1,74 @@ +/* + * 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'; + +export const LABELS = i18n.translate( + 'xpack.apm.metadataTable.section.labelsLabel', + { + defaultMessage: 'Labels' + } +); + +export const HTTP = i18n.translate( + 'xpack.apm.metadataTable.section.httpLabel', + { + defaultMessage: 'HTTP' + } +); + +export const HOST = i18n.translate( + 'xpack.apm.metadataTable.section.hostLabel', + { + defaultMessage: 'Host' + } +); + +export const CONTAINER = i18n.translate( + 'xpack.apm.metadataTable.section.containerLabel', + { + defaultMessage: 'Container' + } +); + +export const SERVICE = i18n.translate( + 'xpack.apm.metadataTable.section.serviceLabel', + { + defaultMessage: 'Service' + } +); + +export const PROCESS = i18n.translate( + 'xpack.apm.metadataTable.section.processLabel', + { + defaultMessage: 'Process' + } +); + +export const AGENT = i18n.translate( + 'xpack.apm.metadataTable.section.agentLabel', + { + defaultMessage: 'Agent' + } +); + +export const URL = i18n.translate('xpack.apm.metadataTable.section.urlLabel', { + defaultMessage: 'URL' +}); + +export const USER = i18n.translate( + 'xpack.apm.metadataTable.section.userLabel', + { + defaultMessage: 'User' + } +); + +export const CUSTOM = i18n.translate( + 'xpack.apm.metadataTable.section.customLabel', + { + defaultMessage: 'Custom' + } +); diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx deleted file mode 100644 index 4db5018dad859c..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 theme from '@elastic/eui/dist/eui_theme_light.json'; -import { isBoolean, isNumber, isObject } from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { StringMap } from '../../../../typings/common'; -import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; -import { sortKeysByConfig } from './tabConfig'; - -const Table = styled.table` - font-family: ${fontFamilyCode}; - font-size: ${fontSize}; - width: 100%; -`; - -const Row = styled.tr` - border-bottom: ${px(1)} solid ${theme.euiColorLightShade}; - &:last-child { - border: 0; - } -`; - -const Cell = styled.td` - vertical-align: top; - padding: ${px(units.half)} 0; - line-height: 1.5; - - ${Row}:first-child> & { - padding-top: 0; - } - - ${Row}:last-child> & { - padding-bottom: 0; - } - - &:first-child { - width: ${px(units.unit * 12)}; - font-weight: bold; - } -`; - -const EmptyValue = styled.span` - color: ${theme.euiColorMediumShade}; -`; - -export function FormattedKey({ - k, - value -}: { - k: string; - value: unknown; -}): JSX.Element { - if (value == null) { - return {k}; - } - - return {k}; -} - -export function FormattedValue({ value }: { value: any }): JSX.Element { - if (isObject(value)) { - return
{JSON.stringify(value, null, 4)}
; - } else if (isBoolean(value) || isNumber(value)) { - return {String(value)}; - } else if (!value) { - return {NOT_AVAILABLE_LABEL}; - } - - return {value}; -} - -export function NestedValue({ - parentKey, - value, - depth -}: { - value: unknown; - depth: number; - parentKey?: string; -}): JSX.Element { - const MAX_LEVEL = 3; - if (depth < MAX_LEVEL && isObject(value)) { - return ( - - ); - } - - return ; -} - -export function NestedKeyValueTable({ - data, - parentKey, - depth -}: { - data: StringMap; - parentKey?: string; - depth: number; -}): JSX.Element { - return ( - - - {sortKeysByConfig(data, parentKey).map(key => ( - - - - - - - - - ))} - -
- ); -} diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/NestedKeyValueTable.test.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/NestedKeyValueTable.test.tsx deleted file mode 100644 index a749a741b93cfd..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/NestedKeyValueTable.test.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { mount, shallow } from 'enzyme'; -import 'jest-styled-components'; -import React from 'react'; -import { - FormattedKey, - FormattedValue, - NestedKeyValueTable, - NestedValue -} from '../NestedKeyValueTable'; - -describe('NestedKeyValueTable component', () => { - it('should render with data', () => { - const testData = { - a: 1, - b: 2, - c: [3, 4, 5], - d: { aa: 1, bb: 2 } - }; - expect( - shallow() - ).toMatchSnapshot(); - }); - - it('should render an empty table if there is no data', () => { - expect( - shallow() - ).toMatchSnapshot(); - }); -}); - -describe('NestedValue component', () => { - it('should render a formatted value when depth is 0', () => { - const wrapper = shallow( - - ); - - expect( - wrapper.equals( - - ) - ).toBe(true); - }); - - it('should render a formatted value when depth > 0 but value is not an object', () => { - expect( - shallow() - ).toMatchSnapshot(); - }); - - it('should render a nested KV Table when depth > 0 and value is an object', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); - }); -}); - -describe('FormattedValue component', () => { - it('should render an object', () => { - expect(mount()).toMatchSnapshot(); - }); - - it('should render an array', () => { - expect(mount()).toMatchSnapshot(); - }); - - it('should render a boolean', () => { - expect(mount()).toMatchSnapshot(); - expect(mount()).toMatchSnapshot(); - }); - - it('should render a number', () => { - expect(mount()).toMatchSnapshot(); - }); - - it('should render a string', () => { - expect(mount()).toMatchSnapshot(); - }); - - it('should render null', () => { - expect(mount()).toMatchSnapshot(); - }); - - it('should render undefined', () => { - expect(mount()).toMatchSnapshot(); - }); -}); - -describe('FormattedKey component', () => { - it('should render when the value is null or undefined', () => { - expect(mount()).toMatchSnapshot(); - expect( - mount() - ).toMatchSnapshot(); - }); - - it('should render when the value is defined', () => { - expect(mount()).toMatchSnapshot(); - expect(mount()).toMatchSnapshot(); - expect(mount()).toMatchSnapshot(); - }); -}); 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 deleted file mode 100644 index 0549bd3145e29e..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/PropertiesTable.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 { PropertiesTable, TabHelpMessage } from '..'; -import * as agentDocs from '../../../../utils/documentation/agents'; - -describe('PropertiesTable', () => { - describe('PropertiesTable component', () => { - it('should render with data', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); - }); - - it("should render empty when data isn't present", () => { - expect( - shallow() - ).toMatchSnapshot(); - }); - - it('should still render NestedKeyValueTable even when data has no keys', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); - }); - }); - - describe('TabHelpMessage component', () => { - const tabKey = 'user'; - const agentName = 'nodejs'; - - it('should render when docs are returned', () => { - jest - .spyOn(agentDocs, 'getAgentDocUrlForTab') - .mockImplementation(() => 'mock-url'); - - expect( - shallow() - ).toMatchSnapshot(); - expect(agentDocs.getAgentDocUrlForTab).toHaveBeenCalledWith( - tabKey, - agentName - ); - }); - - it('should render null empty string when no docs are returned', () => { - jest - .spyOn(agentDocs, 'getAgentDocUrlForTab') - .mockImplementation(() => undefined); - - expect( - shallow() - ).toMatchSnapshot(); - }); - }); -}); 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 deleted file mode 100644 index 55f330b9b9322f..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/NestedKeyValueTable.test.tsx.snap +++ /dev/null @@ -1,288 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FormattedKey component should render when the value is defined 1`] = ` - - testKey - -`; - -exports[`FormattedKey component should render when the value is defined 2`] = ` - - testKey - -`; - -exports[`FormattedKey component should render when the value is defined 3`] = ` - - testKey - -`; - -exports[`FormattedKey component should render when the value is null or undefined 1`] = ` -.c0 { - color: #98a2b3; -} - - - - - testKey - - - -`; - -exports[`FormattedKey component should render when the value is null or undefined 2`] = ` -.c0 { - color: #98a2b3; -} - - - - - testKey - - - -`; - -exports[`FormattedValue component should render a boolean 1`] = ` - - true - -`; - -exports[`FormattedValue component should render a boolean 2`] = ` - - false - -`; - -exports[`FormattedValue component should render a number 1`] = ` - - 243 - -`; - -exports[`FormattedValue component should render a string 1`] = ` - - hey ok cool - -`; - -exports[`FormattedValue component should render an array 1`] = ` - -
-    [
-    1,
-    2,
-    3
-]
-  
-
-`; - -exports[`FormattedValue component should render an object 1`] = ` - -
-    {
-    "a": "ok"
-}
-  
-
-`; - -exports[`FormattedValue component should render null 1`] = ` -.c0 { - color: #98a2b3; -} - - - - - N/A - - - -`; - -exports[`FormattedValue component should render undefined 1`] = ` -.c0 { - color: #98a2b3; -} - - - - - N/A - - - -`; - -exports[`NestedKeyValueTable component should render an empty table if there is no data 1`] = ` - - - -`; - -exports[`NestedKeyValueTable component should render with data 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`NestedValue component should render a formatted value when depth > 0 but value is not an object 1`] = ` - -`; - -exports[`NestedValue component should render a nested KV Table when depth > 0 and value is an object 1`] = ` - -`; diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/PropertiesTable.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/PropertiesTable.test.tsx.snap deleted file mode 100644 index e9a6d7f2733ffe..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/__snapshots__/PropertiesTable.test.tsx.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropertiesTable PropertiesTable component should render empty when data isn't present 1`] = ` - - - No data available - - - -`; - -exports[`PropertiesTable PropertiesTable component should render with data 1`] = ` - - - - -`; - -exports[`PropertiesTable PropertiesTable component should still render NestedKeyValueTable even when data has no keys 1`] = ` - - - - -`; - -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 deleted file mode 100644 index 61d2fcf026652b..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/__test__/tabConfig.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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. - */ - -jest.mock('../tabConfigConst', () => { - return { - TAB_CONFIG: [ - { - key: 'testProperty', - label: 'testPropertyLabel', - required: false, - presortedKeys: ['name', 'age'] - }, - { - key: 'optionalProperty', - label: 'optionalPropertyLabel', - required: false - }, - { - key: 'requiredProperty', - label: 'requiredPropertyLabel', - required: true - } - ] - }; -}); - -import * as propertyConfig from '../tabConfig'; -const { getTabsFromObject, sortKeysByConfig } = propertyConfig; - -describe('tabConfig', () => { - 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' - ]); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx deleted file mode 100644 index 9fc4ccfe30c6a6..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 { 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 React from 'react'; -import styled from 'styled-components'; -import { StringMap } from '../../../../typings/common'; -import { AgentName } from '../../../../typings/es_schemas/ui/fields/Agent'; -import { fontSize, fontSizes, px, unit, units } from '../../../style/variables'; -import { getAgentDocUrlForTab } from '../../../utils/documentation/agents'; -import { NestedKeyValueTable } from './NestedKeyValueTable'; -import { PropertyTabKey } from './tabConfig'; - -const TableContainer = styled.div` - padding-bottom: ${px(units.double)}; -`; - -const TableInfo = styled.div` - padding: ${px(unit)} 0 0; - text-align: center; - font-size: ${fontSize}; - color: ${theme.euiColorDarkShade}; - line-height: 1.5; -`; - -const TableInfoHeader = styled(TableInfo)` - font-size: ${fontSizes.large}; - color: ${theme.euiColorDarkestShade}; -`; - -const EuiIconWithSpace = styled(EuiIcon)` - margin-right: ${px(units.half)}; -`; - -function getTabHelpText(tabKey: PropertyTabKey) { - switch (tabKey) { - case 'user': - return i18n.translate( - 'xpack.apm.propertiesTable.userTab.agentFeatureText', - { - defaultMessage: - 'You can configure your agent to add contextual information about your users.' - } - ); - case 'labels': - return i18n.translate( - 'xpack.apm.propertiesTable.labelsTab.agentFeatureText', - { - defaultMessage: - 'You can configure your agent to add filterable tags on transactions.' - } - ); - case 'transaction.custom': - case 'error.custom': - return i18n.translate( - 'xpack.apm.propertiesTable.customTab.agentFeatureText', - { - defaultMessage: - 'You can configure your agent to add custom contextual information on transactions.' - } - ); - } -} - -export function TabHelpMessage({ - tabKey, - agentName -}: { - tabKey?: PropertyTabKey; - agentName?: AgentName; -}) { - if (!tabKey) { - return null; - } - const docsUrl = getAgentDocUrlForTab(tabKey, agentName); - if (!docsUrl) { - return null; - } - - return ( - - - {getTabHelpText(tabKey)}{' '} - - {i18n.translate( - 'xpack.apm.propertiesTable.agentFeature.learnMoreLinkLabel', - { defaultMessage: 'Learn more in the documentation.' } - )} - - - ); -} - -export function PropertiesTable({ - propData, - propKey, - agentName -}: { - propData?: StringMap; - propKey?: PropertyTabKey; - agentName?: AgentName; -}) { - return ( - - {propData ? ( - - ) : ( - - {i18n.translate( - 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel', - { defaultMessage: 'No data available' } - )} - - )} - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/propertyConfig.ts b/x-pack/plugins/apm/public/components/shared/PropertiesTable/propertyConfig.ts deleted file mode 100644 index 9c11c89a476191..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/propertyConfig.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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'; - -export interface Tab { - key: string; - label: string; -} - -export const PROPERTY_CONFIG = [ - { - 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/PropertiesTable/tabConfig.ts b/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts deleted file mode 100644 index 0f5a64580b46b2..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfig.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { get, indexBy, uniq } from 'lodash'; -import { first, has } from 'lodash'; -import { StringMap } from '../../../../typings/common'; -import { APMError } from '../../../../typings/es_schemas/ui/APMError'; -import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; -import { - PropertyTab, - PropertyTabKey, - TAB_CONFIG, - TabConfig -} from './tabConfigConst'; - -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 { TAB_CONFIG, TabConfig, PropertyTab, PropertyTabKey }; diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfigConst.ts b/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfigConst.ts deleted file mode 100644 index cd3cc7bd112901..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/tabConfigConst.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 { APMError } from '../../../../typings/es_schemas/ui/APMError'; -import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; - -export type PropertyTabKey = - | keyof Transaction - | keyof APMError - | 'transaction.custom' - | 'error.custom'; - -export interface PropertyTab { - key: PropertyTabKey; - label: string; -} - -export interface TabConfig extends PropertyTab { - required: boolean; - presortedKeys: string[]; -} - -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 1961327661f8a9..dfc9c95c9143a0 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -17,7 +17,7 @@ import { units } from '../../../style/variables'; import { Ellipsis } from '../Icons'; -import { PropertiesTable } from '../PropertiesTable'; +import { DottedKeyValueTable } from '../DottedKeyValueTable'; const VariablesContainer = styled.div` background: ${theme.euiColorEmptyShade}; @@ -66,7 +66,7 @@ export class Variables extends React.Component { {this.state.isVisible && ( - + )} diff --git a/x-pack/plugins/apm/public/utils/documentation/agents.ts b/x-pack/plugins/apm/public/utils/documentation/agents.ts deleted file mode 100644 index 8ebbbf6464d929..00000000000000 --- a/x-pack/plugins/apm/public/utils/documentation/agents.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { AgentName } from '../../../typings/es_schemas/ui/fields/Agent'; -import { PropertyTabKey } from '../../components/shared/PropertiesTable/tabConfig'; - -const AGENT_URL_ROOT = 'https://www.elastic.co/guide/en/apm/agent'; - -type DocUrls = { - [tabKey in PropertyTabKey]?: { [agentName in AgentName]: string | undefined } -}; - -const customUrls = { - 'js-base': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-set-custom-context`, - 'rum-js': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-set-custom-context`, - java: undefined, - nodejs: `${AGENT_URL_ROOT}/nodejs/2.x/agent-api.html#apm-set-custom-context`, - python: `${AGENT_URL_ROOT}/python/4.x/api.html#api-set-custom-context`, - dotnet: undefined, - ruby: `${AGENT_URL_ROOT}/ruby/2.x/context.html#_adding_custom_context`, - go: undefined -}; - -const AGENT_DOC_URLS: DocUrls = { - user: { - 'js-base': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-set-user-context`, - 'rum-js': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-set-user-context`, - java: `${AGENT_URL_ROOT}/java/1.x/public-api.html#api-transaction-set-user`, - nodejs: `${AGENT_URL_ROOT}/nodejs/2.x/agent-api.html#apm-set-user-context`, - python: `${AGENT_URL_ROOT}/python/4.x/api.html#api-set-user-context`, - dotnet: undefined, - ruby: `${AGENT_URL_ROOT}/ruby/2.x/context.html#_providing_info_about_the_user`, - go: undefined - }, - labels: { - 'js-base': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-add-tags`, - 'rum-js': `${AGENT_URL_ROOT}/js-base/4.x/api.html#apm-add-tags`, - java: `${AGENT_URL_ROOT}/java/1.x/public-api.html#api-transaction-add-tag`, - nodejs: `${AGENT_URL_ROOT}/nodejs/2.x/agent-api.html#apm-set-tag`, - python: `${AGENT_URL_ROOT}/python/4.x/api.html#api-tag`, - dotnet: `${AGENT_URL_ROOT}/dotnet/current/public-api.html#api-transaction-tags`, - ruby: `${AGENT_URL_ROOT}/ruby/2.x/context.html#_adding_tags`, - go: undefined - }, - 'transaction.custom': customUrls, - 'error.custom': customUrls -}; - -export function getAgentDocUrlForTab( - tabKey: PropertyTabKey, - agentName?: AgentName -) { - const agentUrls = AGENT_DOC_URLS[tabKey]; - if (agentUrls && agentName) { - return agentUrls[agentName]; - } -} diff --git a/x-pack/plugins/apm/public/utils/documentation/apm-get-started.ts b/x-pack/plugins/apm/public/utils/documentation/apm-get-started.ts deleted file mode 100644 index 7b8c185c41e244..00000000000000 --- a/x-pack/plugins/apm/public/utils/documentation/apm-get-started.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { metadata } from 'ui/metadata'; -const STACK_VERSION = metadata.branch; - -export const DROPPED_SPANS_DOCS = `https://www.elastic.co/guide/en/apm/get-started/${STACK_VERSION}/transaction-spans.html#dropped-spans`; diff --git a/x-pack/plugins/apm/public/utils/documentation/xpack.ts b/x-pack/plugins/apm/public/utils/documentation/xpack.ts deleted file mode 100644 index 58960dd2d88c75..00000000000000 --- a/x-pack/plugins/apm/public/utils/documentation/xpack.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { metadata } from 'ui/metadata'; -const STACK_VERSION = metadata.branch; - -const XPACK_URL_ROOT = `https://www.elastic.co/guide/en/x-pack/${STACK_VERSION}`; - -export const XPACK_DOCS = { - xpackEmails: `${XPACK_URL_ROOT}/actions-email.html#configuring-email`, - xpackWatcher: `${XPACK_URL_ROOT}/watcher-getting-started.html` -}; diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 2072fa69e66fd7..a0c8c1305a78ad 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -72,3 +72,25 @@ export function delay(ms: number) { // Await this when you need to "flush" promises to immediately resolve or throw in tests export const tick = () => new Promise(resolve => setImmediate(resolve, 0)); + +export function expectTextsNotInDocument(output: any, texts: string[]) { + texts.forEach(text => { + try { + output.getByText(text); + } catch (err) { + if (err.message.startsWith('Unable to find an element with the text:')) { + return; + } else { + throw err; + } + } + + throw new Error(`Unexpected text found: ${text}`); + }); +} + +export function expectTextsInDocument(output: any, texts: string[]) { + texts.forEach(text => { + expect(output.getByText(text)).toBeInTheDocument(); + }); +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fceb0ec92048ce..bf63c99b9045bd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3287,16 +3287,10 @@ "xpack.apm.metrics.transactionChart.transactionDurationLabel": "事务持续时间", "xpack.apm.metrics.transactionChart.transactionsPerMinuteLabel": "每分钟事务数", "xpack.apm.notAvailableLabel": "不适用", - "xpack.apm.propertiesTable.agentFeature.learnMoreLinkLabel": "在文档中详细了解。", "xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel": "没有可用数据", - "xpack.apm.propertiesTable.customTab.agentFeatureText": "您可以配置代理以添加有关事务的定制上下文信息。", "xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel": "异常堆栈追溯", "xpack.apm.propertiesTable.tabs.logStacktraceLabel": "日志堆栈追溯", - "xpack.apm.propertiesTable.tabs.processLabel": "进程", - "xpack.apm.propertiesTable.tabs.serviceLabel": "服务", "xpack.apm.propertiesTable.tabs.timelineLabel": "时间线", - "xpack.apm.propertiesTable.tabs.userLabel": "用户", - "xpack.apm.propertiesTable.userTab.agentFeatureText": "您可以配置代理以添加有关用户的上下文信息。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription": "当前有 {serviceName}({transactionType})的作业正在运行。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText": "查看现有作业", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsTitle": "作业已存在", diff --git a/yarn.lock b/yarn.lock index dd8ab8f5f58d76..81ccb3ca79b178 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8151,7 +8151,12 @@ css-what@2.1, css-what@^2.1.2: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css@2.X, css@^2.2.1, css@^2.2.4: +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@2.X, css@^2.2.1, css@^2.2.3, css@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== @@ -14770,6 +14775,20 @@ jest-docblock@^24.0.0: dependencies: detect-newline "^2.1.0" +jest-dom@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/jest-dom/-/jest-dom-3.1.3.tgz#9490de549c02366fe586f23bdafffd8374bd1d65" + integrity sha512-V9LdySiA74/spcAKEG3FRMRKnisKlcYr3EeCNYI4n7CWNE7uYg5WoBUHeGXirjWjRYLLZ5vx8rUaR/6x6o75oQ== + dependencies: + chalk "^2.4.1" + css "^2.2.3" + css.escape "^1.5.1" + jest-diff "^24.0.0" + jest-matcher-utils "^24.0.0" + lodash "^4.17.11" + pretty-format "^24.0.0" + redent "^2.0.0" + jest-each@^24.0.0: version "24.0.0" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.0.0.tgz#10987a06b21c7ffbfb7706c89d24c52ed864be55"