-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[APM] Transaction breakdown charts #39531
Changes from all commits
2f2534f
a4149c6
49afb52
52945d0
a7e701a
d72a3c6
c808e71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this rename necessary? Shouldn't I actually prefer |
||
color: timeseriesConfig.color, | ||
data: timeseriesConfig.values, | ||
type: 'area', | ||
hideLegend: true | ||
}; | ||
}, {}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the last arg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. aye, think this was a reduce before and I forgot to remove the second argument. thanks! |
||
}, | ||
[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 | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -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'; | ||||||||
|
@@ -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; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this access checks a lot of places which makes the code a bit harder to read. What about setting a default response like we do in:
Could be something like: const INITIAL_DATA: TransactionBreakdownAPIResponse = {
kpis: [],
timeseries_per_subtype: null
} |
||||||||
|
||||||||
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]; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if no matches are found? Then |
||||||||
|
||||||||
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> | ||||||||
|
@@ -77,9 +120,11 @@ const TransactionBreakdown: React.FC = () => { | |||||||
}} | ||||||||
/> | ||||||||
</EuiFlexItem> | ||||||||
{hasHits && kpis ? ( | ||||||||
{hasHits && sortedAndColoredKpis ? ( | ||||||||
<EuiFlexItem> | ||||||||
{kpis && <TransactionBreakdownKpiList kpis={kpis} />} | ||||||||
{sortedAndColoredKpis && ( | ||||||||
<TransactionBreakdownKpiList kpis={sortedAndColoredKpis} /> | ||||||||
)} | ||||||||
</EuiFlexItem> | ||||||||
) : ( | ||||||||
!loading && ( | ||||||||
|
@@ -114,7 +159,7 @@ const TransactionBreakdown: React.FC = () => { | |||||||
)} | ||||||||
{showChart && hasHits ? ( | ||||||||
<EuiFlexItem> | ||||||||
<TransactionBreakdownGraph /> | ||||||||
<TransactionBreakdownGraph timeseries={timeseries} /> | ||||||||
</EuiFlexItem> | ||||||||
) : null} | ||||||||
</EuiFlexGroup> | ||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ class StaticPlot extends PureComponent { | |
curve={'curveMonotoneX'} | ||
data={serie.data} | ||
color={serie.color} | ||
stack={serie.stack} | ||
/> | ||
); | ||
case 'area': | ||
|
@@ -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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stroke has to be transparent for a stacked area chart, because we stick a line chart on top of it to achieve the look that we want. |
||
fill={serie.areaColor || rgba(serie.color, 0.3)} | ||
stack={serie.stack} | ||
/> | ||
); | ||
case 'areaMaxHeight': | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be consistent with the other charts we have (
name
->title
andvalues
->data
).As mentioned somewhere else I prefer
values
overdata
but might be a bigger job to change that everywhere.Instead of having several different interfaces can we make a single timeseries interface that is re-used?