| // 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 {FuzzyFinder, FuzzySegment} from '../../base/fuzzy'; |
| import {Accordion, AccordionItem} from '../../widgets/accordion'; |
| import {Button} from '../../widgets/button'; |
| import {CopyToClipboardButton} from '../../widgets/copy_to_clipboard_button'; |
| import {Icon} from '../../widgets/icon'; |
| import {TextInput} from '../../widgets/text_input'; |
| import {SqlModules, SqlTable} from '../dev.perfetto.SqlModules/sql_modules'; |
| import { |
| perfettoSqlTypeIcon, |
| perfettoSqlTypeToString, |
| } from '../../trace_processor/perfetto_sql_type'; |
| import {EmptyState} from '../../widgets/empty_state'; |
| |
| interface FilteredTable { |
| table: SqlTable; |
| segments: FuzzySegment[]; |
| } |
| |
| function renderHighlightedName(segments: FuzzySegment[]): m.Children { |
| return segments.map(({matching, value}) => |
| matching ? m('span.pf-simple-table-list__highlight', value) : value, |
| ); |
| } |
| |
| export interface TableListAttrs { |
| readonly sqlModules: SqlModules; |
| // Called when user wants to query a table in a new tab |
| onQueryTable?(tableName: string, query: string): void; |
| } |
| |
| export class TableList implements m.ClassComponent<TableListAttrs> { |
| private searchQuery = ''; |
| private expandedTable: string | undefined = undefined; |
| |
| view({attrs}: m.CVnode<TableListAttrs>): m.Children { |
| const tables = attrs.sqlModules.listTables(); |
| |
| // Filter tables using fuzzy search (results ordered by relevance) |
| const searchTerm = this.searchQuery.trim(); |
| let filteredTables: FilteredTable[]; |
| if (searchTerm === '') { |
| filteredTables = tables.map((table) => ({ |
| table, |
| segments: [{matching: false, value: table.name}], |
| })); |
| } else { |
| const finder = new FuzzyFinder(tables, (t) => t.name); |
| filteredTables = finder.find(searchTerm).map((result) => ({ |
| table: result.item, |
| segments: result.segments, |
| })); |
| } |
| |
| const items: AccordionItem[] = filteredTables.map(({table, segments}) => ({ |
| id: table.name, |
| header: m( |
| 'code.pf-simple-table-list__item-name', |
| renderHighlightedName(segments), |
| ), |
| content: this.renderTableContent(table, attrs.onQueryTable), |
| })); |
| |
| return m( |
| '.pf-simple-table-list', |
| m(TextInput, { |
| className: 'pf-simple-table-list__search', |
| placeholder: 'Search tables...', |
| value: this.searchQuery, |
| leftIcon: 'search', |
| onInput: (value) => { |
| this.searchQuery = value; |
| }, |
| }), |
| items.length > 0 |
| ? m( |
| '.pf-simple-table-list__items', |
| m(Accordion, { |
| items, |
| expanded: this.expandedTable, |
| onToggle: (id) => { |
| this.expandedTable = id; |
| }, |
| }), |
| ) |
| : m(EmptyState, { |
| title: 'No matching tables found', |
| }), |
| ); |
| } |
| |
| private generateQuery(table: SqlTable): string { |
| const lines: string[] = []; |
| |
| // Add INCLUDE statement if needed |
| if (table.includeKey) { |
| lines.push(`INCLUDE PERFETTO MODULE ${table.includeKey};`); |
| lines.push(''); |
| } |
| |
| // Build SELECT with all columns |
| const columns = |
| table.columns.length > 0 |
| ? table.columns.map((c) => c.name).join(',\n ') |
| : '*'; |
| |
| lines.push('SELECT'); |
| lines.push(` ${columns}`); |
| lines.push(`FROM ${table.name}`); |
| lines.push('LIMIT 1000'); |
| |
| return lines.join('\n'); |
| } |
| |
| private renderTableContent( |
| table: SqlTable, |
| onQueryTable?: (tableName: string, query: string) => void, |
| ): m.Children { |
| return [ |
| // Description |
| table.description && |
| m('.pf-simple-table-list__description', table.description), |
| |
| m( |
| '.pf-simple-table-list__detail-row', |
| m('span.pf-simple-table-list__detail-label', 'Table name'), |
| m('code.pf-simple-table-list__detail-value', table.name), |
| m(CopyToClipboardButton, { |
| className: 'pf-show-on-hover', |
| textToCopy: table.name, |
| tooltip: 'Copy table name to clipboard', |
| }), |
| onQueryTable && |
| m(Button, { |
| className: 'pf-show-on-hover', |
| icon: 'play_arrow', |
| compact: true, |
| tooltip: `SELECT * FROM ${table.name} in a new tab`, |
| onclick: () => onQueryTable(table.name, this.generateQuery(table)), |
| }), |
| ), |
| // Module |
| table.includeKey && |
| m( |
| '.pf-simple-table-list__detail-row', |
| m('span.pf-simple-table-list__detail-label', 'Include'), |
| m( |
| 'code.pf-simple-table-list__detail-value', |
| `INCLUDE PERFETTO MODULE ${table.includeKey};`, |
| ), |
| m(CopyToClipboardButton, { |
| className: 'pf-show-on-hover', |
| textToCopy: `INCLUDE PERFETTO MODULE ${table.includeKey};`, |
| tooltip: 'Copy include string to clipboard', |
| }), |
| ), |
| |
| // Columns |
| table.columns.length > 0 && |
| m( |
| '.pf-simple-table-list__columns', |
| m('span.pf-simple-table-list__detail-label', 'Columns'), |
| m( |
| '.pf-simple-table-list__column-list', |
| table.columns.map((col) => |
| m( |
| '.pf-simple-table-list__column', |
| m(Icon, { |
| icon: perfettoSqlTypeIcon(col.type), |
| className: 'pf-simple-table-list__column-icon', |
| }), |
| m( |
| '.pf-simple-table-list__column-info', |
| m( |
| '.pf-simple-table-list__column-header', |
| m('code.pf-simple-table-list__column-name', col.name), |
| m( |
| '.pf-simple-table-list__column-copy', |
| m(CopyToClipboardButton, { |
| textToCopy: col.name, |
| compact: true, |
| tooltip: 'Copy column name to clipboard', |
| }), |
| ), |
| m( |
| 'span.pf-simple-table-list__column-type', |
| perfettoSqlTypeToString(col.type), |
| ), |
| ), |
| col.description && |
| m('.pf-simple-table-list__column-desc', col.description), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ]; |
| } |
| } |