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 {GenericSliceDetailsTabConfig} from '../core/generic_slice_details_types'; 18import {raf} from '../core/raf_scheduler'; 19import {ColumnType} from '../trace_processor/query_result'; 20import {sqlValueToReadableString} from '../trace_processor/sql_utils'; 21import {DetailsShell} from '../widgets/details_shell'; 22import {GridLayout} from '../widgets/grid_layout'; 23import {Section} from '../widgets/section'; 24import {SqlRef} from '../widgets/sql_ref'; 25import {dictToTree, Tree, TreeNode} from '../widgets/tree'; 26 27import {BottomTab, NewBottomTabArgs} from './bottom_tab'; 28 29export { 30 ColumnConfig, 31 Columns, 32 GenericSliceDetailsTabConfig, 33 GenericSliceDetailsTabConfigBase, 34} from '../core/generic_slice_details_types'; 35 36// A details tab, which fetches slice-like object from a given SQL table by id 37// and renders it according to the provided config, specifying which columns 38// need to be rendered and how. 39export class GenericSliceDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> { 40 static readonly kind = 'dev.perfetto.GenericSliceDetailsTab'; 41 42 data: {[key: string]: ColumnType} | undefined; 43 44 static create( 45 args: NewBottomTabArgs<GenericSliceDetailsTabConfig>, 46 ): GenericSliceDetailsTab { 47 return new GenericSliceDetailsTab(args); 48 } 49 50 constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) { 51 super(args); 52 53 this.engine 54 .query( 55 `select * from ${this.config.sqlTableName} where id = ${this.config.id}`, 56 ) 57 .then((queryResult) => { 58 this.data = queryResult.firstRow({}); 59 raf.scheduleFullRedraw(); 60 }); 61 } 62 63 viewTab() { 64 if (this.data === undefined) { 65 return m('h2', 'Loading'); 66 } 67 68 const args: {[key: string]: m.Child} = {}; 69 if (this.config.columns !== undefined) { 70 for (const key of Object.keys(this.config.columns)) { 71 let argKey = key; 72 if (this.config.columns[key].displayName !== undefined) { 73 argKey = this.config.columns[key].displayName!; 74 } 75 args[argKey] = sqlValueToReadableString(this.data[key]); 76 } 77 } else { 78 for (const key of Object.keys(this.data)) { 79 args[key] = sqlValueToReadableString(this.data[key]); 80 } 81 } 82 83 const details = dictToTree(args); 84 85 return m( 86 DetailsShell, 87 { 88 title: this.config.title, 89 }, 90 m( 91 GridLayout, 92 m(Section, {title: 'Details'}, m(Tree, details)), 93 m( 94 Section, 95 {title: 'Metadata'}, 96 m(Tree, [ 97 m(TreeNode, { 98 left: 'SQL ID', 99 right: m(SqlRef, { 100 table: this.config.sqlTableName, 101 id: this.config.id, 102 }), 103 }), 104 ]), 105 ), 106 ), 107 ); 108 } 109 110 getTitle(): string { 111 return this.config.title; 112 } 113 114 isLoading() { 115 return this.data === undefined; 116 } 117} 118