Skip to content

Commit ee533a2

Browse files
committed
change formatter based on available legends
1 parent 294879e commit ee533a2

File tree

9 files changed

+366
-139
lines changed

9 files changed

+366
-139
lines changed

x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,7 @@ export class InnerCustomPlot extends PureComponent {
8080
});
8181

8282
if (typeof this.props.onToggleLegend === 'function') {
83-
//Filters out disabled series
84-
const availableSeries = this.props.series.filter(
85-
(serie, index) => !nextSeriesEnabledState[index]
86-
);
87-
this.props.onToggleLegend(availableSeries);
83+
this.props.onToggleLegend(nextSeriesEnabledState);
8884
}
8985

9086
return {

x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,17 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React from 'react';
8-
import { i18n } from '@kbn/i18n';
97
import { EuiTitle } from '@elastic/eui';
10-
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
11-
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
12-
import { TransactionLineChart } from './TransactionLineChart';
13-
import { getMaxY } from '.';
14-
import {
15-
getDurationFormatter,
16-
TimeFormatter,
17-
} from '../../../../utils/formatters';
8+
import { i18n } from '@kbn/i18n';
9+
import React from 'react';
1810
import { useAvgDurationByBrowser } from '../../../../hooks/useAvgDurationByBrowser';
19-
import { Coordinate } from '../../../../../typings/timeseries';
20-
21-
function getResponseTimeTickFormatter(formatter: TimeFormatter) {
22-
return (t: number) => formatter(t).formatted;
23-
}
24-
25-
function getResponseTimeTooltipFormatter(formatter: TimeFormatter) {
26-
return (p: Coordinate) => {
27-
return isValidCoordinateValue(p.y)
28-
? formatter(p.y).formatted
29-
: NOT_AVAILABLE_LABEL;
30-
};
31-
}
11+
import { getDurationFormatter } from '../../../../utils/formatters';
12+
import {
13+
getResponseTimeTickFormatter,
14+
getResponseTimeTooltipFormatter,
15+
getMaxY,
16+
} from './helper';
17+
import { TransactionLineChart } from './TransactionLineChart';
3218

3319
export function BrowserLineChart() {
3420
const { data } = useAvgDurationByBrowser();

x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface Props {
1919
height?: number;
2020
stacked?: boolean;
2121
onHover?: () => void;
22-
onToggleLegend?: (visibleSeries: TimeSeries[]) => void;
22+
onToggleLegend?: (disabledSeriesState: boolean[]) => void;
2323
}
2424

2525
function TransactionLineChart(props: Props) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 {
8+
getResponseTimeTickFormatter,
9+
getResponseTimeTooltipFormatter,
10+
getMaxY,
11+
} from './helper';
12+
import {
13+
getDurationFormatter,
14+
toMicroseconds,
15+
} from '../../../../utils/formatters';
16+
import { TimeSeries } from '../../../../../typings/timeseries';
17+
18+
describe('transaction chart helper', () => {
19+
describe('getResponseTimeTickFormatter', () => {
20+
it('formattes time tick in minutes', () => {
21+
const formatter = getDurationFormatter(toMicroseconds(11, 'minutes'));
22+
const timeTickFormatter = getResponseTimeTickFormatter(formatter);
23+
expect(timeTickFormatter(toMicroseconds(60, 'seconds'))).toEqual(
24+
'1.0 min'
25+
);
26+
});
27+
it('formattes time tick in seconds', () => {
28+
const formatter = getDurationFormatter(toMicroseconds(11, 'seconds'));
29+
const timeTickFormatter = getResponseTimeTickFormatter(formatter);
30+
expect(timeTickFormatter(toMicroseconds(6, 'seconds'))).toEqual('6.0 s');
31+
});
32+
});
33+
describe('getResponseTimeTooltipFormatter', () => {
34+
const formatter = getDurationFormatter(toMicroseconds(11, 'minutes'));
35+
const tooltipFormatter = getResponseTimeTooltipFormatter(formatter);
36+
it("doesn't format invalid y coordinate", () => {
37+
expect(tooltipFormatter({ x: 1, y: undefined })).toEqual('N/A');
38+
expect(tooltipFormatter({ x: 1, y: null })).toEqual('N/A');
39+
});
40+
it('formattes tooltip in minutes', () => {
41+
expect(
42+
tooltipFormatter({ x: 1, y: toMicroseconds(60, 'seconds') })
43+
).toEqual('1.0 min');
44+
});
45+
});
46+
describe('getMaxY', () => {
47+
it('returns zero when empty time series', () => {
48+
expect(getMaxY([])).toEqual(0);
49+
});
50+
it('returns zero for invalid y coordinate', () => {
51+
const timeSeries = ([
52+
{ data: [{ x: 1 }, { x: 2 }, { x: 3, y: -1 }] },
53+
] as unknown) as TimeSeries[];
54+
expect(getMaxY(timeSeries)).toEqual(0);
55+
});
56+
it('returns the max y coordinate', () => {
57+
const timeSeries = ([
58+
{
59+
data: [
60+
{ x: 1, y: 10 },
61+
{ x: 2, y: 5 },
62+
{ x: 3, y: 1 },
63+
],
64+
},
65+
] as unknown) as TimeSeries[];
66+
expect(getMaxY(timeSeries)).toEqual(10);
67+
});
68+
});
69+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 { flatten } from 'lodash';
8+
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
9+
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
10+
import { TimeSeries, Coordinate } from '../../../../../typings/timeseries';
11+
import { TimeFormatter } from '../../../../utils/formatters';
12+
13+
export function getResponseTimeTickFormatter(formatter: TimeFormatter) {
14+
return (t: number) => {
15+
return formatter(t).formatted;
16+
};
17+
}
18+
19+
export function getResponseTimeTooltipFormatter(formatter: TimeFormatter) {
20+
return (coordinate: Coordinate) => {
21+
return isValidCoordinateValue(coordinate.y)
22+
? formatter(coordinate.y).formatted
23+
: NOT_AVAILABLE_LABEL;
24+
};
25+
}
26+
27+
export function getMaxY(timeSeries: TimeSeries[]) {
28+
const coordinates = flatten(
29+
timeSeries.map((serie: TimeSeries) => serie.data as Coordinate[])
30+
);
31+
32+
const numbers: number[] = coordinates.map((c: Coordinate) => (c.y ? c.y : 0));
33+
34+
return Math.max(...numbers, 0);
35+
}

x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx

Lines changed: 22 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,41 @@ import {
88
EuiFlexGrid,
99
EuiFlexGroup,
1010
EuiFlexItem,
11-
EuiIconTip,
1211
EuiPanel,
1312
EuiSpacer,
14-
EuiText,
1513
EuiTitle,
1614
} from '@elastic/eui';
1715
import { i18n } from '@kbn/i18n';
1816
import { Location } from 'history';
19-
import { flatten, isEmpty } from 'lodash';
2017
import React from 'react';
21-
import styled from 'styled-components';
2218
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
2319
import {
2420
TRANSACTION_PAGE_LOAD,
2521
TRANSACTION_REQUEST,
2622
TRANSACTION_ROUTE_CHANGE,
2723
} from '../../../../../common/transaction_types';
28-
import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
24+
import { Coordinate } from '../../../../../typings/timeseries';
2925
import { LicenseContext } from '../../../../context/LicenseContext';
3026
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
3127
import { ITransactionChartData } from '../../../../selectors/chartSelectors';
32-
import {
33-
asDecimal,
34-
getDurationFormatter,
35-
tpmUnit,
36-
} from '../../../../utils/formatters';
28+
import { asDecimal, tpmUnit } from '../../../../utils/formatters';
3729
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
38-
import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink';
3930
import { BrowserLineChart } from './BrowserLineChart';
4031
import { DurationByCountryMap } from './DurationByCountryMap';
32+
import {
33+
getResponseTimeTickFormatter,
34+
getResponseTimeTooltipFormatter,
35+
} from './helper';
36+
import { MLHeader } from './ml_header';
4137
import { TransactionLineChart } from './TransactionLineChart';
38+
import { useFormatter } from './use_formatter';
4239

4340
interface TransactionChartProps {
4441
charts: ITransactionChartData;
4542
location: Location;
4643
urlParams: IUrlParams;
4744
}
4845

49-
const ShiftedIconWrapper = styled.span`
50-
padding-right: 5px;
51-
position: relative;
52-
top: -1px;
53-
display: inline-block;
54-
`;
55-
56-
const ShiftedEuiText = styled(EuiText)`
57-
position: relative;
58-
top: 5px;
59-
`;
60-
61-
export function getMaxY(responseTimeSeries: TimeSeries[]) {
62-
const coordinates = flatten(
63-
responseTimeSeries.map((serie: TimeSeries) => serie.data as Coordinate[])
64-
);
65-
66-
const numbers: number[] = coordinates.map((c: Coordinate) => (c.y ? c.y : 0));
67-
68-
return Math.max(...numbers, 0);
69-
}
70-
7146
export function TransactionCharts({
7247
charts,
7348
location,
@@ -84,82 +59,16 @@ export function TransactionCharts({
8459
: NOT_AVAILABLE_LABEL;
8560
};
8661

87-
function renderMLHeader(hasValidMlLicense: boolean | undefined) {
88-
const { mlJobId } = charts;
89-
90-
if (!hasValidMlLicense || !mlJobId) {
91-
return null;
92-
}
93-
94-
const { serviceName, kuery, transactionType } = urlParams;
95-
if (!serviceName) {
96-
return null;
97-
}
98-
99-
const hasKuery = !isEmpty(kuery);
100-
const icon = hasKuery ? (
101-
<EuiIconTip
102-
aria-label="Warning"
103-
type="alert"
104-
color="warning"
105-
content="The Machine learning results are hidden when the search bar is used for filtering"
106-
/>
107-
) : (
108-
<EuiIconTip
109-
content={i18n.translate(
110-
'xpack.apm.metrics.transactionChart.machineLearningTooltip',
111-
{
112-
defaultMessage:
113-
'The stream around the average duration shows the expected bounds. An annotation is shown for anomaly scores ≥ 75.',
114-
}
115-
)}
116-
/>
117-
);
118-
119-
return (
120-
<EuiFlexItem grow={false}>
121-
<ShiftedEuiText size="xs">
122-
<ShiftedIconWrapper>{icon}</ShiftedIconWrapper>
123-
<span>
124-
{i18n.translate(
125-
'xpack.apm.metrics.transactionChart.machineLearningLabel',
126-
{
127-
defaultMessage: 'Machine learning:',
128-
}
129-
)}{' '}
130-
</span>
131-
<MLJobLink
132-
jobId={mlJobId}
133-
serviceName={serviceName}
134-
transactionType={transactionType}
135-
>
136-
View Job
137-
</MLJobLink>
138-
</ShiftedEuiText>
139-
</EuiFlexItem>
140-
);
141-
}
142-
const { responseTimeSeries, tpmSeries } = charts;
14362
const { transactionType } = urlParams;
144-
const maxY = getMaxY(responseTimeSeries);
145-
let formatter = getDurationFormatter(maxY);
14663

147-
function onToggleLegend(visibleSeries: TimeSeries[]) {
148-
if (!isEmpty(visibleSeries)) {
149-
// recalculate the formatter based on the max Y from the visible series
150-
const maxVisibleY = getMaxY(visibleSeries);
151-
formatter = getDurationFormatter(maxVisibleY);
152-
}
153-
}
64+
const { responseTimeSeries, tpmSeries } = charts;
15465

155-
function getResponseTimeTickFormatter(t: number) {
156-
return formatter(t).formatted;
157-
}
66+
const { formatter, setDisabledSeriesState } = useFormatter(
67+
responseTimeSeries
68+
);
15869

159-
function getResponseTimeTooltipFormatter(coordinate: Coordinate) {
160-
return isValidCoordinateValue(coordinate.y)
161-
? formatter(coordinate.y).formatted
162-
: NOT_AVAILABLE_LABEL;
70+
function onToggleLegend(disabledSeriesStates: boolean[]) {
71+
setDisabledSeriesState(disabledSeriesStates);
16372
}
16473

16574
return (
@@ -175,15 +84,18 @@ export function TransactionCharts({
17584
</EuiTitle>
17685
</EuiFlexItem>
17786
<LicenseContext.Consumer>
178-
{(license) =>
179-
renderMLHeader(license?.getFeature('ml').isAvailable)
180-
}
87+
{(license) => (
88+
<MLHeader
89+
hasValidMlLicense={license?.getFeature('ml').isAvailable}
90+
mlJobId={charts.mlJobId}
91+
/>
92+
)}
18193
</LicenseContext.Consumer>
18294
</EuiFlexGroup>
18395
<TransactionLineChart
18496
series={responseTimeSeries}
185-
tickFormatY={getResponseTimeTickFormatter}
186-
formatTooltipValue={getResponseTimeTooltipFormatter}
97+
tickFormatY={getResponseTimeTickFormatter(formatter)}
98+
formatTooltipValue={getResponseTimeTooltipFormatter(formatter)}
18799
onToggleLegend={onToggleLegend}
188100
/>
189101
</React.Fragment>

0 commit comments

Comments
 (0)