1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16import {copyToClipboard} from '../../base/clipboard'; 17import {Icons} from '../../base/semantic_icons'; 18import {exists} from '../../base/utils'; 19import {Button} from '../../widgets/button'; 20import {DetailsShell} from '../../widgets/details_shell'; 21import {Popup, PopupPosition} from '../../widgets/popup'; 22import {AddDebugTrackMenu} from '../tracks/add_debug_track_menu'; 23import {Filter} from '../widgets/sql/table/column'; 24import {SqlTableState} from '../widgets/sql/table/state'; 25import {SqlTable} from '../widgets/sql/table/table'; 26import {SqlTableDescription} from '../widgets/sql/table/table_description'; 27import {Trace} from '../../public/trace'; 28import {MenuItem, PopupMenu2} from '../../widgets/menu'; 29import {addEphemeralTab} from './add_ephemeral_tab'; 30import {Tab} from '../../public/tab'; 31import {addChartTab} from '../widgets/charts/chart_tab'; 32import { 33 ChartOption, 34 createChartConfigFromSqlTableState, 35} from '../widgets/charts/chart'; 36import {AddChartMenuItem} from '../widgets/charts/add_chart_menu'; 37 38export interface AddSqlTableTabParams { 39 table: SqlTableDescription; 40 filters?: Filter[]; 41 imports?: string[]; 42} 43 44export function addSqlTableTab( 45 trace: Trace, 46 config: AddSqlTableTabParams, 47): void { 48 addSqlTableTabWithState( 49 new SqlTableState(trace, config.table, { 50 filters: config.filters, 51 imports: config.imports, 52 }), 53 ); 54} 55 56function addSqlTableTabWithState(state: SqlTableState) { 57 addEphemeralTab('sqlTable', new SqlTableTab(state)); 58} 59 60class SqlTableTab implements Tab { 61 constructor(private readonly state: SqlTableState) {} 62 63 render() { 64 const range = this.state.getDisplayedRange(); 65 const rowCount = this.state.getTotalRowCount(); 66 const navigation = [ 67 exists(range) && 68 exists(rowCount) && 69 `Showing rows ${range.from}-${range.to} of ${rowCount}`, 70 m(Button, { 71 icon: Icons.GoBack, 72 disabled: !this.state.canGoBack(), 73 onclick: () => this.state.goBack(), 74 }), 75 m(Button, { 76 icon: Icons.GoForward, 77 disabled: !this.state.canGoForward(), 78 onclick: () => this.state.goForward(), 79 }), 80 ]; 81 const {selectStatement, columns} = this.state.getCurrentRequest(); 82 const debugTrackColumns = Object.values(columns).filter( 83 (c) => !c.startsWith('__'), 84 ); 85 const addDebugTrack = m( 86 Popup, 87 { 88 trigger: m(Button, {label: 'Show debug track'}), 89 position: PopupPosition.Top, 90 }, 91 m(AddDebugTrackMenu, { 92 trace: this.state.trace, 93 dataSource: { 94 sqlSource: `SELECT ${debugTrackColumns.join(', ')} FROM (${selectStatement})`, 95 columns: debugTrackColumns, 96 }, 97 }), 98 ); 99 100 return m( 101 DetailsShell, 102 { 103 title: 'Table', 104 description: this.getDisplayName(), 105 buttons: [ 106 ...navigation, 107 addDebugTrack, 108 m( 109 PopupMenu2, 110 { 111 trigger: m(Button, { 112 icon: Icons.Menu, 113 }), 114 }, 115 m(MenuItem, { 116 label: 'Duplicate', 117 icon: 'tab_duplicate', 118 onclick: () => addSqlTableTabWithState(this.state.clone()), 119 }), 120 m(MenuItem, { 121 label: 'Copy SQL query', 122 icon: Icons.Copy, 123 onclick: () => 124 copyToClipboard(this.state.getNonPaginatedSQLQuery()), 125 }), 126 ), 127 ], 128 }, 129 m(SqlTable, { 130 state: this.state, 131 addColumnMenuItems: (column, columnAlias) => 132 m(AddChartMenuItem, { 133 chartConfig: createChartConfigFromSqlTableState( 134 column, 135 columnAlias, 136 this.state, 137 ), 138 chartOptions: [ChartOption.HISTOGRAM], 139 addChart: (chart) => addChartTab(chart), 140 }), 141 }), 142 ); 143 } 144 145 getTitle(): string { 146 const rowCount = this.state.getTotalRowCount(); 147 const rows = rowCount === undefined ? '' : ` (${rowCount})`; 148 return `Table ${this.getDisplayName()}${rows}`; 149 } 150 151 private getDisplayName(): string { 152 return this.state.config.displayName ?? this.state.config.name; 153 } 154 155 isLoading(): boolean { 156 return this.state.isLoading(); 157 } 158} 159