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 {TrackEventDetailsPanel} from '../../public/details_panel';
17import {ColumnType} from '../../trace_processor/query_result';
18import {sqlValueToReadableString} from '../../trace_processor/sql_utils';
19import {DetailsShell} from '../../widgets/details_shell';
20import {GridLayout} from '../../widgets/grid_layout';
21import {Section} from '../../widgets/section';
22import {SqlRef} from '../../widgets/sql_ref';
23import {dictToTree, Tree, TreeNode} from '../../widgets/tree';
24import {Trace} from '../../public/trace';
25
26export interface ColumnConfig {
27  readonly displayName?: string;
28}
29
30export type Columns = {
31  readonly [columnName: string]: ColumnConfig;
32};
33
34// A details tab, which fetches slice-like object from a given SQL table by id
35// and renders it according to the provided config, specifying which columns
36// need to be rendered and how.
37export class GenericSliceDetailsTab implements TrackEventDetailsPanel {
38  private data?: {[key: string]: ColumnType};
39
40  constructor(
41    private readonly trace: Trace,
42    private readonly sqlTableName: string,
43    private readonly id: number,
44    private readonly title: string,
45    private readonly columns?: Columns,
46  ) {}
47
48  async load() {
49    const result = await this.trace.engine.query(
50      `select * from ${this.sqlTableName} where id = ${this.id}`,
51    );
52
53    this.data = result.firstRow({});
54  }
55
56  render() {
57    if (!this.data) {
58      return m('h2', 'Loading');
59    }
60
61    const args: {[key: string]: m.Child} = {};
62    if (this.columns !== undefined) {
63      for (const key of Object.keys(this.columns)) {
64        let argKey = key;
65        if (this.columns[key].displayName !== undefined) {
66          argKey = this.columns[key].displayName!;
67        }
68        args[argKey] = sqlValueToReadableString(this.data[key]);
69      }
70    } else {
71      for (const key of Object.keys(this.data)) {
72        args[key] = sqlValueToReadableString(this.data[key]);
73      }
74    }
75
76    const details = dictToTree(args);
77
78    return m(
79      DetailsShell,
80      {
81        title: this.title,
82      },
83      m(
84        GridLayout,
85        m(Section, {title: 'Details'}, m(Tree, details)),
86        m(
87          Section,
88          {title: 'Metadata'},
89          m(Tree, [
90            m(TreeNode, {
91              left: 'SQL ID',
92              right: m(SqlRef, {
93                table: this.sqlTableName,
94                id: this.id,
95              }),
96            }),
97          ]),
98        ),
99      ),
100    );
101  }
102}
103