mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* [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 # Conflicts: # x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap # x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx # x-pack/plugins/apm/public/components/shared/PropertiesTable/index.tsx * Typescript fixes
This commit is contained in:
parent
69fc0d8186
commit
e757cb3e1c
26 changed files with 745 additions and 587 deletions
|
@ -38,7 +38,7 @@ exports[`Error PROCESSOR_EVENT 1`] = `"error"`;
|
|||
|
||||
exports[`Error PROCESSOR_NAME 1`] = `"error"`;
|
||||
|
||||
exports[`Error SERVICE_AGENT_NAME 1`] = `"agent name"`;
|
||||
exports[`Error SERVICE_AGENT_NAME 1`] = `"java"`;
|
||||
|
||||
exports[`Error SERVICE_LANGUAGE_NAME 1`] = `"nodejs"`;
|
||||
|
||||
|
@ -116,7 +116,7 @@ exports[`Span PROCESSOR_EVENT 1`] = `"span"`;
|
|||
|
||||
exports[`Span PROCESSOR_NAME 1`] = `"transaction"`;
|
||||
|
||||
exports[`Span SERVICE_AGENT_NAME 1`] = `"agent name"`;
|
||||
exports[`Span SERVICE_AGENT_NAME 1`] = `"java"`;
|
||||
|
||||
exports[`Span SERVICE_LANGUAGE_NAME 1`] = `undefined`;
|
||||
|
||||
|
@ -194,7 +194,7 @@ exports[`Transaction PROCESSOR_EVENT 1`] = `"transaction"`;
|
|||
|
||||
exports[`Transaction PROCESSOR_NAME 1`] = `"transaction"`;
|
||||
|
||||
exports[`Transaction SERVICE_AGENT_NAME 1`] = `"agent name"`;
|
||||
exports[`Transaction SERVICE_AGENT_NAME 1`] = `"java"`;
|
||||
|
||||
exports[`Transaction SERVICE_LANGUAGE_NAME 1`] = `"nodejs"`;
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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)
|
||||
];
|
||||
}
|
|
@ -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(
|
||||
<StickyErrorProperties error={error} transaction={transaction} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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 <Fragment>{NOT_AVAILABLE_LABEL}</Fragment>;
|
||||
}
|
||||
|
||||
const isSampled = error.transaction.sampled;
|
||||
if (!isSampled) {
|
||||
return <Fragment>{transaction.transaction.id}</Fragment>;
|
||||
}
|
||||
|
||||
const path = `/${
|
||||
transaction.service.name
|
||||
}/transactions/${legacyEncodeURIComponent(
|
||||
transaction.transaction.type
|
||||
)}/${legacyEncodeURIComponent(transaction.transaction.name)}`;
|
||||
|
||||
return (
|
||||
<KibanaLink
|
||||
hash={path}
|
||||
query={{
|
||||
transactionId: transaction.transaction.id,
|
||||
traceId: transaction.trace.id
|
||||
}}
|
||||
>
|
||||
{transaction.transaction.id}
|
||||
</KibanaLink>
|
||||
);
|
||||
}
|
||||
|
||||
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: <TransactionLink transaction={transaction} error={error} />,
|
||||
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 <StickyProperties stickyProperties={stickyProperties} />;
|
||||
}
|
|
@ -1,48 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DetailView should render Discover button 1`] = `
|
||||
<DiscoverErrorLink
|
||||
error={
|
||||
Object {
|
||||
"@timestamp": "myTimestamp",
|
||||
"error": Object {
|
||||
"exception": Object {
|
||||
"handled": true,
|
||||
},
|
||||
},
|
||||
"http": Object {
|
||||
"request": Object {
|
||||
"method": "GET",
|
||||
},
|
||||
},
|
||||
"service": Object {
|
||||
"name": "myService",
|
||||
},
|
||||
"transaction": Object {
|
||||
"id": "myTransactionId",
|
||||
"sampled": true,
|
||||
},
|
||||
"url": Object {
|
||||
"full": "myUrl",
|
||||
},
|
||||
"user": Object {
|
||||
"id": "myUserId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
iconSide="left"
|
||||
iconType="discoverApp"
|
||||
type="button"
|
||||
>
|
||||
View 10 occurrences in Discover
|
||||
</EuiButtonEmpty>
|
||||
</DiscoverErrorLink>
|
||||
`;
|
||||
|
||||
exports[`DetailView should render StickyProperties 1`] = `
|
||||
exports[`StickyErrorProperties should render StickyProperties 1`] = `
|
||||
<StickyProperties
|
||||
stickyProperties={
|
||||
Array [
|
||||
|
@ -144,60 +102,3 @@ exports[`DetailView should render StickyProperties 1`] = `
|
|||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`DetailView should render TabContent 1`] = `
|
||||
<TabContent
|
||||
currentTab={
|
||||
Object {
|
||||
"key": "exception_stacktrace",
|
||||
"label": "Exception stacktrace",
|
||||
}
|
||||
}
|
||||
error={
|
||||
Object {
|
||||
"@timestamp": "myTimestamp",
|
||||
"context": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`DetailView should render tabs 1`] = `
|
||||
<EuiTabs
|
||||
expand={false}
|
||||
size="m"
|
||||
>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={true}
|
||||
key="exception_stacktrace"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Exception stacktrace
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="service"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Service
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="user"
|
||||
onClick={[Function]}
|
||||
>
|
||||
User
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="labels"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Labels
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
`;
|
|
@ -0,0 +1,100 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DetailView should render Discover button 1`] = `
|
||||
<DiscoverErrorLink
|
||||
error={
|
||||
Object {
|
||||
"@timestamp": "myTimestamp",
|
||||
"error": Object {
|
||||
"exception": Object {
|
||||
"handled": true,
|
||||
},
|
||||
},
|
||||
"http": Object {
|
||||
"request": Object {
|
||||
"method": "GET",
|
||||
},
|
||||
},
|
||||
"service": Object {
|
||||
"name": "myService",
|
||||
},
|
||||
"transaction": Object {
|
||||
"id": "myTransactionId",
|
||||
"sampled": true,
|
||||
},
|
||||
"url": Object {
|
||||
"full": "myUrl",
|
||||
},
|
||||
"user": Object {
|
||||
"id": "myUserId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
iconSide="left"
|
||||
iconType="discoverApp"
|
||||
type="button"
|
||||
>
|
||||
View 10 occurrences in Discover
|
||||
</EuiButtonEmpty>
|
||||
</DiscoverErrorLink>
|
||||
`;
|
||||
|
||||
exports[`DetailView should render TabContent 1`] = `
|
||||
<TabContent
|
||||
currentTab={
|
||||
Object {
|
||||
"key": "exception_stacktrace",
|
||||
"label": "Exception stacktrace",
|
||||
}
|
||||
}
|
||||
error={
|
||||
Object {
|
||||
"@timestamp": "myTimestamp",
|
||||
"context": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`DetailView should render tabs 1`] = `
|
||||
<EuiTabs
|
||||
expand={false}
|
||||
size="m"
|
||||
>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={true}
|
||||
key="exception_stacktrace"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Exception stacktrace
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="service"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Service
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="user"
|
||||
onClick={[Function]}
|
||||
>
|
||||
User
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="labels"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Labels
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
`;
|
|
@ -10,10 +10,9 @@ 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 +66,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 +75,9 @@ describe('DetailView', () => {
|
|||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
).find('StickyProperties');
|
||||
).find('StickyErrorProperties');
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render tabs', () => {
|
|
@ -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<ErrorGroupAPIResponse>;
|
||||
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: <TransactionLink transaction={transaction} error={error} />,
|
||||
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) {
|
|||
</HeaderContainer>
|
||||
|
||||
<PaddedContainer>
|
||||
<StickyProperties stickyProperties={stickyProperties} />
|
||||
<StickyErrorProperties error={error} transaction={transaction} />
|
||||
</PaddedContainer>
|
||||
|
||||
<EuiSpacer />
|
||||
|
@ -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 <Fragment>{NOT_AVAILABLE_LABEL}</Fragment>;
|
||||
}
|
||||
|
||||
const isSampled = idx(error, _ => _.transaction.sampled);
|
||||
if (!isSampled) {
|
||||
return <Fragment>{transaction.transaction.id}</Fragment>;
|
||||
}
|
||||
|
||||
const path = `/${
|
||||
transaction.service.name
|
||||
}/transactions/${legacyEncodeURIComponent(
|
||||
transaction.transaction.type
|
||||
)}/${legacyEncodeURIComponent(transaction.transaction.name)}`;
|
||||
|
||||
return (
|
||||
<KibanaLink
|
||||
hash={path}
|
||||
query={{
|
||||
transactionId: transaction.transaction.id,
|
||||
traceId: idx(transaction, _ => _.trace.id)
|
||||
}}
|
||||
>
|
||||
{transaction.transaction.id}
|
||||
</KibanaLink>
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<Props> = ({
|
|||
transaction,
|
||||
urlParams
|
||||
}) => {
|
||||
const tabs = getTabs(transaction);
|
||||
const tabs = getTabsFromObject(transaction);
|
||||
const currentTab = getCurrentTab(tabs, urlParams.flyoutDetailTab);
|
||||
const agentName = transaction.agent.name;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -18,11 +18,11 @@ import {
|
|||
import { Transaction } from '../../../../../typings/es_schemas/Transaction';
|
||||
import { IUrlParams } from '../../../../store/urlParams';
|
||||
import { px, units } from '../../../../style/variables';
|
||||
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';
|
||||
|
||||
|
@ -30,41 +30,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 (
|
||||
<div>
|
||||
|
@ -92,7 +85,7 @@ export function TransactionPropertiesTable({
|
|||
|
||||
<EuiSpacer />
|
||||
|
||||
{isTimelineTab ? (
|
||||
{currentTab.key === timelineTab.key ? (
|
||||
<WaterfallContainer
|
||||
transaction={transaction}
|
||||
location={location}
|
|
@ -21,7 +21,7 @@ import { IUrlParams } from '../../../../store/urlParams';
|
|||
import { TransactionLink } from '../../../shared/Links/TransactionLink';
|
||||
import { TransactionActionMenu } from '../../../shared/TransactionActionMenu/TransactionActionMenu';
|
||||
import { StickyTransactionProperties } from './StickyTransactionProperties';
|
||||
import { TransactionPropertiesTable } from './TransactionPropertiesTable';
|
||||
import { TransactionTabs } from './TransactionTabs';
|
||||
import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
|
||||
function MaybeViewTraceLink({
|
||||
|
@ -149,7 +149,7 @@ export const Transaction: React.SFC<Props> = ({
|
|||
|
||||
<EuiSpacer />
|
||||
|
||||
<TransactionPropertiesTable
|
||||
<TransactionTabs
|
||||
transaction={transaction}
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
|
|
|
@ -11,8 +11,7 @@ import styled from 'styled-components';
|
|||
import { NOT_AVAILABLE_LABEL } from 'x-pack/plugins/apm/common/i18n';
|
||||
import { StringMap } from '../../../../typings/common';
|
||||
import { fontFamilyCode, fontSize, px, units } from '../../../style/variables';
|
||||
|
||||
export type KeySorter = (data: StringMap, parentKey?: string) => 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({
|
|||
<NestedKeyValueTable
|
||||
data={value as StringMap}
|
||||
parentKey={parentKey}
|
||||
keySorter={keySorter}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
);
|
||||
|
@ -105,29 +101,22 @@ export function NestedValue({
|
|||
export function NestedKeyValueTable({
|
||||
data,
|
||||
parentKey,
|
||||
keySorter = Object.keys,
|
||||
depth
|
||||
}: {
|
||||
data: StringMap<unknown>;
|
||||
parentKey?: string;
|
||||
keySorter?: KeySorter;
|
||||
depth: number;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Table>
|
||||
<tbody>
|
||||
{keySorter(data, parentKey).map(key => (
|
||||
{sortKeysByConfig(data, parentKey).map(key => (
|
||||
<Row key={key}>
|
||||
<Cell>
|
||||
<FormattedKey k={key} value={data[key]} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<NestedValue
|
||||
parentKey={key}
|
||||
value={data[key]}
|
||||
keySorter={keySorter}
|
||||
depth={depth}
|
||||
/>
|
||||
<NestedValue parentKey={key} value={data[key]} depth={depth} />
|
||||
</Cell>
|
||||
</Row>
|
||||
))}
|
||||
|
|
|
@ -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(
|
||||
<PropertiesTable
|
||||
propData={{ a: 'hello', b: 'bananas' }}
|
||||
propKey="testPropKey"
|
||||
agentName="testAgentName"
|
||||
propKey="kubernetes"
|
||||
agentName="java"
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
|
@ -39,9 +25,7 @@ describe('PropertiesTable', () => {
|
|||
|
||||
it("should render empty when data isn't present", () => {
|
||||
expect(
|
||||
shallow(
|
||||
<PropertiesTable propKey="testPropKey" agentName="testAgentName" />
|
||||
)
|
||||
shallow(<PropertiesTable propKey="kubernetes" agentName="java" />)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -50,126 +34,40 @@ describe('PropertiesTable', () => {
|
|||
shallow(
|
||||
<PropertiesTable
|
||||
propData={{}}
|
||||
propKey="testPropKey"
|
||||
agentName="testAgentName"
|
||||
propKey="kubernetes"
|
||||
agentName="java"
|
||||
/>
|
||||
)
|
||||
).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(
|
||||
<AgentFeatureTipMessage
|
||||
featureName={featureName}
|
||||
agentName={agentName}
|
||||
/>
|
||||
)
|
||||
shallow(<TabHelpMessage tabKey={tabKey} agentName={agentName} />)
|
||||
).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(
|
||||
<AgentFeatureTipMessage
|
||||
featureName={featureName}
|
||||
agentName={agentName}
|
||||
/>
|
||||
)
|
||||
shallow(<TabHelpMessage tabKey={tabKey} agentName={agentName} />)
|
||||
).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;
|
||||
}
|
||||
|
|
|
@ -187,7 +187,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = `
|
|||
<styled.td>
|
||||
<NestedValue
|
||||
depth={0}
|
||||
keySorter={[Function]}
|
||||
parentKey="a"
|
||||
value={1}
|
||||
/>
|
||||
|
@ -205,7 +204,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = `
|
|||
<styled.td>
|
||||
<NestedValue
|
||||
depth={0}
|
||||
keySorter={[Function]}
|
||||
parentKey="b"
|
||||
value={2}
|
||||
/>
|
||||
|
@ -229,7 +227,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = `
|
|||
<styled.td>
|
||||
<NestedValue
|
||||
depth={0}
|
||||
keySorter={[Function]}
|
||||
parentKey="c"
|
||||
value={
|
||||
Array [
|
||||
|
@ -258,7 +255,6 @@ exports[`NestedKeyValueTable component should render with data 1`] = `
|
|||
<styled.td>
|
||||
<NestedValue
|
||||
depth={0}
|
||||
keySorter={[Function]}
|
||||
parentKey="d"
|
||||
value={
|
||||
Object {
|
||||
|
|
|
@ -1,8 +1,53 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropertiesTable AgentFeatureTipMessage component should render null empty string when no docs are returned 1`] = `""`;
|
||||
exports[`PropertiesTable PropertiesTable component should render empty when data isn't present 1`] = `
|
||||
<styled.div>
|
||||
<Styled(styled.div)>
|
||||
No data available
|
||||
</Styled(styled.div)>
|
||||
<TabHelpMessage
|
||||
agentName="java"
|
||||
tabKey="kubernetes"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
exports[`PropertiesTable AgentFeatureTipMessage component should render when docs are returned 1`] = `
|
||||
exports[`PropertiesTable PropertiesTable component should render with data 1`] = `
|
||||
<styled.div>
|
||||
<NestedKeyValueTable
|
||||
data={
|
||||
Object {
|
||||
"a": "hello",
|
||||
"b": "bananas",
|
||||
}
|
||||
}
|
||||
depth={1}
|
||||
parentKey="kubernetes"
|
||||
/>
|
||||
<TabHelpMessage
|
||||
agentName="java"
|
||||
tabKey="kubernetes"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
exports[`PropertiesTable PropertiesTable component should still render NestedKeyValueTable even when data has no keys 1`] = `
|
||||
<styled.div>
|
||||
<NestedKeyValueTable
|
||||
data={Object {}}
|
||||
depth={1}
|
||||
parentKey="kubernetes"
|
||||
/>
|
||||
<TabHelpMessage
|
||||
agentName="java"
|
||||
tabKey="kubernetes"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<styled.div>
|
||||
<Styled(EuiIcon)
|
||||
type="iInCircle"
|
||||
|
@ -20,50 +65,3 @@ exports[`PropertiesTable AgentFeatureTipMessage component should render when doc
|
|||
</EuiLink>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
exports[`PropertiesTable PropertiesTable component should render empty when data isn't present 1`] = `
|
||||
<styled.div>
|
||||
<Styled(styled.div)>
|
||||
No data available
|
||||
</Styled(styled.div)>
|
||||
<AgentFeatureTipMessage
|
||||
agentName="testAgentName"
|
||||
featureName="testPropKey"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
exports[`PropertiesTable PropertiesTable component should render with data 1`] = `
|
||||
<styled.div>
|
||||
<NestedKeyValueTable
|
||||
data={
|
||||
Object {
|
||||
"a": "hello",
|
||||
"b": "bananas",
|
||||
}
|
||||
}
|
||||
depth={1}
|
||||
keySorter={[Function]}
|
||||
parentKey="testPropKey"
|
||||
/>
|
||||
<AgentFeatureTipMessage
|
||||
agentName="testAgentName"
|
||||
featureName="testPropKey"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
||||
exports[`PropertiesTable PropertiesTable component should still render NestedKeyValueTable even when data has no keys 1`] = `
|
||||
<styled.div>
|
||||
<NestedKeyValueTable
|
||||
data={Object {}}
|
||||
depth={1}
|
||||
keySorter={[Function]}
|
||||
parentKey="testPropKey"
|
||||
/>
|
||||
<AgentFeatureTipMessage
|
||||
agentName="testAgentName"
|
||||
featureName="testPropKey"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<TableInfo>
|
||||
<EuiIconWithSpace type="iInCircle" />
|
||||
{getAgentFeatureText(featureName)}{' '}
|
||||
{getTabHelpText(tabKey)}{' '}
|
||||
<EuiLink target="_blank" rel="noopener" href={docsUrl}>
|
||||
{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<any>;
|
||||
propKey: string;
|
||||
agentName?: string;
|
||||
propKey?: PropertyTabKey;
|
||||
agentName?: AgentName;
|
||||
}) {
|
||||
return (
|
||||
<TableContainer>
|
||||
{propData ? (
|
||||
<NestedKeyValueTable
|
||||
data={propData}
|
||||
parentKey={propKey}
|
||||
keySorter={sortKeysByConfig}
|
||||
depth={1}
|
||||
/>
|
||||
<NestedKeyValueTable data={propData} parentKey={propKey} depth={1} />
|
||||
) : (
|
||||
<TableInfoHeader>
|
||||
{i18n.translate(
|
||||
|
@ -137,7 +118,8 @@ export function PropertiesTable({
|
|||
)}
|
||||
</TableInfoHeader>
|
||||
)}
|
||||
<AgentFeatureTipMessage featureName={propKey} agentName={agentName} />
|
||||
|
||||
<TabHelpMessage tabKey={propKey} agentName={agentName} />
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<T extends { key: string; label: string }>(
|
||||
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: []
|
||||
}
|
||||
];
|
|
@ -70,7 +70,7 @@ export class Variables extends React.Component<Props> {
|
|||
</VariablesToggle>
|
||||
{this.state.isVisible && (
|
||||
<VariablesTableContainer>
|
||||
<PropertiesTable propData={this.props.vars} propKey={'custom'} />
|
||||
<PropertiesTable propData={this.props.vars} />
|
||||
</VariablesTableContainer>
|
||||
)}
|
||||
</VariablesContainer>
|
||||
|
|
|
@ -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(
|
||||
<TransactionActionMenu transaction={transaction} location={location} />
|
||||
|
|
|
@ -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
|
||||
};
|
|
@ -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 APM_AGENT_FEATURE_DOCS: {
|
||||
[featureName: string]: AgentNamedValues;
|
||||
} = {
|
||||
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 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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -3371,7 +3371,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": "查看现有作业",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue