mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[APM] Link to related errors from Transaction detail page (#29313)
* Closes #21920 by: - linking to errors list filtered by current transaction id - including the error count in the transaction details link * [APM] improved code org and fix warning message in unit test * [APM] improved code readability and parallelized ES queries
This commit is contained in:
parent
4d6bfd6b26
commit
58f4295b22
11 changed files with 144 additions and 20 deletions
|
@ -35,5 +35,8 @@
|
|||
"groupId": "8673d8bf7a032e387c101bafbab0d2bc",
|
||||
"latestOccurrenceAt": "2018-01-10T10:06:13.211Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
"location": {
|
||||
"search": "?transactionId=abcdef0123456789"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
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 { legacyEncodeURIComponent } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers';
|
||||
import {
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_RESULT,
|
||||
|
@ -24,11 +26,13 @@ import {
|
|||
interface Props {
|
||||
transaction: Transaction;
|
||||
totalDuration?: number;
|
||||
errorCount?: number;
|
||||
}
|
||||
|
||||
export function StickyTransactionProperties({
|
||||
transaction,
|
||||
totalDuration
|
||||
totalDuration,
|
||||
errorCount
|
||||
}: Props) {
|
||||
const timestamp = transaction['@timestamp'];
|
||||
const url =
|
||||
|
@ -90,5 +94,43 @@ export function StickyTransactionProperties({
|
|||
}
|
||||
];
|
||||
|
||||
if (errorCount !== undefined) {
|
||||
const errorsOverviewLink = (
|
||||
<KibanaLink
|
||||
pathname={'/app/apm'}
|
||||
hash={`/${idx(transaction, _ => _.service.name)}/errors`}
|
||||
query={{
|
||||
kuery: legacyEncodeURIComponent(
|
||||
`transaction.id : "${transaction.transaction.id}"`
|
||||
)
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.apm.transactionDetails.errorsOverviewLink', {
|
||||
values: { errorCount },
|
||||
defaultMessage:
|
||||
'{errorCount, plural, one {View 1 error} other {View # errors}}'
|
||||
})}
|
||||
</KibanaLink>
|
||||
);
|
||||
|
||||
const noErrorsText = i18n.translate(
|
||||
'xpack.apm.transactionDetails.errorsNone',
|
||||
{
|
||||
defaultMessage: 'None'
|
||||
}
|
||||
);
|
||||
|
||||
stickyProperties.push({
|
||||
label: i18n.translate(
|
||||
'xpack.apm.transactionDetails.errorsOverviewLabel',
|
||||
{
|
||||
defaultMessage: 'Errors'
|
||||
}
|
||||
),
|
||||
val: errorCount === 0 ? noErrorsText : errorsOverviewLink,
|
||||
width: '25%'
|
||||
});
|
||||
}
|
||||
|
||||
return <StickyProperties stickyProperties={stickyProperties} />;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ export function StickySpanProperties({ span, totalDuration }: Props) {
|
|||
),
|
||||
val: spanTypeLabel,
|
||||
truncated: true,
|
||||
widht: '50%'
|
||||
width: '50%'
|
||||
},
|
||||
{
|
||||
fieldName: SPAN_DURATION,
|
||||
|
|
|
@ -97,13 +97,15 @@ interface Props {
|
|||
urlParams: IUrlParams;
|
||||
location: Location;
|
||||
waterfall: IWaterfall;
|
||||
errorCount?: number;
|
||||
}
|
||||
|
||||
export const Transaction: React.SFC<Props> = ({
|
||||
transaction,
|
||||
urlParams,
|
||||
location,
|
||||
waterfall
|
||||
waterfall,
|
||||
errorCount
|
||||
}) => {
|
||||
return (
|
||||
<EuiPanel paddingSize="m">
|
||||
|
@ -140,6 +142,7 @@ export const Transaction: React.SFC<Props> = ({
|
|||
<EuiSpacer />
|
||||
|
||||
<StickyTransactionProperties
|
||||
errorCount={errorCount}
|
||||
transaction={transaction}
|
||||
totalDuration={waterfall.traceRootDuration}
|
||||
/>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import { idx } from 'x-pack/plugins/apm/common/idx';
|
||||
import { TransactionDetailsRequest } from '../../../store/reactReduxRequest/transactionDetails';
|
||||
import { TransactionDetailsChartsRequest } from '../../../store/reactReduxRequest/transactionDetailsCharts';
|
||||
import { TransactionDistributionRequest } from '../../../store/reactReduxRequest/transactionDistribution';
|
||||
|
@ -67,7 +68,10 @@ export function TransactionDetailsView({ urlParams, location }: Props) {
|
|||
|
||||
<TransactionDetailsRequest
|
||||
urlParams={urlParams}
|
||||
render={({ data: transaction }) => {
|
||||
render={({ data }) => {
|
||||
const transaction = idx(data, _ => _.transaction);
|
||||
const errorCount = idx(data, _ => _.errorCount);
|
||||
|
||||
if (!transaction) {
|
||||
return (
|
||||
<EmptyMessage
|
||||
|
@ -99,6 +103,7 @@ export function TransactionDetailsView({ urlParams, location }: Props) {
|
|||
transaction={transaction}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
errorCount={errorCount}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { KFetchError } from 'ui/kfetch/kfetch_error';
|
||||
import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
|
||||
import { TransactionWithErrorCountAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { getEncodedEsQuery } from './apm';
|
||||
|
@ -19,7 +19,7 @@ export async function loadTransaction({
|
|||
kuery
|
||||
}: IUrlParams) {
|
||||
try {
|
||||
const result = await callApi<TransactionAPIResponse>({
|
||||
const result = await callApi<TransactionWithErrorCountAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
|
||||
query: {
|
||||
traceId,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Request, RRRRender } from 'react-redux-request';
|
||||
import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction';
|
||||
import { TransactionWithErrorCountAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
|
||||
import { loadTransaction } from '../../services/rest/apm/transactions';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
|
@ -21,7 +21,7 @@ export function TransactionDetailsRequest({
|
|||
render
|
||||
}: {
|
||||
urlParams: IUrlParams;
|
||||
render: RRRRender<Transaction | null>;
|
||||
render: RRRRender<TransactionWithErrorCountAPIResponse>;
|
||||
}) {
|
||||
const { serviceName, start, end, transactionId, traceId, kuery } = urlParams;
|
||||
|
||||
|
|
53
x-pack/plugins/apm/server/lib/errors/get_error_count.ts
Normal file
53
x-pack/plugins/apm/server/lib/errors/get_error_count.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { ESFilter } from 'elasticsearch';
|
||||
import { idx } from 'x-pack/plugins/apm/common/idx';
|
||||
import { APMError } from 'x-pack/plugins/apm/typings/es_schemas/Error';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
TRACE_ID,
|
||||
TRANSACTION_ID
|
||||
} from '../../../common/constants';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
|
||||
export async function getErrorCount(
|
||||
transactionId: string,
|
||||
traceId: string,
|
||||
setup: Setup
|
||||
): Promise<number> {
|
||||
const { start, end, esFilterQuery, client, config } = setup;
|
||||
const filter: ESFilter[] = [
|
||||
{ term: { [TRANSACTION_ID]: transactionId } },
|
||||
{ term: { [TRACE_ID]: traceId } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'error' } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: start,
|
||||
lte: end,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (esFilterQuery) {
|
||||
filter.push(esFilterQuery);
|
||||
}
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.errorIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: { filter }
|
||||
}
|
||||
}
|
||||
};
|
||||
const resp = await client<APMError>('search', params);
|
||||
return idx(resp, _ => _.hits.total) || 0;
|
||||
}
|
|
@ -74,7 +74,7 @@ export async function getErrorGroup({
|
|||
const traceId = idx(error, _ => _.trace.id);
|
||||
|
||||
let transaction;
|
||||
if (transactionId) {
|
||||
if (transactionId && traceId) {
|
||||
transaction = await getTransaction(transactionId, traceId, setup);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,19 @@ import {
|
|||
TRACE_ID,
|
||||
TRANSACTION_ID
|
||||
} from '../../../../common/constants';
|
||||
import { getErrorCount } from '../../errors/get_error_count';
|
||||
import { Setup } from '../../helpers/setup_request';
|
||||
|
||||
export type TransactionAPIResponse = Transaction | undefined;
|
||||
|
||||
export interface TransactionWithErrorCountAPIResponse {
|
||||
transaction: TransactionAPIResponse;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
export async function getTransaction(
|
||||
transactionId: string,
|
||||
traceId: string | undefined,
|
||||
traceId: string,
|
||||
setup: Setup
|
||||
): Promise<TransactionAPIResponse> {
|
||||
const { start, end, esFilterQuery, client, config } = setup;
|
||||
|
@ -26,6 +32,7 @@ export async function getTransaction(
|
|||
const filter: ESFilter[] = [
|
||||
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
|
||||
{ term: { [TRANSACTION_ID]: transactionId } },
|
||||
{ term: { [TRACE_ID]: traceId } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
|
@ -41,10 +48,6 @@ export async function getTransaction(
|
|||
filter.push(esFilterQuery);
|
||||
}
|
||||
|
||||
if (traceId) {
|
||||
filter.push({ term: { [TRACE_ID]: traceId } });
|
||||
}
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.transactionIndices'),
|
||||
body: {
|
||||
|
@ -60,3 +63,14 @@ export async function getTransaction(
|
|||
const resp = await client<Transaction>('search', params);
|
||||
return idx(resp, _ => _.hits.hits[0]._source);
|
||||
}
|
||||
|
||||
export async function getTransactionWithErrorCount(
|
||||
transactionId: string,
|
||||
traceId: string,
|
||||
setup: Setup
|
||||
): Promise<TransactionWithErrorCountAPIResponse> {
|
||||
return Promise.all([
|
||||
getTransaction(transactionId, traceId, setup),
|
||||
getErrorCount(transactionId, traceId, setup)
|
||||
]).then(([transaction, errorCount]) => ({ transaction, errorCount }));
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Server } from 'hapi';
|
|||
import Joi from 'joi';
|
||||
import { withDefaultValidators } from '../lib/helpers/input_validation';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getTransaction } from '../lib/transactions/get_transaction';
|
||||
import { getTransactionWithErrorCount } from '../lib/transactions/get_transaction';
|
||||
|
||||
export function initTransactionsApi(server: Server) {
|
||||
server.route({
|
||||
|
@ -18,7 +18,7 @@ export function initTransactionsApi(server: Server) {
|
|||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
traceId: Joi.string().allow('') // TODO: this should be a path param and made required by 7.0
|
||||
traceId: Joi.string().required()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -26,9 +26,13 @@ export function initTransactionsApi(server: Server) {
|
|||
const { transactionId } = req.params;
|
||||
const { traceId } = req.query as { traceId: string };
|
||||
const setup = setupRequest(req);
|
||||
const transaction = await getTransaction(transactionId, traceId, setup);
|
||||
if (transaction) {
|
||||
return transaction;
|
||||
const transactionWithErrorCount = await getTransactionWithErrorCount(
|
||||
transactionId,
|
||||
traceId,
|
||||
setup
|
||||
);
|
||||
if (transactionWithErrorCount.transaction) {
|
||||
return transactionWithErrorCount;
|
||||
} else {
|
||||
throw Boom.notFound('Cannot find the requested page');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue