Skip to content

Commit a80366b

Browse files
[APM] Pagination of top 10 trace samples (#51911)
* adding trace pagination * adding trace pagination * refactoring * refactoring
1 parent 99c6396 commit a80366b

File tree

11 files changed

+296
-207
lines changed

11 files changed

+296
-207
lines changed

x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/__jest__/distribution.test.js

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { getFormattedBuckets } from '../index';
8+
import { IBucket } from '../../../../../../server/lib/transactions/distribution/get_buckets/transform';
9+
10+
describe('Distribution', () => {
11+
it('getFormattedBuckets', () => {
12+
const buckets = [
13+
{ key: 0, count: 0, samples: [] },
14+
{ key: 20, count: 0, samples: [] },
15+
{ key: 40, count: 0, samples: [] },
16+
{
17+
key: 60,
18+
count: 5,
19+
samples: [
20+
{
21+
transactionId: 'someTransactionId'
22+
}
23+
]
24+
},
25+
{
26+
key: 80,
27+
count: 100,
28+
samples: [
29+
{
30+
transactionId: 'anotherTransactionId'
31+
}
32+
]
33+
}
34+
] as IBucket[];
35+
expect(getFormattedBuckets(buckets, 20)).toEqual([
36+
{ x: 20, x0: 0, y: 0, style: { cursor: 'default' }, samples: [] },
37+
{ x: 40, x0: 20, y: 0, style: { cursor: 'default' }, samples: [] },
38+
{ x: 60, x0: 40, y: 0, style: { cursor: 'default' }, samples: [] },
39+
{
40+
x: 80,
41+
x0: 60,
42+
y: 5,
43+
style: { cursor: 'pointer' },
44+
samples: [
45+
{
46+
transactionId: 'someTransactionId'
47+
}
48+
]
49+
},
50+
{
51+
x: 100,
52+
x0: 80,
53+
y: 100,
54+
style: { cursor: 'pointer' },
55+
samples: [
56+
{
57+
transactionId: 'anotherTransactionId'
58+
}
59+
]
60+
}
61+
]);
62+
});
63+
});

x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { EuiIconTip, EuiTitle } from '@elastic/eui';
88
import { i18n } from '@kbn/i18n';
99
import d3 from 'd3';
1010
import React, { FunctionComponent, useCallback } from 'react';
11+
import { isEmpty } from 'lodash';
1112
import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution';
1213
import { IBucket } from '../../../../../server/lib/transactions/distribution/get_buckets/transform';
1314
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
@@ -20,7 +21,7 @@ import { history } from '../../../../utils/history';
2021
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
2122

2223
interface IChartPoint {
23-
sample?: IBucket['sample'];
24+
samples: IBucket['samples'];
2425
x0: number;
2526
x: number;
2627
y: number;
@@ -35,13 +36,15 @@ export function getFormattedBuckets(buckets: IBucket[], bucketSize: number) {
3536
}
3637

3738
return buckets.map(
38-
({ sample, count, key }): IChartPoint => {
39+
({ samples, count, key }): IChartPoint => {
3940
return {
40-
sample,
41+
samples,
4142
x0: key,
4243
x: key + bucketSize,
4344
y: count,
44-
style: { cursor: count > 0 && sample ? 'pointer' : 'default' }
45+
style: {
46+
cursor: isEmpty(samples) ? 'default' : 'pointer'
47+
}
4548
};
4649
}
4750
);
@@ -91,15 +94,17 @@ interface Props {
9194
distribution?: TransactionDistributionAPIResponse;
9295
urlParams: IUrlParams;
9396
isLoading: boolean;
97+
bucketIndex: number;
9498
}
9599

96100
export const TransactionDistribution: FunctionComponent<Props> = (
97101
props: Props
98102
) => {
99103
const {
100104
distribution,
101-
urlParams: { transactionId, traceId, transactionType },
102-
isLoading
105+
urlParams: { transactionType },
106+
isLoading,
107+
bucketIndex
103108
} = props;
104109

105110
const formatYShort = useCallback(getFormatYShort(transactionType), [
@@ -134,13 +139,6 @@ export const TransactionDistribution: FunctionComponent<Props> = (
134139
const xMax = d3.max(buckets, d => d.x) || 0;
135140
const timeFormatter = getDurationFormatter(xMax);
136141

137-
const bucketIndex = buckets.findIndex(
138-
bucket =>
139-
bucket.sample != null &&
140-
bucket.sample.transactionId === transactionId &&
141-
bucket.sample.traceId === traceId
142-
);
143-
144142
return (
145143
<div>
146144
<EuiTitle size="xs">
@@ -175,31 +173,30 @@ export const TransactionDistribution: FunctionComponent<Props> = (
175173
bucketSize={distribution.bucketSize}
176174
bucketIndex={bucketIndex}
177175
onClick={(bucket: IChartPoint) => {
178-
if (bucket.sample && bucket.y > 0) {
176+
if (!isEmpty(bucket.samples)) {
177+
const sample = bucket.samples[0];
179178
history.push({
180179
...history.location,
181180
search: fromQuery({
182181
...toQuery(history.location.search),
183-
transactionId: bucket.sample.transactionId,
184-
traceId: bucket.sample.traceId
182+
transactionId: sample.transactionId,
183+
traceId: sample.traceId
185184
})
186185
});
187186
}
188187
}}
189188
formatX={(time: number) => timeFormatter(time).formatted}
190189
formatYShort={formatYShort}
191190
formatYLong={formatYLong}
192-
verticalLineHover={(bucket: IChartPoint) =>
193-
bucket.y > 0 && !bucket.sample
194-
}
195-
backgroundHover={(bucket: IChartPoint) => bucket.y > 0 && bucket.sample}
191+
verticalLineHover={(bucket: IChartPoint) => isEmpty(bucket.samples)}
192+
backgroundHover={(bucket: IChartPoint) => !isEmpty(bucket.samples)}
196193
tooltipHeader={(bucket: IChartPoint) => {
197194
const xFormatted = timeFormatter(bucket.x);
198195
const x0Formatted = timeFormatter(bucket.x0);
199196
return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`;
200197
}}
201198
tooltipFooter={(bucket: IChartPoint) =>
202-
!bucket.sample &&
199+
isEmpty(bucket.samples) &&
203200
i18n.translate(
204201
'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip',
205202
{
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React from 'react';
8+
import { EuiButton, EuiFlexItem, EuiToolTip } from '@elastic/eui';
9+
import { i18n } from '@kbn/i18n';
10+
import { Transaction as ITransaction } from '../../../../../typings/es_schemas/ui/Transaction';
11+
import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink';
12+
import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
13+
14+
export const MaybeViewTraceLink = ({
15+
transaction,
16+
waterfall
17+
}: {
18+
transaction: ITransaction;
19+
waterfall: IWaterfall;
20+
}) => {
21+
const viewFullTraceButtonLabel = i18n.translate(
22+
'xpack.apm.transactionDetails.viewFullTraceButtonLabel',
23+
{
24+
defaultMessage: 'View full trace'
25+
}
26+
);
27+
28+
// the traceroot cannot be found, so we cannot link to it
29+
if (!waterfall.traceRoot) {
30+
return (
31+
<EuiFlexItem grow={false}>
32+
<EuiToolTip
33+
content={i18n.translate(
34+
'xpack.apm.transactionDetails.noTraceParentButtonTooltip',
35+
{
36+
defaultMessage: 'The trace parent cannot be found'
37+
}
38+
)}
39+
>
40+
<EuiButton iconType="apmTrace" disabled={true}>
41+
{viewFullTraceButtonLabel}
42+
</EuiButton>
43+
</EuiToolTip>
44+
</EuiFlexItem>
45+
);
46+
}
47+
48+
const isRoot =
49+
transaction.transaction.id === waterfall.traceRoot.transaction.id;
50+
51+
// the user is already viewing the full trace, so don't link to it
52+
if (isRoot) {
53+
return (
54+
<EuiFlexItem grow={false}>
55+
<EuiToolTip
56+
content={i18n.translate(
57+
'xpack.apm.transactionDetails.viewingFullTraceButtonTooltip',
58+
{
59+
defaultMessage: 'Currently viewing the full trace'
60+
}
61+
)}
62+
>
63+
<EuiButton iconType="apmTrace" disabled={true}>
64+
{viewFullTraceButtonLabel}
65+
</EuiButton>
66+
</EuiToolTip>
67+
</EuiFlexItem>
68+
);
69+
70+
// the user is viewing a zoomed in version of the trace. Link to the full trace
71+
} else {
72+
const traceRoot = waterfall.traceRoot;
73+
return (
74+
<EuiFlexItem grow={false}>
75+
<TransactionDetailLink
76+
serviceName={traceRoot.service.name}
77+
transactionId={traceRoot.transaction.id}
78+
traceId={traceRoot.trace.id}
79+
transactionName={traceRoot.transaction.name}
80+
transactionType={traceRoot.transaction.type}
81+
>
82+
<EuiButton iconType="apmTrace">{viewFullTraceButtonLabel}</EuiButton>
83+
</TransactionDetailLink>
84+
</EuiFlexItem>
85+
);
86+
}
87+
};

0 commit comments

Comments
 (0)