Add generic tabs demo (WIP) Change-Id: Ic52abb653f94bdaad6575e523418d65634612ae0
diff --git a/ui/src/components/details/sql_table_tab.ts b/ui/src/components/details/sql_table_tab.ts index 81f410e..15567e7 100644 --- a/ui/src/components/details/sql_table_tab.ts +++ b/ui/src/components/details/sql_table_tab.ts
@@ -46,6 +46,9 @@ imports?: string[]; } +/** + * @deprecated New code should not depend on this. + */ export function addLegacyTableTab( trace: Trace, config: AddSqlTableTabParams,
diff --git a/ui/src/components/extensions.ts b/ui/src/components/extensions.ts index dcd5a54..ef5a90b 100644 --- a/ui/src/components/extensions.ts +++ b/ui/src/components/extensions.ts
@@ -31,6 +31,10 @@ export interface ExtensionApi { addDebugSliceTrack: typeof addDebugSliceTrack; addDebugCounterTrack: typeof addDebugCounterTrack; + + /** + * @deprecated New code should not depend on this. + */ addLegacySqlTableTab: typeof addLegacyTableTab; addVisualizedArgTracks: typeof addVisualizedArgTracks; addQueryResultsTab: typeof addQueryResultsTab;
diff --git a/ui/src/components/widgets/datagrid/sql_schema.ts b/ui/src/components/widgets/datagrid/sql_schema.ts index 10bc6ee..1a3383e 100644 --- a/ui/src/components/widgets/datagrid/sql_schema.ts +++ b/ui/src/components/widgets/datagrid/sql_schema.ts
@@ -257,7 +257,7 @@ this.registry = registry; this.rootSchemaName = rootSchemaName; this.baseAlias = - baseAlias ?? `${registry[rootSchemaName]?.table ?? 'base'}_0`; + baseAlias ?? `${rootSchemaName.replace(/[^a-zA-Z0-9_]/g, '_')}_0`; } /**
diff --git a/ui/src/components/widgets/sql/pivot_table/pivot_table.ts b/ui/src/components/widgets/sql/pivot_table/pivot_table.ts index 85137f5..2ef25bd 100644 --- a/ui/src/components/widgets/sql/pivot_table/pivot_table.ts +++ b/ui/src/components/widgets/sql/pivot_table/pivot_table.ts
@@ -42,6 +42,9 @@ extraRowButton?(node: PivotTreeNode): m.Children; } +/** + * @deprecated Use DataGrid instead. + */ export class PivotTable implements m.ClassComponent<PivotTableAttrs> { view({attrs}: m.CVnode<PivotTableAttrs>) { const state = attrs.state;
diff --git a/ui/src/core/tab_manager.ts b/ui/src/core/tab_manager.ts index e99603e..ea5ceb3 100644 --- a/ui/src/core/tab_manager.ts +++ b/ui/src/core/tab_manager.ts
@@ -13,8 +13,14 @@ // limitations under the License. import {DetailsPanel} from '../public/details_panel'; -import {TabDescriptor, TabManager} from '../public/tab'; +import { + GenericTab, + GenericTabInstance, + TabDescriptor, + TabManager, +} from '../public/tab'; import {DrawerPanelVisibility, toggleVisibility} from '../widgets/drawer_panel'; +import {Registry} from '../base/registry'; export interface ResolvedTab { uri: string; @@ -154,6 +160,27 @@ * @returns List of resolved tabs. */ resolveTabs(tabUris: string[]): ResolvedTab[] { + const genericTabs = this.genericTabs.map( + ({id, tabId, config}): ResolvedTab => { + return { + uri: id, + tab: { + uri: id, + isEphemeral: true, + content: { + render: () => { + const resolvedTabRenderer = this.genericTabsReg.get(tabId); + const {schema, render} = resolvedTabRenderer; + const parsedConfig = schema.parse(config); + return render(id, parsedConfig); + }, + getTitle: () => `Generic Tab (${tabId})`, + }, + }, + }; + }, + ); + // Refresh the list of old tabs const newTabs = new Map<string, TabDescriptor>(); const tabs: ResolvedTab[] = []; @@ -185,7 +212,7 @@ this._instantiatedTabs = newTabs; - return tabs; + return tabs.concat(genericTabs); } setTabPanelVisibility(visibility: DrawerPanelVisibility): void { @@ -222,4 +249,18 @@ this._registry.delete(tab.uri); } } + + private readonly genericTabsReg = new Registry<GenericTab<unknown>>( + ({id}) => id, + ); + private readonly genericTabs: GenericTabInstance[] = []; + + registerGenericTab<T>(tab: GenericTab<T>) { + this.genericTabsReg.register(tab as unknown as GenericTab<unknown>); + } + + openGenericTab(config: GenericTabInstance) { + this.genericTabs.push(config); + this._currentTab = config.id; + } }
diff --git a/ui/src/plugins/dev.perfetto.PivotTable/index.ts b/ui/src/plugins/dev.perfetto.PivotTable/index.ts new file mode 100644 index 0000000..99e018f --- /dev/null +++ b/ui/src/plugins/dev.perfetto.PivotTable/index.ts
@@ -0,0 +1,79 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this 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 z from 'zod'; +import {PerfettoPlugin} from '../../public/plugin'; +import {Trace} from '../../public/trace'; +import {shortUuid} from '../../base/uuid'; +import { + DataGrid, + DataGridAttrs, +} from '../../components/widgets/datagrid/datagrid'; +import {SQLDataSource} from '../../components/widgets/datagrid/sql_data_source'; +import {createSimpleSchema} from '../../components/widgets/datagrid/sql_schema'; + +export default class implements PerfettoPlugin { + static readonly id = 'dev.perfetto.PivotTable'; + + private readonly dataSourceMap = new Map<string, SQLDataSource>(); + + async onTraceLoad(trace: Trace): Promise<void> { + trace.commands.registerCommand({ + id: 'dev.perfetto.PivotTable.openExamplePivotTableTab', + name: 'Open example Pivot Table Tab', + callback: () => { + trace.tabs.openGenericTab({ + id: shortUuid(), + tabId: 'dev.perfetto.PivotTableTab', + config: {query: 'SELECT * FROM slice LIMIT 1000'}, + }); + }, + }); + + trace.tabs.registerGenericTab({ + id: 'dev.perfetto.PivotTableTab', + schema: z.object({ + query: z.string(), + }), + render: (id, config) => { + console.log('Rendering Pivot Table Tab with config:', config); + const tableName = 'query'; + let dataSource = this.dataSourceMap.get(id); + if (!dataSource) { + const sqlSchema = createSimpleSchema(config.query, tableName); + dataSource = new SQLDataSource({ + engine: trace.engine, + sqlSchema: sqlSchema, + rootSchemaName: tableName, + }); + this.dataSourceMap.set(id, dataSource); + } + return m(DataGrid, { + fillHeight: true, + data: dataSource, + schema: { + [tableName]: { + id: {}, + name: {}, + ts: {}, + dur: {}, + }, + }, + rootSchema: tableName, + } satisfies DataGridAttrs); + }, + }); + } +}
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts index b8308f8..650ef0f 100644 --- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts +++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts
@@ -29,6 +29,9 @@ import {SLICE_TABLE} from '../../components/widgets/sql/table_definitions'; import {resolveTableDefinition} from '../../components/widgets/sql/table/columns'; +/** + * @deprecated Use DataGrid in a custom tab instead. + */ export class PivotTableTab implements AreaSelectionTab { readonly id = 'pivot_table'; readonly name = 'Pivot Table';
diff --git a/ui/src/public/tab.ts b/ui/src/public/tab.ts index 97d7065..c811b9d 100644 --- a/ui/src/public/tab.ts +++ b/ui/src/public/tab.ts
@@ -13,12 +13,32 @@ // limitations under the License. import m from 'mithril'; +import z from 'zod'; + +export interface GenericTabInstance { + // Id of the tab renderer instance + readonly tabId: string; + + // Unique ID for this tab instance + readonly id: string; + + // This tab's configuration + readonly config: unknown; +} + +export interface GenericTab<T> { + readonly id: string; + readonly render: (id: string, config: T) => m.Children; + readonly schema: z.ZodType<T>; +} export interface TabManager { registerTab(tab: TabDescriptor): void; showTab(uri: string): void; hideTab(uri: string): void; addDefaultTab(uri: string): void; + registerGenericTab<T>(tab: GenericTab<T>): void; + openGenericTab(config: GenericTabInstance): void; } export interface Tab {