• 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';
16import {copyToClipboard} from '../../base/clipboard';
17import {Icons} from '../../base/semantic_icons';
18import {exists} from '../../base/utils';
19import {Button, ButtonBar} from '../../widgets/button';
20import {DetailsShell} from '../../widgets/details_shell';
21import {Popup, PopupPosition} from '../../widgets/popup';
22import {AddDebugTrackMenu} from '../tracks/add_debug_track_menu';
23import {SqlTableState} from '../widgets/sql/table/state';
24import {SqlTable} from '../widgets/sql/table/table';
25import {SqlTableDescription} from '../widgets/sql/table/table_description';
26import {Trace} from '../../public/trace';
27import {MenuItem, PopupMenu} from '../../widgets/menu';
28import {addEphemeralTab} from './add_ephemeral_tab';
29import {Tab} from '../../public/tab';
30import {addChartTab} from '../widgets/charts/chart_tab';
31import {ChartType} from '../widgets/charts/chart';
32import {AddChartMenuItem} from '../widgets/charts/add_chart_menu';
33import {Filter, Filters, renderFilters} from '../widgets/sql/table/filters';
34import {PivotTableState} from '../widgets/sql/pivot_table/pivot_table_state';
35import {TableColumn} from '../widgets/sql/table/table_column';
36import {PivotTable} from '../widgets/sql/pivot_table/pivot_table';
37import {pivotId} from '../widgets/sql/pivot_table/ids';
38
39export interface AddSqlTableTabParams {
40  table: SqlTableDescription;
41  filters?: Filter[];
42  imports?: string[];
43}
44
45export function addLegacyTableTab(
46  trace: Trace,
47  config: AddSqlTableTabParams,
48): void {
49  addSqlTableTabWithState(
50    new SqlTableState(trace, config.table, {
51      filters: new Filters(config.filters),
52      imports: config.imports,
53    }),
54  );
55}
56
57function addSqlTableTabWithState(state: SqlTableState) {
58  addEphemeralTab('sqlTable', new LegacySqlTableTab(state));
59}
60
61class LegacySqlTableTab implements Tab {
62  constructor(private readonly state: SqlTableState) {
63    this.selected = state;
64  }
65
66  private selected: SqlTableState | PivotTableState;
67
68  private pivots: PivotTableState[] = [];
69
70  private getTableButtons() {
71    const range = this.state.getDisplayedRange();
72    const rowCount = this.state.getTotalRowCount();
73    const navigation = [
74      exists(range) &&
75        exists(rowCount) &&
76        `Showing rows ${range.from}-${range.to} of ${rowCount}`,
77      m(Button, {
78        icon: Icons.GoBack,
79        disabled: !this.state.canGoBack(),
80        onclick: () => this.state.goBack(),
81      }),
82      m(Button, {
83        icon: Icons.GoForward,
84        disabled: !this.state.canGoForward(),
85        onclick: () => this.state.goForward(),
86      }),
87    ];
88    const {selectStatement, columns} = this.state.getCurrentRequest();
89    const debugTrackColumns = Object.values(columns).filter(
90      (c) => !c.startsWith('__'),
91    );
92    const addDebugTrack = m(
93      Popup,
94      {
95        trigger: m(Button, {label: 'Show debug track'}),
96        position: PopupPosition.Top,
97      },
98      m(AddDebugTrackMenu, {
99        trace: this.state.trace,
100        dataSource: {
101          sqlSource: `SELECT ${debugTrackColumns.join(', ')} FROM (${selectStatement})`,
102          columns: debugTrackColumns,
103        },
104      }),
105    );
106    return [
107      ...navigation,
108      addDebugTrack,
109      m(
110        PopupMenu,
111        {
112          trigger: m(Button, {
113            icon: Icons.Menu,
114          }),
115        },
116        m(MenuItem, {
117          label: 'Duplicate',
118          icon: 'tab_duplicate',
119          onclick: () => addSqlTableTabWithState(this.state.clone()),
120        }),
121        m(MenuItem, {
122          label: 'Copy SQL query',
123          icon: Icons.Copy,
124          onclick: () => copyToClipboard(this.state.getNonPaginatedSQLQuery()),
125        }),
126      ),
127    ];
128  }
129
130  private tableMenuItems(column: TableColumn, alias: string) {
131    const chartAttrs = {
132      data: this.state.nonPaginatedData?.rows,
133      columns: [alias],
134    };
135
136    return [
137      m(AddChartMenuItem, {
138        chartOptions: [
139          {
140            chartType: ChartType.BAR_CHART,
141            ...chartAttrs,
142          },
143          {
144            chartType: ChartType.HISTOGRAM,
145            ...chartAttrs,
146          },
147        ],
148        addChart: (chart) => addChartTab(chart),
149      }),
150      m(MenuItem, {
151        label: 'Pivot',
152        onclick: () => {
153          const state = new PivotTableState({
154            pivots: [column],
155            table: this.state.config,
156            trace: this.state.trace,
157            filters: this.state.filters,
158          });
159          this.selected = state;
160          this.pivots.push(state);
161        },
162      }),
163    ];
164  }
165
166  render() {
167    return m(
168      DetailsShell,
169      {
170        title: 'Table',
171        description: this.getDisplayName(),
172        buttons: this.getTableButtons(),
173      },
174      m('div', renderFilters(this.state.filters)),
175      this.pivots.length > 0 &&
176        m(
177          ButtonBar,
178          m(Button, {
179            label: 'Table',
180            active: this.selected === this.state,
181            onclick: () => {
182              this.selected = this.state;
183            },
184          }),
185          this.pivots.map((pivot) =>
186            m(Button, {
187              label: `Pivot: ${pivot.getPivots().map(pivotId).join(', ')}`,
188              active: this.selected === pivot,
189              onclick: () => {
190                this.selected = pivot;
191              },
192            }),
193          ),
194        ),
195      this.selected === this.state &&
196        m(SqlTable, {
197          state: this.state,
198          addColumnMenuItems: this.tableMenuItems.bind(this),
199        }),
200      this.selected instanceof PivotTableState &&
201        m(PivotTable, {
202          state: this.selected,
203          extraRowButton: (node) =>
204            // Do not show any buttons for root as it doesn't have any filters anyway.
205            !node.isRoot() &&
206            m(
207              PopupMenu,
208              {
209                trigger: m(Button, {
210                  icon: Icons.GoTo,
211                }),
212              },
213              m(MenuItem, {
214                label: 'Add filters',
215                onclick: () => {
216                  this.state.filters.addFilters(node.getFilters());
217                },
218              }),
219              m(MenuItem, {
220                label: 'Open tab with filters',
221                onclick: () => {
222                  const newState = this.state.clone();
223                  newState.filters.addFilters(node.getFilters());
224                  addSqlTableTabWithState(newState);
225                },
226              }),
227            ),
228        }),
229    );
230  }
231
232  getTitle(): string {
233    const rowCount = this.state.getTotalRowCount();
234    const rows = rowCount === undefined ? '' : ` (${rowCount})`;
235    return `Table ${this.getDisplayName()}${rows}`;
236  }
237
238  private getDisplayName(): string {
239    return this.state.config.displayName ?? this.state.config.name;
240  }
241
242  isLoading(): boolean {
243    return this.state.isLoading();
244  }
245}
246