| // 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); |
| } |
| } |