[Profiling] Click on series in stacked chart opens traces view (#153325)

In the Threads view when the user clicks on one of the stacked bar it
navigates to the Traces view filtering the result based on the category.


https://user-images.githubusercontent.com/55978943/226430014-0fbeaad8-6ecb-4e34-a841-926ee2c8ddb9.mov
This commit is contained in:
Cauê Marcondes 2023-03-22 09:21:41 -04:00 committed by GitHub
parent 1c8d63775d
commit ba755f95e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 25 deletions

View file

@ -20,6 +20,7 @@ import { useProfilingDependencies } from '../contexts/profiling_dependencies/use
import { ProfilingAppPageTemplate } from '../profiling_app_page_template';
import { StackedBarChart } from '../stacked_bar_chart';
import { getStackTracesTabs } from './get_stack_traces_tabs';
import { getTracesViewRouteParams } from './utils';
export function StackTracesView() {
const routePath = useProfilingRoutePath();
@ -80,6 +81,13 @@ export function StackTracesView() {
const { data } = state;
function onStackedBarClick(category: string) {
profilingRouter.push(
'/stacktraces/{topNType}',
getTracesViewRouteParams({ query, topNType: path.topNType, category })
);
}
return (
<ProfilingAppPageTemplate tabs={tabs}>
<EuiFlexGroup direction="column">
@ -140,6 +148,7 @@ export function StackTracesView() {
});
}}
showFrames={topNType === TopNType.Traces}
onClick={topNType === TopNType.Threads ? onStackedBarClick : undefined}
/>
</AsyncComponent>
</EuiFlexItem>
@ -155,7 +164,7 @@ export function StackTracesView() {
/>
</AsyncComponent>
</EuiFlexItem>
{(data?.charts.length ?? 0) > limit ? (
{(data?.charts.length ?? 0) > limit && (
<EuiFlexItem>
<EuiButton
onClick={() => {
@ -173,7 +182,7 @@ export function StackTracesView() {
})}
</EuiButton>
</EuiFlexItem>
) : null}
)}
</EuiFlexGroup>
</ProfilingAppPageTemplate>
);

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces';
import { getTracesViewRouteParams } from './utils';
describe('stack traces view utils', () => {
describe('getTracesViewRouteParams', () => {
it('filters by category only', () => {
expect(
getTracesViewRouteParams({
query: {
rangeFrom: 'now-15m',
rangeTo: 'now',
displayAs: StackTracesDisplayOption.StackTraces,
kuery: '',
limit: 10,
},
topNType: TopNType.Traces,
category: 'Foo',
})
).toEqual({
path: { topNType: 'traces' },
query: {
rangeFrom: 'now-15m',
rangeTo: 'now',
displayAs: 'stackTraces',
kuery: 'Stacktrace.id:"Foo"',
limit: 10,
},
});
});
it('keeps current filter and adds category', () => {
expect(
getTracesViewRouteParams({
query: {
rangeFrom: 'now-15m',
rangeTo: 'now',
displayAs: StackTracesDisplayOption.StackTraces,
kuery: 'container.name:"bar"',
limit: 10,
},
topNType: TopNType.Traces,
category: 'Foo',
})
).toEqual({
path: { topNType: 'traces' },
query: {
rangeFrom: 'now-15m',
rangeTo: 'now',
displayAs: 'stackTraces',
kuery: '(container.name:"bar") AND Stacktrace.id:"Foo"',
limit: 10,
},
});
});
});
});

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { TypeOf } from '@kbn/typed-react-router-config';
import { getFieldNameForTopNType, TopNType } from '../../../common/stack_traces';
import { ProfilingRoutes } from '../../routing';
export function getTracesViewRouteParams({
query,
topNType,
category,
}: {
query: TypeOf<ProfilingRoutes, '/stacktraces/{topNType}'>['query'];
topNType: TopNType;
category: string;
}) {
return {
path: { topNType: TopNType.Traces },
query: {
...query,
kuery: `${query.kuery ? `(${query.kuery}) AND ` : ''}${getFieldNameForTopNType(
topNType
)}:"${category}"`,
},
};
}

View file

@ -22,11 +22,11 @@ import {
import { EuiPanel } from '@elastic/eui';
import { keyBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import { TopNSample, TopNSubchart } from '../../common/topn';
import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting';
import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme';
import { asPercentage } from '../utils/formatters/as_percentage';
import { SubChart } from './subchart';
import { TopNSample, TopNSubchart } from '../../../common/topn';
import { useKibanaTimeZoneSetting } from '../../hooks/use_kibana_timezone_setting';
import { useProfilingChartsTheme } from '../../hooks/use_profiling_charts_theme';
import { asPercentage } from '../../utils/formatters/as_percentage';
import { SubChart } from '../subchart';
// 2 * padding (16px)
const MAX_TOOLTIP_WIDTH = 224;
@ -37,15 +37,17 @@ export interface StackedBarChartProps {
onBrushEnd: (range: { rangeFrom: string; rangeTo: string }) => void;
charts: TopNSubchart[];
showFrames: boolean;
onClick?: (category: string) => void;
}
export const StackedBarChart: React.FC<StackedBarChartProps> = ({
export function StackedBarChart({
height,
asPercentages,
onBrushEnd,
charts,
showFrames,
}) => {
onClick,
}: StackedBarChartProps) {
const chartsbyCategoryMap = useMemo(() => {
return keyBy(charts, 'Category');
}, [charts]);
@ -107,6 +109,15 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
const [value] = events[0] as XYChartElementEvent;
setHighlightedSample(value.datum as TopNSample);
}}
onElementClick={
onClick
? (elements) => {
const [value] = elements[0] as XYChartElementEvent;
const sample = value.datum as TopNSample;
onClick(sample.Category);
}
: undefined
}
onElementOut={() => {
setHighlightedSample(undefined);
}}
@ -135,4 +146,4 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
/>
</Chart>
);
};
}

View file

@ -32,7 +32,6 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { StackFrameMetadata } from '../../common/profiling';
import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces';
import { CountPerTime, OTHER_BUCKET_LABEL, TopNSample } from '../../common/topn';
import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting';
import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme';
@ -41,6 +40,7 @@ import { useProfilingRouter } from '../hooks/use_profiling_router';
import { asNumber } from '../utils/formatters/as_number';
import { asPercentage } from '../utils/formatters/as_percentage';
import { StackFrameSummary } from './stack_frame_summary';
import { getTracesViewRouteParams } from './stack_traces_view/utils';
export interface SubChartProps {
index: number;
@ -62,7 +62,7 @@ export interface SubChartProps {
const NUM_DISPLAYED_FRAMES = 5;
export const SubChart: React.FC<SubChartProps> = ({
export function SubChart({
index,
color,
category,
@ -78,24 +78,17 @@ export const SubChart: React.FC<SubChartProps> = ({
showFrames,
padTitle,
sample,
}) => {
}: SubChartProps) {
const theme = useEuiTheme();
const profilingRouter = useProfilingRouter();
const { path, query } = useProfilingParams('/stacktraces/{topNType}');
const href = profilingRouter.link('/stacktraces/{topNType}', {
path: {
topNType: TopNType.Traces,
},
query: {
...query,
kuery: `${query.kuery ? `(${query.kuery}) AND ` : ''}${getFieldNameForTopNType(
path.topNType
)}:"${category}"`,
},
});
const href = profilingRouter.link(
'/stacktraces/{topNType}',
getTracesViewRouteParams({ query, topNType: path.topNType, category })
);
const timeZone = useKibanaTimeZoneSetting();
@ -304,4 +297,4 @@ export const SubChart: React.FC<SubChartProps> = ({
{bottomElement}
</EuiFlexGroup>
);
};
}