blob: bca8b5fb1a04cc3672b90b558a34ef4118f63398 [file] [edit]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use size file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import {Duration} from '../base/time';
import type {SqlValue} from '../trace_processor/query_result';
import {Box} from '../widgets/box';
import {Stack, StackAuto, StackFixed} from '../widgets/stack';
import type {BarChartData, ColumnDef} from './aggregation';
import {
DataGrid,
renderCell,
type DataGridApi,
} from './widgets/datagrid/datagrid';
import {defaultValueFormatter} from './widgets/datagrid/export_utils';
import type {AggregatePivotModel, DataGridState} from './aggregation_adapter';
import type {
CellRenderer,
ColumnSchema,
ColumnType,
SchemaRegistry,
} from './widgets/datagrid/datagrid_schema';
import type {DataSource} from './widgets/datagrid/data_source';
import {Button} from '../widgets/button';
import {Icons} from '../base/semantic_icons';
export interface AggregationPanelAttrs {
readonly dataSource: DataSource;
readonly columns: ReadonlyArray<ColumnDef> | AggregatePivotModel;
readonly barChartData?: ReadonlyArray<BarChartData>;
readonly onReady?: (api: DataGridApi) => void;
readonly dataGridState?: DataGridState;
readonly onClearGridState?: () => void;
readonly controls?: m.Children;
}
export class AggregationPanel
implements m.ClassComponent<AggregationPanelAttrs>
{
view({attrs}: m.CVnode<AggregationPanelAttrs>) {
const {
dataSource,
columns,
barChartData,
onReady,
dataGridState,
onClearGridState,
controls,
} = attrs;
return m(Stack, {fillHeight: true, spacing: 'none'}, [
barChartData && m(StackFixed, m(Box, this.renderBarChart(barChartData))),
m(
StackAuto,
this.renderTable(
controls,
dataSource,
columns,
onReady,
dataGridState,
onClearGridState,
),
),
]);
}
private renderTable(
controls: m.Children | undefined,
dataSource: DataSource,
model: ReadonlyArray<ColumnDef> | AggregatePivotModel,
onReady?: (api: DataGridApi) => void,
dataGridState?: DataGridState,
onClearGridState?: () => void,
) {
// Get column definitions - either from pivot model or flat model
const columnDefs = 'groupBy' in model ? model.columns : model;
// Build schema from column definitions
const columnSchema: ColumnSchema = {};
for (const c of columnDefs) {
columnSchema[c.columnId] = {
title: c.title,
titleString: c.title,
columnType: filterTypeForColumnDef(c.formatHint),
cellRenderer:
c.cellRenderer ?? getCellRenderer(c.formatHint, c.columnId),
cellFormatter: getValueFormatter(c.formatHint),
};
}
const schema: SchemaRegistry = {data: columnSchema};
return m(DataGrid, {
fillHeight: true,
schema,
rootSchema: 'data',
data: dataSource,
onReady,
// Spread controlled state props (columns, filters, pivot and callbacks)
...dataGridState,
toolbarItemsLeft: [
controls,
onClearGridState &&
m(Button, {
icon: Icons.ResetState,
tooltip: 'Reset grid state to default for this aggregation',
onclick: () => onClearGridState(),
}),
],
});
}
private renderBarChart(data: ReadonlyArray<BarChartData>) {
const summedValues = data.reduce((sum, item) => sum + item.value, 0);
return m(
'.pf-aggregation-panel__bar-chart',
data.map((d) => {
const width = (d.value / summedValues) * 100;
return m(
'.pf-aggregation-panel__bar-chart-bar',
{
style: {
background: d.color.base.cssString,
color: d.color.textBase.cssString,
borderColor: d.color.variant.cssString,
width: `${width}%`,
},
},
d.title,
);
}),
);
}
}
function filterTypeForColumnDef(
formatHint: string | undefined,
): ColumnType | undefined {
switch (formatHint) {
case undefined:
return undefined;
case 'ID':
return 'identifier';
case 'NUMERIC':
case 'DURATION_NS':
case 'PERCENT':
return 'quantitative';
case 'STRING':
default:
return 'text';
}
}
function getValueFormatter(
formatHint: string | undefined,
): (value: SqlValue) => string {
switch (formatHint) {
case 'DURATION_NS':
return formatDurationValue;
case 'PERCENT':
return formatPercentValue;
default:
return defaultValueFormatter;
}
}
function getCellRenderer(
formatHint: string | undefined,
columnName: string,
): CellRenderer {
switch (formatHint) {
case 'DURATION_NS':
return formatDurationValue;
case 'PERCENT':
return formatPercentValue;
default:
return function (value) {
return renderCell(value, columnName);
};
}
}
function formatDurationValue(value: SqlValue): string {
if (typeof value === 'bigint') {
return Duration.humanise(value);
} else if (typeof value === 'number') {
return Duration.humanise(BigInt(Math.round(value)));
} else {
return String(value);
}
}
function formatPercentValue(value: SqlValue): string {
if (typeof value === 'number') {
return `${(value * 100).toFixed(2)}%`;
} else {
return String(value);
}
}