xref: /aosp_15_r20/external/perfetto/ui/src/components/details/sql_table_tab.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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