[Profiling][OnWeek project] Improve Diff topN functions grid view. (#170008)

-  Same height on both sides
-  Maximize feature
-  ~~Highlight the corresponding item on the other side~~
-  Details of the corresponding item on the other side



dd81664e-b5ae-403d-847c-b56ca8881277
This commit is contained in:
Cauê Marcondes 2023-11-22 19:13:18 +00:00 committed by GitHub
parent e7eba99835
commit 74e46f6939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 597 additions and 106 deletions

View file

@ -207,3 +207,21 @@ export const topNFunctionSortFieldRt = t.union([
t.literal(TopNFunctionSortField.AnnualizedCo2),
t.literal(TopNFunctionSortField.AnnualizedDollarCost),
]);
export enum TopNComparisonFunctionSortField {
ComparisonRank = 'comparison_rank',
ComparisonFrame = 'comparison_frame',
ComparisonSamples = 'comparison_samples',
ComparisonSelfCPU = 'comparison_selfCPU',
ComparisonTotalCPU = 'comparison_totalCPU',
ComparisonDiff = 'comparison_diff',
}
export const topNComparisonFunctionSortFieldRt = t.union([
t.literal(TopNComparisonFunctionSortField.ComparisonRank),
t.literal(TopNComparisonFunctionSortField.ComparisonFrame),
t.literal(TopNComparisonFunctionSortField.ComparisonSamples),
t.literal(TopNComparisonFunctionSortField.ComparisonSelfCPU),
t.literal(TopNComparisonFunctionSortField.ComparisonTotalCPU),
t.literal(TopNComparisonFunctionSortField.ComparisonDiff),
]);

View file

@ -30,6 +30,8 @@ export {
createTopNFunctions,
TopNFunctionSortField,
topNFunctionSortFieldRt,
TopNComparisonFunctionSortField,
topNComparisonFunctionSortFieldRt,
} from './common/functions';
export type { CalleeTree } from './common/callee';

View file

@ -167,17 +167,8 @@ describe('Differential Functions page', () => {
});
cy.wait('@getTopNFunctions');
cy.wait('@getTopNFunctions');
cy.get('[data-test-subj="topNFunctionsGrid"] .euiDataGridRow').should('have.length.gt', 1);
cy.get('[data-test-subj="TopNFunctionsComparisonGrid"] .euiDataGridRow').should(
'have.length.gt',
1
);
cy.get(
'[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
).contains('vmlinux');
cy.get(
'[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
).contains('vmlinux');
cy.get('[data-test-subj="frame"]').contains('vmlinux');
cy.get('[data-test-subj="comparison_frame"]').contains('vmlinux');
cy.addKqlFilter({
key: 'process.thread.name',
@ -190,12 +181,8 @@ describe('Differential Functions page', () => {
});
cy.wait('@getTopNFunctions');
cy.wait('@getTopNFunctions');
cy.get(
'[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
).contains('libsystemd-shared-237.so');
cy.get(
'[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
).contains('libjvm.so');
cy.get('[data-test-subj="frame"]').contains('libsystemd-shared-237.so');
cy.get('[data-test-subj="comparison_frame"]').contains('libjvm.so');
});
});
});

View file

@ -43,6 +43,7 @@ const mockPlugin = {
};
const mockCore = {
uiSettings: { get: () => {} },
application: {
currentAppId$: new Observable(),
getUrlForApp: (appId: string) => '',

View file

@ -0,0 +1,129 @@
/*
* 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 { EuiDataGridColumn, EuiDataGridColumnCellAction } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TopNComparisonFunctionSortField, TopNFunctionSortField } from '@kbn/profiling-utils';
import React from 'react';
import { CPULabelWithHint } from '../cpu_label_with_hint';
import { LabelWithHint } from '../label_with_hint';
export const getColumns = (
compareFrameAction: EuiDataGridColumnCellAction
): EuiDataGridColumn[] => [
{
id: TopNFunctionSortField.Rank,
actions: { showHide: false },
displayAsText: 'Rank',
initialWidth: 65,
schema: 'numeric',
},
{
id: TopNFunctionSortField.Frame,
actions: { showHide: false },
displayAsText: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
defaultMessage: 'Function',
}),
cellActions: [compareFrameAction],
},
{
id: TopNFunctionSortField.Samples,
initialWidth: 120,
schema: 'numeric',
actions: { showHide: false },
display: (
<LabelWithHint
label={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
defaultMessage: 'Samples',
})}
hint={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel.hint', {
defaultMessage: 'Estimated values',
})}
labelSize="s"
labelStyle={{ fontWeight: 700 }}
iconSize="s"
/>
),
},
{
id: TopNFunctionSortField.SelfCPU,
actions: { showHide: false },
schema: 'numeric',
initialWidth: 120,
display: (
<CPULabelWithHint type="self" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
),
},
{
id: TopNFunctionSortField.TotalCPU,
actions: { showHide: false },
schema: 'numeric',
initialWidth: 120,
display: (
<CPULabelWithHint type="total" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
),
},
{
id: TopNComparisonFunctionSortField.ComparisonRank,
actions: { showHide: false },
schema: 'numeric',
displayAsText: 'Rank',
initialWidth: 69,
displayHeaderCellProps: { className: 'thickBorderLeft' },
},
{
id: TopNComparisonFunctionSortField.ComparisonFrame,
actions: { showHide: false },
displayAsText: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
defaultMessage: 'Function',
}),
cellActions: [compareFrameAction],
},
{
id: TopNComparisonFunctionSortField.ComparisonSamples,
actions: { showHide: false },
schema: 'numeric',
initialWidth: 120,
display: (
<LabelWithHint
label={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
defaultMessage: 'Samples',
})}
hint={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel.hint', {
defaultMessage: 'Estimated values',
})}
labelSize="s"
labelStyle={{ fontWeight: 700 }}
iconSize="s"
/>
),
},
{
id: TopNComparisonFunctionSortField.ComparisonSelfCPU,
actions: { showHide: false },
schema: 'numeric',
initialWidth: 120,
display: (
<CPULabelWithHint type="self" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
),
},
{
id: TopNComparisonFunctionSortField.ComparisonTotalCPU,
actions: { showHide: false },
schema: 'numeric',
initialWidth: 120,
display: (
<CPULabelWithHint type="total" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
),
},
{
displayAsText: 'Diff',
actions: { showHide: false },
id: TopNComparisonFunctionSortField.ComparisonDiff,
initialWidth: 70,
isSortable: false,
},
];

View file

@ -0,0 +1,121 @@
/*
* 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 {
EuiBasicTable,
EuiDataGridColumnCellAction,
EuiDataGridColumnCellActionProps,
EuiPopover,
EuiPopoverTitle,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { getCalleeFunction } from '@kbn/profiling-utils';
import { getFrameIdentification, isComparisonColumn, SelectedFrame } from '.';
import { IFunctionRow } from '../topn_functions/utils';
interface Props {
baseRows: IFunctionRow[];
comparisonRows: IFunctionRow[];
selectedFrame?: SelectedFrame;
onClick: (selectedFrame?: SelectedFrame) => void;
}
export const getCompareFrameAction =
({ baseRows, comparisonRows, selectedFrame, onClick }: Props): EuiDataGridColumnCellAction =>
({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => {
const isComparison = isComparisonColumn(columnId);
const currentRow = isComparison ? comparisonRows[rowIndex] : baseRows[rowIndex];
if (currentRow === undefined) {
return null;
}
const currentFrameId = getFrameIdentification(currentRow.frame);
const isOpen = selectedFrame
? selectedFrame.currentFrameId === currentFrameId &&
selectedFrame.isComparison === isComparison
: false;
const compareRow = isComparison
? baseRows.find((item) => getFrameIdentification(item.frame) === currentFrameId)
: comparisonRows.find((item) => getFrameIdentification(item.frame) === currentFrameId);
return (
<EuiPopover
button={
<Component
onClick={() => {
onClick({ currentFrameId, isComparison });
}}
iconType="inspect"
>
{i18n.translate('xpack.profiling.compareFrame.component.findLabel', {
defaultMessage: 'Find corresponding frame',
})}
</Component>
}
isOpen={isOpen}
closePopover={() => {
onClick(undefined);
}}
anchorPosition="upRight"
css={css`
.euiPopover__anchor {
align-items: start;
display: flex;
}
`}
>
{compareRow ? (
<div style={{ maxWidth: 400 }}>
<EuiPopoverTitle paddingSize="s">
{isComparison
? i18n.translate('xpack.profiling.diffTopNFunctions.baseLineFunction', {
defaultMessage: 'Baseline function',
})
: i18n.translate('xpack.profiling.diffTopNFunctions.comparisonLineFunction', {
defaultMessage: 'Comparison function',
})}
</EuiPopoverTitle>
<EuiTitle size="xs">
<EuiText>{getCalleeFunction(compareRow.frame)}</EuiText>
</EuiTitle>
<EuiBasicTable
items={[compareRow]}
columns={[
{ field: 'rank', name: 'Rank' },
{
field: 'samples',
name: 'Samples',
render: (_, value) => value.samples.toLocaleString(),
},
{
field: 'selfCPUPerc',
name: 'Self CPU',
render: (_, value) => `${value.selfCPUPerc.toFixed(2)}%`,
},
{
field: 'totalCPUPerc',
name: 'Total CPU',
render: (_, value) => `${value.totalCPUPerc.toFixed(2)}%`,
},
]}
/>
</div>
) : (
<EuiText color="subdued" size="s">
{i18n.translate('xpack.profiling.diffTopNFunctions.noCorrespondingValueFound', {
defaultMessage: 'No corresponding value found',
})}
</EuiText>
)}
</EuiPopover>
);
};

View file

@ -0,0 +1,265 @@
/*
* 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 {
EuiDataGrid,
EuiDataGridCellValueElementProps,
EuiDataGridColumn,
EuiDataGridSorting,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import {
getCalleeFunction,
StackFrameMetadata,
TopNComparisonFunctionSortField,
TopNFunctions,
TopNFunctionSortField,
} from '@kbn/profiling-utils';
import { orderBy } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
import { FunctionRow } from '../topn_functions/function_row';
import { getFunctionsRows, IFunctionRow } from '../topn_functions/utils';
import { getColumns } from './get_columns';
import { getCompareFrameAction } from './get_compare_frame_action';
const removeComparisonFromId = (id: string) => id.replace('comparison_', '');
export const isComparisonColumn = (id: string) => id.startsWith('comparison_');
type SortDirection = 'asc' | 'desc';
function sortRows(data: IFunctionRow[], sortField: string, sortDirection: SortDirection) {
switch (sortField) {
case TopNFunctionSortField.Frame:
return orderBy(data, (row) => getCalleeFunction(row.frame), sortDirection);
case TopNFunctionSortField.SelfCPU:
return orderBy(data, (row) => row.selfCPUPerc, sortDirection);
case TopNFunctionSortField.TotalCPU:
return orderBy(data, (row) => row.totalCPUPerc, sortDirection);
default:
return orderBy(data, sortField, sortDirection);
}
}
export type OnChangeSortParams =
| { sortField: TopNFunctionSortField; sortDirection: SortDirection }
| {
comparisonSortField: TopNComparisonFunctionSortField;
comparisonSortDirection: SortDirection;
};
export function getFrameIdentification(frame: StackFrameMetadata) {
return [
frame.SourceFilename,
frame.FunctionName,
frame.ExeFileName,
frame.FileID,
frame.AddressOrLine,
].join('|');
}
export interface SelectedFrame {
currentFrameId?: string;
isComparison: boolean;
}
interface Props {
base?: TopNFunctions;
baselineScaleFactor?: number;
comparison?: TopNFunctions;
comparisonScaleFactor?: number;
onChangePage: (nextPage: number) => void;
onChangeSort: (sorting: OnChangeSortParams) => void;
onFrameClick?: (functionName: string) => void;
pageIndex: number;
sortDirection: 'asc' | 'desc';
sortField: TopNFunctionSortField;
comparisonSortDirection: 'asc' | 'desc';
comparisonSortField: TopNComparisonFunctionSortField;
totalSeconds: number;
}
export function DifferentialTopNFunctionsGrid({
base,
baselineScaleFactor,
comparison,
comparisonScaleFactor,
onChangePage,
onChangeSort,
pageIndex,
sortDirection,
sortField,
totalSeconds,
onFrameClick,
comparisonSortDirection,
comparisonSortField,
}: Props) {
const calculateImpactEstimates = useCalculateImpactEstimate();
const [selectedFrame, setSelectedFrame] = useState<SelectedFrame | undefined>();
const theme = useEuiTheme();
const totalCount = useMemo(() => {
if (!base || !base.TotalCount) {
return 0;
}
return base.TotalCount;
}, [base]);
function onSort(newSortingColumns: EuiDataGridSorting['columns']) {
// As newSortingColumns is an array and we only sort by a single field for both base and comparison
// I need to look for the item that is not the same as in the URL to identify what's the side being sorted.
const sortingItem = newSortingColumns.reverse().find((item) => {
const isComparison = isComparisonColumn(item.id);
if (isComparison) {
return !(comparisonSortField === item.id && comparisonSortDirection === item.direction);
}
return !(sortField === item.id && sortDirection === item.direction);
});
if (sortingItem) {
const isComparison = isComparisonColumn(sortingItem.id);
onChangeSort(
isComparison
? {
comparisonSortDirection: sortingItem.direction,
comparisonSortField: sortingItem.id as TopNComparisonFunctionSortField,
}
: {
sortDirection: sortingItem.direction,
sortField: sortingItem.id as TopNFunctionSortField,
}
);
}
}
const { baseRows, comparisonRows } = useMemo(() => {
return {
baseRows: getFunctionsRows({
calculateImpactEstimates,
topNFunctions: base,
totalSeconds: 900,
}),
comparisonRows: getFunctionsRows({
baselineScaleFactor,
calculateImpactEstimates,
comparisonScaleFactor,
comparisonTopNFunctions: base,
topNFunctions: comparison,
totalSeconds,
}),
};
}, [
base,
baselineScaleFactor,
calculateImpactEstimates,
comparison,
comparisonScaleFactor,
totalSeconds,
]);
const columns: EuiDataGridColumn[] = useMemo(() => {
const compareFrameAction = getCompareFrameAction({
baseRows,
comparisonRows,
onClick: setSelectedFrame,
selectedFrame,
});
return getColumns(compareFrameAction);
}, [baseRows, comparisonRows, selectedFrame]);
const sortedBaseRows = useMemo(() => {
return sortRows(baseRows, sortField, sortDirection);
}, [baseRows, sortDirection, sortField]);
const sortedComparisonRows = useMemo(() => {
return sortRows(
comparisonRows,
removeComparisonFromId(comparisonSortField),
comparisonSortDirection
);
}, [comparisonRows, comparisonSortDirection, comparisonSortField]);
const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id));
function CellValue({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) {
const isComparison = isComparisonColumn(columnId);
const data = isComparison ? sortedComparisonRows[rowIndex] : sortedBaseRows[rowIndex];
useEffect(() => {
// Add thick border to divide the baseline and comparison columns
if (isComparison && columnId === TopNComparisonFunctionSortField.ComparisonRank) {
setCellProps({
style: { borderLeft: theme.euiTheme.border.thick },
});
} else if (columnId === TopNFunctionSortField.TotalCPU) {
setCellProps({
style: { borderRight: theme.euiTheme.border.thin },
});
}
}, [columnId, isComparison, setCellProps]);
if (data === undefined) {
return null;
}
return (
<div data-test-subj={columnId}>
<FunctionRow
functionRow={data}
columnId={removeComparisonFromId(columnId)}
setCellProps={setCellProps}
totalCount={totalCount}
onFrameClick={onFrameClick}
/>
</div>
);
}
const rowCount = Math.min(Math.max(sortedBaseRows.length, sortedComparisonRows.length), 100);
return (
<EuiDataGrid
data-test-subj="profilingDiffTopNFunctionsGrid"
css={css`
.thickBorderLeft {
border-left: ${theme.euiTheme.border.thick} !important;
}
`}
aria-label={i18n.translate('xpack.profiling.onWeelkDiffTopN.euiDataGrid.topNFunctionsLabel', {
defaultMessage: 'TopN functions',
})}
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={rowCount}
renderCellValue={CellValue}
sorting={{
columns: [
{ id: sortField, direction: sortDirection },
{ id: comparisonSortField, direction: comparisonSortDirection },
],
onSort,
}}
pagination={{
pageIndex,
pageSize: 50,
// Left it empty on purpose as it is a required property on the pagination
onChangeItemsPerPage: () => {},
onChangePage,
pageSizeOptions: [],
}}
rowHeightsOptions={{ defaultHeight: 'auto' }}
toolbarVisibility={{
showColumnSelector: false,
showKeyboardShortcuts: false,
showDisplaySelector: false,
showSortSelector: false,
}}
/>
);
}

View file

@ -112,7 +112,7 @@ function DiffColumn({ diff, setCellProps }: DiffColumnProps) {
const dangerColor = useEuiBackgroundColor('danger');
useEffect(() => {
if (diff && diff.rank > 0) {
if (diff && diff.rank !== 0) {
const color = diff.rank > 0 ? 'success' : 'danger';
setCellProps({
style: { backgroundColor: color === 'success' ? successColor : dangerColor },

View file

@ -33,6 +33,7 @@ export function scaleValue({ value, scaleFactor = 1 }: { value: number; scaleFac
}
export interface IFunctionRow {
id: string;
rank: number;
frame: StackFrameMetadata;
samples: number;
@ -106,6 +107,7 @@ export function getFunctionsRows({
const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
return {
id: comparisonRow.Id,
rank: topN.Rank - comparisonRow.Rank,
samples: scaledDiffSamples,
selfCPU: comparisonRow.CountExclusive,
@ -120,6 +122,7 @@ export function getFunctionsRows({
}
return {
id: topN.Id,
rank: topN.Rank,
frame: topN.Frame,
samples: scaledSelfCPU,

View file

@ -6,33 +6,35 @@
*/
import { i18n } from '@kbn/i18n';
import { toNumberRt } from '@kbn/io-ts-utils';
import {
StackTracesDisplayOption,
TopNComparisonFunctionSortField,
topNComparisonFunctionSortFieldRt,
TopNFunctionSortField,
topNFunctionSortFieldRt,
TopNType,
} from '@kbn/profiling-utils';
import { createRouter, Outlet } from '@kbn/typed-react-router-config';
import * as t from 'io-ts';
import React from 'react';
import {
StackTracesDisplayOption,
TopNType,
TopNFunctionSortField,
topNFunctionSortFieldRt,
} from '@kbn/profiling-utils';
import {
indexLifecyclePhaseRt,
IndexLifecyclePhaseSelectOption,
} from '../../common/storage_explorer';
import { ComparisonMode, NormalizationMode } from '../components/normalization_menu';
import { RedirectTo } from '../components/redirect_to';
import { AddDataTabs, AddDataView } from '../views/add_data_view';
import { DeleteDataView } from '../views/delete_data_view';
import { FlameGraphsView } from '../views/flamegraphs';
import { DifferentialFlameGraphsView } from '../views/flamegraphs/differential_flamegraphs';
import { FlameGraphView } from '../views/flamegraphs/flamegraph';
import { FunctionsView } from '../views/functions';
import { DifferentialTopNFunctionsView } from '../views/functions/differential_topn';
import { TopNFunctionsView } from '../views/functions/topn';
import { AddDataTabs, AddDataView } from '../views/add_data_view';
import { Settings } from '../views/settings';
import { StackTracesView } from '../views/stack_traces_view';
import { StorageExplorerView } from '../views/storage_explorer';
import { RouteBreadcrumb } from './route_breadcrumb';
import { DeleteDataView } from '../views/delete_data_view';
import { Settings } from '../views/settings';
const routes = {
'/settings': {
@ -253,6 +255,8 @@ const routes = {
t.literal(NormalizationMode.Scale),
t.literal(NormalizationMode.Time),
]),
comparisonSortField: topNComparisonFunctionSortFieldRt,
comparisonSortDirection: t.union([t.literal('asc'), t.literal('desc')]),
}),
t.partial({
baseline: toNumberRt,
@ -267,6 +271,8 @@ const routes = {
comparisonRangeTo: 'now',
comparisonKuery: '',
normalizationMode: NormalizationMode.Time,
comparisonSortField: TopNComparisonFunctionSortField.ComparisonRank,
comparisonSortDirection: 'asc',
},
},
},

View file

@ -4,28 +4,21 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiDataGridRefProps,
EuiDataGridSorting,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
import { TopNFunctionSortField } from '@kbn/profiling-utils';
import React, { useRef } from 'react';
import { GridOnScrollProps } from 'react-window';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui';
import React from 'react';
import { AsyncComponent } from '../../../components/async_component';
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
import { FramesSummary } from '../../../components/frames_summary';
import {
DifferentialTopNFunctionsGrid,
OnChangeSortParams,
} from '../../../components/differential_topn_functions_grid';
import {
NormalizationMenu,
NormalizationMode,
NormalizationOptions,
} from '../../../components/normalization_menu';
import { PrimaryAndComparisonSearchBar } from '../../../components/primary_and_comparison_search_bar';
import { TopNFunctionsGrid } from '../../../components/topn_functions';
import { AsyncStatus } from '../../../hooks/use_async';
import { useProfilingParams } from '../../../hooks/use_profiling_params';
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
@ -34,8 +27,6 @@ import { useTimeRange } from '../../../hooks/use_time_range';
import { useTimeRangeAsync } from '../../../hooks/use_time_range_async';
export function DifferentialTopNFunctionsView() {
const baseGridRef = useRef<EuiDataGridRefProps>(null);
const comparisonGridRef = useRef<EuiDataGridRefProps>(null);
const { query } = useProfilingParams('/functions/differential');
const {
rangeFrom,
@ -50,6 +41,8 @@ export function DifferentialTopNFunctionsView() {
baseline = 1,
comparison = 1,
pageIndex = 0,
comparisonSortDirection,
comparisonSortField,
} = query;
const timeRange = useTimeRange({ rangeFrom, rangeTo });
@ -147,20 +140,6 @@ export function DifferentialTopNFunctionsView() {
});
}
function handleBaseGridScroll(scroll: GridOnScrollProps) {
if (comparisonGridRef?.current?.scrollTo) {
comparisonGridRef.current.scrollTo({
scrollTop: scroll.scrollTop,
});
}
}
function handleComparisonGridScroll(scroll: GridOnScrollProps) {
if (baseGridRef?.current?.scrollTo) {
baseGridRef.current.scrollTo({ scrollTop: scroll.scrollTop });
}
}
function handlePageChange(nextPage: number) {
profilingRouter.push('/functions/differential', {
path: {},
@ -168,14 +147,10 @@ export function DifferentialTopNFunctionsView() {
});
}
function handleSortChange(sorting: EuiDataGridSorting['columns'][0]) {
function handleOnSort(sorting: OnChangeSortParams) {
profilingRouter.push('/functions/differential', {
path: {},
query: {
...query,
sortField: sorting.id as TopNFunctionSortField,
sortDirection: sorting.direction,
},
query: { ...query, ...sorting },
});
}
@ -223,50 +198,27 @@ export function DifferentialTopNFunctionsView() {
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem>
<AsyncComponent {...state} size="xl" alignTop>
<TopNFunctionsGrid
ref={baseGridRef}
topNFunctions={state.data}
totalSeconds={timeRange.inSeconds.end - timeRange.inSeconds.start}
isDifferentialView={true}
onFrameClick={handleOnFrameClick}
baselineScaleFactor={isNormalizedByTime ? baselineTime : baseline}
onScroll={handleBaseGridScroll}
pageIndex={pageIndex}
onChangePage={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onChangeSort={handleSortChange}
/>
</AsyncComponent>
</EuiFlexItem>
{comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end ? (
<EuiFlexItem>
<AsyncComponent {...comparisonState} size="xl" alignTop>
<TopNFunctionsGrid
ref={comparisonGridRef}
topNFunctions={comparisonState.data}
baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
comparisonTopNFunctions={state.data}
comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline}
totalSeconds={totalSeconds}
isDifferentialView={true}
onFrameClick={handleOnFrameClick}
onScroll={handleComparisonGridScroll}
showDiffColumn
pageIndex={pageIndex}
onChangePage={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onChangeSort={handleSortChange}
dataTestSubj="TopNFunctionsComparisonGrid"
/>
</AsyncComponent>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
<AsyncComponent
{...(comparisonState.status === AsyncStatus.Loading ? comparisonState : state)}
size="xl"
alignTop
>
<DifferentialTopNFunctionsGrid
base={state.data}
comparison={comparisonState.data}
baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline}
totalSeconds={totalSeconds}
pageIndex={pageIndex}
onChangePage={handlePageChange}
onChangeSort={handleOnSort}
sortField={sortField}
sortDirection={sortDirection}
onFrameClick={handleOnFrameClick}
comparisonSortDirection={comparisonSortDirection}
comparisonSortField={comparisonSortField}
/>
</AsyncComponent>
</EuiFlexItem>
</EuiFlexGroup>
</>

View file

@ -8,6 +8,7 @@
import { EuiPageHeaderContentProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { TopNComparisonFunctionSortField } from '@kbn/profiling-utils';
import { NormalizationMode } from '../../components/normalization_menu';
import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template';
import { RedirectTo } from '../../components/redirect_to';
@ -51,6 +52,12 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
comparisonKuery: query.kuery,
normalizationMode:
'normalizationMode' in query ? query.normalizationMode : NormalizationMode.Time,
comparisonSortField:
'comparisonSortField' in query
? query.comparisonSortField
: TopNComparisonFunctionSortField.ComparisonRank,
comparisonSortDirection:
'comparisonSortDirection' in query ? query.comparisonSortDirection : 'asc',
},
}),
},