Skip to content
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] Adding comparison to transactions table #91435

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ export function ServiceOverview({
return (
<AnnotationsContextProvider>
<ChartPointerEventContextProvider>
<SearchBar prepend={transactionTypeLabel} showCorrelations />
<SearchBar
prepend={transactionTypeLabel}
showCorrelations
showTimeComparison
/>
<EuiPage>
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { EuiBasicTableColumn } from '@elastic/eui';
import { EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { ValuesType } from 'utility-types';
Expand Down Expand Up @@ -34,10 +34,12 @@ export function getColumns({
serviceName,
latencyAggregationType,
transactionGroupComparisonStatistics,
comparisonEnabled,
}: {
serviceName: string;
latencyAggregationType?: LatencyAggregationType;
transactionGroupComparisonStatistics?: TransactionGroupComparisonStatistics;
comparisonEnabled?: boolean;
}): Array<EuiBasicTableColumn<ServiceTransactionGroupItem>> {
return [
{
Expand Down Expand Up @@ -71,13 +73,18 @@ export function getColumns({
name: getLatencyColumnLabel(latencyAggregationType),
width: px(unit * 10),
render: (_, { latency, name }) => {
const timeseries =
transactionGroupComparisonStatistics?.[name]?.latency;
const currentTimeseries =
transactionGroupComparisonStatistics?.currentPeriod?.[name]?.latency;
const previousTimeseries =
transactionGroupComparisonStatistics?.previousPeriod?.[name]?.latency;
return (
<SparkPlot
color="euiColorVis1"
compact
series={timeseries}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
}
valueLabel={asMillisecondDuration(latency)}
/>
);
Expand All @@ -92,13 +99,20 @@ export function getColumns({
),
width: px(unit * 10),
render: (_, { throughput, name }) => {
const timeseries =
transactionGroupComparisonStatistics?.[name]?.throughput;
const currentTimeseries =
transactionGroupComparisonStatistics?.currentPeriod?.[name]
?.throughput;
const previousTimeseries =
transactionGroupComparisonStatistics?.previousPeriod?.[name]
?.throughput;
return (
<SparkPlot
color="euiColorVis0"
compact
series={timeseries}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
}
valueLabel={asTransactionRate(throughput)}
/>
);
Expand All @@ -113,13 +127,20 @@ export function getColumns({
),
width: px(unit * 8),
render: (_, { errorRate, name }) => {
const timeseries =
transactionGroupComparisonStatistics?.[name]?.errorRate;
const currentTimeseries =
transactionGroupComparisonStatistics?.currentPeriod?.[name]
?.errorRate;
const previousTimeseries =
transactionGroupComparisonStatistics?.previousPeriod?.[name]
?.errorRate;
return (
<SparkPlot
color="euiColorVis7"
compact
series={timeseries}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
}
valueLabel={asPercent(errorRate, 1)}
/>
);
Expand All @@ -134,9 +155,22 @@ export function getColumns({
),
width: px(unit * 5),
render: (_, { name }) => {
const impact =
transactionGroupComparisonStatistics?.[name]?.impact ?? 0;
return <ImpactBar value={impact} size="m" />;
const currentImpact =
transactionGroupComparisonStatistics?.currentPeriod?.[name]?.impact ??
0;
const previousImpact =
transactionGroupComparisonStatistics?.previousPeriod?.[name]
?.impact ?? 0;
return (
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem>
<ImpactBar value={currentImpact} size="m" />
</EuiFlexItem>
<EuiFlexItem>
<ImpactBar value={previousImpact} size="s" color="subdued" />
</EuiFlexItem>
</EuiFlexGroup>
);
},
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useUrlParams } from '../../../../context/url_params_context/use_url_par
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link';
import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper';
import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison';
import { ServiceOverviewTableContainer } from '../service_overview_table_container';
import { getColumns } from './get_columns';

Expand Down Expand Up @@ -54,12 +55,27 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
});

const { pageIndex, sort } = tableOptions;
const { direction, field } = sort;

const { transactionType } = useApmServiceContext();
const {
urlParams: { environment, kuery, start, end, latencyAggregationType },
urlParams: {
start,
end,
latencyAggregationType,
comparisonType,
comparisonEnabled,
environment,
kuery,
},
} = useUrlParams();

const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
start,
end,
comparisonType,
});

const { data = INITIAL_STATE, status } = useFetcher(
(callApmApi) => {
if (!start || !end || !latencyAggregationType || !transactionType) {
Expand All @@ -81,11 +97,13 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
},
}).then((response) => {
return {
// Everytime the primary statistics is refetched, updates the requestId making the comparison API to be refetched.
requestId: uuid(),
...response,
};
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
environment,
kuery,
Expand All @@ -94,14 +112,18 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
end,
transactionType,
latencyAggregationType,
comparisonType,
pageIndex,
direction,
field,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be worth trying to figure out if we can move calculating the visible transaction groups into the useFetcher call as well, to see if we no longer need to disable react-hooks/exhaustive-deps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgieselaar I can calculate the visible transaction groups inside useFetcher, but I'll still need the react-hooks/exhaustive-deps, because comparisonType needs to be listed on its dependencies, even though it is not used, to trigger the comparison API call when it's changed.

]
);

const { transactionGroups, requestId } = data;
const currentPageTransactionGroups = orderBy(
transactionGroups,
sort.field,
sort.direction
field,
direction
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);

const transactionNames = JSON.stringify(
Expand All @@ -114,6 +136,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
} = useFetcher(
(callApmApi) => {
if (
status !== FETCH_STATUS.LOADING &&
currentPageTransactionGroups.length &&
start &&
end &&
Expand All @@ -134,21 +157,24 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
transactionType,
latencyAggregationType,
transactionNames,
comparisonStart,
comparisonEnd,
},
},
});
}
},
// only fetches statistics when requestId changes or transaction names changes
// only fetches comparison statistics when requestId is invalidated by primary statistics api call
// eslint-disable-next-line react-hooks/exhaustive-deps
[requestId, transactionNames],
[requestId],
{ preservePreviousData: false }
);

const columns = getColumns({
serviceName,
latencyAggregationType,
transactionGroupComparisonStatistics,
comparisonEnabled,
});

const isLoading =
Expand All @@ -162,13 +188,6 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
hidePerPageOptions: true,
};

const sorting = {
sort: {
field: sort.field,
direction: sort.direction,
},
};

return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
Expand Down Expand Up @@ -211,7 +230,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
items={currentPageTransactionGroups}
columns={columns}
pagination={pagination}
sorting={sorting}
sorting={{ sort: { field, direction } }}
onChange={(newTableOptions: {
page?: {
index: number;
Expand Down
12 changes: 4 additions & 8 deletions x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@ import React from 'react';
// TODO: extend from EUI's EuiProgress prop interface
export interface ImpactBarProps extends Record<string, unknown> {
value: number;
size?: 'l' | 'm';
size?: 's' | 'l' | 'm';
max?: number;
color?: string;
}

export function ImpactBar({
value,
size = 'l',
max = 100,
color = 'primary',
...rest
}: ImpactBarProps) {
return (
<EuiProgress
size={size}
value={value}
max={max}
color="primary"
{...rest}
/>
<EuiProgress size={size} value={value} max={max} color={color} {...rest} />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
AreaSeries,
Chart,
CurveType,
LineSeries,
ScaleType,
Settings,
} from '@elastic/charts';
Expand All @@ -20,8 +21,9 @@ import { Coordinate } from '../../../../../typings/timeseries';
import { useChartTheme } from '../../../../../../observability/public';
import { px, unit } from '../../../../style/variables';
import { useTheme } from '../../../../hooks/use_theme';
import { getComparisonChartTheme } from '../../time_comparison/get_time_range_comparison';

type Color =
export type Color =
| 'euiColorVis0'
| 'euiColorVis1'
| 'euiColorVis2'
Expand All @@ -33,19 +35,27 @@ type Color =
| 'euiColorVis8'
| 'euiColorVis9';

function isEmptyTimeseries(series: Coordinate[]) {
return series.every((point) => point.y === null);
}

export function SparkPlot({
color,
series,
comparisonSeries = [],
valueLabel,
compact,
}: {
color: Color;
series?: Coordinate[] | null;
valueLabel: React.ReactNode;
compact?: boolean;
comparisonSeries?: Coordinate[];
}) {
const theme = useTheme();
const defaultChartTheme = useChartTheme();
const comparisonChartTheme = getComparisonChartTheme(theme);
const hasComparisonSeries = !!comparisonSeries?.length;

const sparkplotChartTheme = merge({}, defaultChartTheme, {
chartMargins: { left: 0, right: 0, top: 0, bottom: 0 },
Expand All @@ -55,6 +65,7 @@ export function SparkPlot({
areaSeriesStyle: {
point: { opacity: 0 },
},
...(hasComparisonSeries ? comparisonChartTheme : {}),
});

const colorValue = theme.eui[color];
Expand All @@ -64,10 +75,12 @@ export function SparkPlot({
width: compact ? px(unit * 3) : px(unit * 4),
};

const SparkelineSeries = hasComparisonSeries ? LineSeries : AreaSeries;

return (
<EuiFlexGroup gutterSize="m" responsive={false}>
<EuiFlexItem grow={false}>
{!series || series.every((point) => point.y === null) ? (
{!series || isEmptyTimeseries(series) ? (
<div
style={{
...chartSize,
Expand All @@ -85,8 +98,8 @@ export function SparkPlot({
showLegend={false}
tooltip="none"
/>
<AreaSeries
id="area"
<SparkelineSeries
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be a LineSeries all the time (or an area series)? What changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary because when comparison is enabled/visible we should change the current period sparkline to LineSeries and show the previous period with AreaSeries.

There's no technical problem with changing all sparklines to LineSeries, but all tables that yet don't have comparison implemented would change to a LineSeries, if @formgeist agrees I'm fine too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thoughts behind the mixed chart types was primarily to create difference in the sparklines between the current and previous time periods. I think if we keep to either line or area, the comparison vanishes a bit because of the size of the chart. @cauemarcondes is it easy to exemplify the two charts (line or area)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, I am fine with mixing line and area. I was just wondering why it's necessary for the current period to switch, and why it can't always be either a line or an area series.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why it's necessary for the current period to switch, and why it can't always be either a line or an area series.

Because there are some places where the spakline is used and it doesn't have comparison data, like on the Service Inventory page table. If I always show it as a LineSeries it would change this table too, that's why I asked @formgeist .

id="sparkelineSeries"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={'x'}
Expand All @@ -95,6 +108,18 @@ export function SparkPlot({
color={colorValue}
curve={CurveType.CURVE_MONOTONE_X}
/>
{hasComparisonSeries && (
<AreaSeries
id="comparisonSeries"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={'x'}
yAccessors={['y']}
data={comparisonSeries}
color={theme.eui.euiColorLightestShade}
curve={CurveType.CURVE_MONOTONE_X}
/>
)}
</Chart>
)}
</EuiFlexItem>
Expand Down
Loading