Skip to content

Commit

Permalink
[APM] Transaction breakdown charts (elastic#39531)
Browse files Browse the repository at this point in the history
* [APM] Transaction breakdown graphs

Closes elastic#36441.

* Don't use .keyword now that mapping is correct

* Use better heuristic for determining noHits; wrap App in memo to prevent unnecessary re-renders

* Remove nested panel around TransactionBreakdown

* Fix display issues in charts

* Address review feedback

* Address additional review feedback
  • Loading branch information
dgieselaar committed Jul 2, 2019
1 parent c1e822c commit 8ccf2e4
Show file tree
Hide file tree
Showing 29 changed files with 19,678 additions and 368 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ export const TRANSACTION_RESULT = 'transaction.result';
export const TRANSACTION_NAME = 'transaction.name';
export const TRANSACTION_ID = 'transaction.id';
export const TRANSACTION_SAMPLED = 'transaction.sampled';
export const TRANSACTION_BREAKDOWN_COUNT = 'transaction.breakdown.count';

export const TRACE_ID = 'trace.id';

export const SPAN_DURATION = 'span.duration.us';
export const SPAN_TYPE = 'span.type';
export const SPAN_SUBTYPE = 'span.subtype';
export const SPAN_SELF_TIME_SUM = 'span.self_time.sum.us';
export const SPAN_ACTION = 'span.action';
export const SPAN_NAME = 'span.name';
export const SPAN_ID = 'span.id';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { FETCH_STATUS } from '../../../hooks/useFetcher';
import { TransactionBreakdown } from '../../shared/TransactionBreakdown';
import { ChartsTimeContextProvider } from '../../../context/ChartsTimeContext';

export function TransactionDetails() {
const location = useLocation();
Expand All @@ -42,16 +43,18 @@ export function TransactionDetails() {
</EuiTitle>
</ApmHeader>

<TransactionBreakdown />
<ChartsTimeContextProvider>
<TransactionBreakdown />

<EuiSpacer size="s" />
<EuiSpacer size="s" />

<TransactionCharts
hasMLJob={false}
charts={transactionDetailsChartsData}
urlParams={urlParams}
location={location}
/>
<TransactionCharts
hasMLJob={false}
charts={transactionDetailsChartsData}
urlParams={urlParams}
location={location}
/>
</ChartsTimeContextProvider>

<EuiHorizontalRule size="full" margin="l" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useFetcher } from '../../../hooks/useFetcher';
import { getHasMLJob } from '../../../services/rest/ml';
import { history } from '../../../utils/history';
import { useLocation } from '../../../hooks/useLocation';
import { ChartsTimeContextProvider } from '../../../context/ChartsTimeContext';

interface Props {
urlParams: IUrlParams;
Expand Down Expand Up @@ -114,16 +115,18 @@ export function TransactionOverview({
</EuiFormRow>
) : null}

<TransactionBreakdown />
<ChartsTimeContextProvider>
<TransactionBreakdown initialIsOpen={true} />

<EuiSpacer size="s" />
<EuiSpacer size="s" />

<TransactionCharts
hasMLJob={hasMLJob}
charts={transactionOverviewCharts}
location={location}
urlParams={urlParams}
/>
<TransactionCharts
hasMLJob={hasMLJob}
charts={transactionOverviewCharts}
location={location}
urlParams={urlParams}
/>
</ChartsTimeContextProvider>

<EuiSpacer size="s" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,62 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { useMemo, useCallback } from 'react';
import numeral from '@elastic/numeral';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { Coordinate } from '../../../../../typings/timeseries';
import { TransactionLineChart } from '../../charts/TransactionCharts/TransactionLineChart';
import { asPercent } from '../../../../utils/formatters';
import { unit } from '../../../../style/variables';

const TransactionBreakdownGraph: React.FC<{}> = () => {
return <div />;
interface Props {
timeseries: Array<{
name: string;
color: string;
values: Array<{ x: number; y: number | null }>;
}>;
}

const TransactionBreakdownGraph: React.FC<Props> = props => {
const { timeseries } = props;

const series: React.ComponentProps<
typeof TransactionLineChart
>['series'] = useMemo(
() => {
return timeseries.map(timeseriesConfig => {
return {
title: timeseriesConfig.name,
color: timeseriesConfig.color,
data: timeseriesConfig.values,
type: 'area',
hideLegend: true
};
}, {});
},
[timeseries]
);

const tickFormatY = useCallback((y: number | null) => {
return numeral(y || 0).format('0 %');
}, []);

const formatTooltipValue = useCallback((coordinate: Coordinate) => {
return coordinate.y !== null && coordinate.y !== undefined
? asPercent(coordinate.y, 1)
: NOT_AVAILABLE_LABEL;
}, []);

return (
<TransactionLineChart
series={series}
stacked={true}
tickFormatY={tickFormatY}
formatTooltipValue={formatTooltipValue}
yMax={1}
height={unit * 12}
/>
);
};

export { TransactionBreakdownGraph };
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { FORMATTERS } from '../../../../../infra/public/utils/formatters';
interface TransactionBreakdownKpi {
name: string;
percentage: number;
count: number;
color: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
EuiSpacer,
EuiPanel
} from '@elastic/eui';
import { sortBy } from 'lodash';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown';
Expand All @@ -38,32 +37,76 @@ const NoTransactionsTitle = styled.span`
font-weight: bold;
`;

const TransactionBreakdown: React.FC = () => {
const [showChart, setShowChart] = useState(false);
const TransactionBreakdown: React.FC<{
initialIsOpen?: boolean;
}> = ({ initialIsOpen }) => {
const [showChart, setShowChart] = useState(!!initialIsOpen);

const {
data,
status,
receivedDataDuringLifetime
} = useTransactionBreakdown();

const kpis = useMemo(
const kpis = data ? data.kpis : undefined;
const timeseriesPerSubtype = data ? data.timeseries_per_subtype : undefined;

const legends = useMemo(
() => {
return data
? sortBy(data, 'name').map((breakdown, index) => {
return {
...breakdown,
color: COLORS[index % COLORS.length]
};
})
: null;
const names = kpis ? kpis.map(kpi => kpi.name).sort() : [];

return names.map((name, index) => {
return {
name,
color: COLORS[index % COLORS.length]
};
});
},
[data]
[kpis]
);

const sortedAndColoredKpis = useMemo(
() => {
if (!kpis) {
return null;
}

return legends.map(legend => {
const { color } = legend;

const breakdown = kpis.find(
b => b.name === legend.name
) as typeof kpis[0];

return {
...breakdown,
color
};
});
},
[kpis, legends]
);

const loading = status === FETCH_STATUS.LOADING || status === undefined;

const hasHits = data && data.length > 0;
const hasHits = data && data.kpis.length > 0;
const timeseries = useMemo(
() => {
if (!timeseriesPerSubtype) {
return [];
}
return legends.map(legend => {
const series = timeseriesPerSubtype[legend.name];

return {
name: legend.name,
values: series,
color: legend.color
};
});
},
[timeseriesPerSubtype, legends]
);

return receivedDataDuringLifetime ? (
<EuiPanel>
Expand All @@ -77,9 +120,11 @@ const TransactionBreakdown: React.FC = () => {
}}
/>
</EuiFlexItem>
{hasHits && kpis ? (
{hasHits && sortedAndColoredKpis ? (
<EuiFlexItem>
{kpis && <TransactionBreakdownKpiList kpis={kpis} />}
{sortedAndColoredKpis && (
<TransactionBreakdownKpiList kpis={sortedAndColoredKpis} />
)}
</EuiFlexItem>
) : (
!loading && (
Expand Down Expand Up @@ -114,7 +159,7 @@ const TransactionBreakdown: React.FC = () => {
)}
{showChart && hasHits ? (
<EuiFlexItem>
<TransactionBreakdownGraph />
<TransactionBreakdownGraph timeseries={timeseries} />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ function getPointByX(serie, x) {

class InteractivePlot extends PureComponent {
getMarkPoints = hoverX => {
return this.props.series
.filter(serie =>
serie.data.some(point => point.x === hoverX && point.y != null)
)
.map(serie => {
const { x, y } = getPointByX(serie, hoverX) || {};
return {
x,
y,
color: serie.color
};
});
return (
this.props.series
.filter(serie =>
serie.data.some(point => point.x === hoverX && point.y != null)
)
.map(serie => {
const { x, y } = getPointByX(serie, hoverX) || {};
return {
x,
y,
color: serie.color
};
})
// needs to be reversed, as StaticPlot.js does the same
.reverse()
);
};

getTooltipPoints = hoverX => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class StaticPlot extends PureComponent {
curve={'curveMonotoneX'}
data={serie.data}
color={serie.color}
stack={serie.stack}
/>
);
case 'area':
Expand All @@ -53,8 +54,9 @@ class StaticPlot extends PureComponent {
curve={'curveMonotoneX'}
data={serie.data}
color={serie.color}
stroke={serie.color}
stroke={serie.stack ? 'rgba(0,0,0,0)' : serie.color}
fill={serie.areaColor || rgba(serie.color, 0.3)}
stack={serie.stack}
/>
);
case 'areaMaxHeight':
Expand Down
Loading

0 comments on commit 8ccf2e4

Please sign in to comment.