• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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';
16
17import {copyToClipboard} from '../../base/clipboard';
18import {Icons} from '../../base/semantic_icons';
19import {exists} from '../../base/utils';
20import {AddDebugTrackMenu} from '../debug_tracks/add_debug_track_menu';
21import {Button} from '../../widgets/button';
22import {DetailsShell} from '../../widgets/details_shell';
23import {Popup, PopupPosition} from '../../widgets/popup';
24
25import {Filter, SqlTableState} from './state';
26import {SqlTable} from './table';
27import {SqlTableDescription, tableDisplayName} from './table_description';
28import {Engine} from '../../public';
29import {globals} from '../globals';
30import {assertExists} from '../../base/logging';
31import {uuidv4} from '../../base/uuid';
32import {BottomTab, NewBottomTabArgs} from '../bottom_tab';
33import {addEphemeralTab} from '../../common/addEphemeralTab';
34
35interface SqlTableTabConfig {
36  table: SqlTableDescription;
37  displayName?: string;
38  filters?: Filter[];
39}
40
41export function addSqlTableTab(config: SqlTableTabConfig): void {
42  const queryResultsTab = new SqlTableTab({
43    config,
44    engine: getEngine(),
45    uuid: uuidv4(),
46  });
47
48  addEphemeralTab(queryResultsTab, 'sqlTable');
49}
50
51// TODO(stevegolton): Find a way to make this more elegant.
52function getEngine(): Engine {
53  const engConfig = globals.getCurrentEngine();
54  const engineId = assertExists(engConfig).id;
55  return assertExists(globals.engines.get(engineId)).getProxy('QueryResult');
56}
57
58export class SqlTableTab extends BottomTab<SqlTableTabConfig> {
59  static readonly kind = 'dev.perfetto.SqlTableTab';
60
61  private state: SqlTableState;
62
63  constructor(args: NewBottomTabArgs<SqlTableTabConfig>) {
64    super(args);
65
66    this.state = new SqlTableState(
67      this.engine,
68      this.config.table,
69      this.config.filters,
70    );
71  }
72
73  static create(args: NewBottomTabArgs<SqlTableTabConfig>): SqlTableTab {
74    return new SqlTableTab(args);
75  }
76
77  viewTab() {
78    const range = this.state.getDisplayedRange();
79    const rowCount = this.state.getTotalRowCount();
80    const navigation = [
81      exists(range) &&
82        exists(rowCount) &&
83        `Showing rows ${range.from}-${range.to} of ${rowCount}`,
84      m(Button, {
85        icon: Icons.GoBack,
86        disabled: !this.state.canGoBack(),
87        onclick: () => this.state.goBack(),
88      }),
89      m(Button, {
90        icon: Icons.GoForward,
91        disabled: !this.state.canGoForward(),
92        onclick: () => this.state.goForward(),
93      }),
94    ];
95    const {selectStatement, columns} = this.state.buildSqlSelectStatement();
96    const addDebugTrack = m(
97      Popup,
98      {
99        trigger: m(Button, {label: 'Show debug track'}),
100        position: PopupPosition.Top,
101      },
102      m(AddDebugTrackMenu, {
103        dataSource: {
104          sqlSource: selectStatement,
105          columns: columns,
106        },
107        engine: this.engine,
108      }),
109    );
110
111    return m(
112      DetailsShell,
113      {
114        title: 'Table',
115        description: this.getDisplayName(),
116        buttons: [
117          ...navigation,
118          addDebugTrack,
119          m(Button, {
120            label: 'Copy SQL query',
121            onclick: () =>
122              copyToClipboard(this.state.getNonPaginatedSQLQuery()),
123          }),
124        ],
125      },
126      m(SqlTable, {
127        state: this.state,
128      }),
129    );
130  }
131
132  getTitle(): string {
133    const rowCount = this.state.getTotalRowCount();
134    const rows = rowCount === undefined ? '' : ` (${rowCount})`;
135    return `Table ${this.getDisplayName()}${rows}`;
136  }
137
138  private getDisplayName(): string {
139    return this.config.displayName ?? tableDisplayName(this.config.table);
140  }
141
142  isLoading(): boolean {
143    return this.state.isLoading();
144  }
145}
146