1// Copyright (C) 2024 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 SqlModulesPlugin from '../dev.perfetto.SqlModules'; 17 18import {PageWithTraceAttrs} from '../../public/page'; 19import {DataVisualiser} from './data_visualiser/data_visualiser'; 20import {QueryBuilder} from './query_builder/builder'; 21import {Button} from '../../widgets/button'; 22import {Intent} from '../../widgets/common'; 23import {NodeType, QueryNode} from './query_node'; 24import {MenuItem} from '../../widgets/menu'; 25import {Icons} from '../../base/semantic_icons'; 26import {VisViewSource} from './data_visualiser/view_source'; 27import {PopupMenu} from '../../widgets/menu'; 28import {createModal} from './query_builder/builder'; 29import { 30 StdlibTableAttrs, 31 StdlibTableNode, 32 StdlibTableSource, 33} from './query_builder/sources/stdlib_table'; 34import { 35 SlicesSource, 36 SlicesSourceAttrs, 37 SlicesSourceNode, 38} from './query_builder/sources/slices_source'; 39import { 40 SqlSource, 41 SqlSourceAttrs, 42 SqlSourceNode, 43} from './query_builder/sources/sql_source'; 44 45export interface ExplorePageState { 46 rootNodes: QueryNode[]; 47 selectedNode?: QueryNode; // Selected Query Node on which to perform actions 48 activeViewSource?: VisViewSource; // View Source of activeQueryNode 49 mode: ExplorePageModes; 50} 51 52export enum ExplorePageModes { 53 QUERY_BUILDER, 54 DATA_VISUALISER, 55} 56 57export const ExplorePageModeToLabel: Record<ExplorePageModes, string> = { 58 [ExplorePageModes.QUERY_BUILDER]: 'Query Builder', 59 [ExplorePageModes.DATA_VISUALISER]: 'Visualise Data', 60}; 61 62interface ExplorePageAttrs extends PageWithTraceAttrs { 63 readonly sqlModulesPlugin: SqlModulesPlugin; 64 readonly state: ExplorePageState; 65} 66 67export class ExplorePage implements m.ClassComponent<ExplorePageAttrs> { 68 renderNodeActionsMenuItems(node: QueryNode, state: ExplorePageState) { 69 // TODO: Split into operations on graph (like delete or duplicate) and 70 // operations on node (like edit). 71 return [ 72 m(MenuItem, { 73 label: 'Visualise Data', 74 icon: Icons.Chart, 75 onclick: () => { 76 state.selectedNode = node; 77 state.mode = ExplorePageModes.DATA_VISUALISER; 78 }, 79 }), 80 m(MenuItem, { 81 label: 'Edit', 82 onclick: async () => { 83 const attrsCopy = node.getState(); 84 switch (node.type) { 85 case NodeType.kStdlibTable: 86 createModal( 87 'Standard library table', 88 () => m(StdlibTableSource, attrsCopy as StdlibTableAttrs), 89 () => { 90 // TODO: Support editing non root nodes. 91 state.rootNodes[state.rootNodes.indexOf(node)] = 92 new StdlibTableNode(attrsCopy as StdlibTableAttrs); 93 state.selectedNode = node; 94 }, 95 ); 96 node = new StdlibTableNode(attrsCopy as StdlibTableAttrs); 97 break; 98 case NodeType.kSimpleSlices: 99 createModal( 100 'Slices', 101 () => m(SlicesSource, attrsCopy as SlicesSourceAttrs), 102 () => { 103 // TODO: Support editing non root nodes. 104 state.rootNodes[state.rootNodes.indexOf(node)] = 105 new SlicesSourceNode(attrsCopy as SlicesSourceAttrs); 106 state.selectedNode = node; 107 }, 108 ); 109 break; 110 case NodeType.kSqlSource: 111 createModal( 112 'SQL', 113 () => m(SqlSource, attrsCopy as SqlSourceAttrs), 114 () => { 115 // TODO: Support editing non root nodes. 116 state.rootNodes[state.rootNodes.indexOf(node)] = 117 new SqlSourceNode(attrsCopy as SqlSourceAttrs); 118 state.selectedNode = node; 119 }, 120 ); 121 } 122 }, 123 }), 124 m(MenuItem, { 125 label: 'Duplicate', 126 onclick: async () => { 127 state.rootNodes.push(cloneQueryNode(node)); 128 }, 129 }), 130 m(MenuItem, { 131 label: 'Delete', 132 onclick: async () => { 133 const idx = state.rootNodes.indexOf(node); 134 if (idx !== -1) { 135 state.rootNodes.splice(idx, 1); 136 state.selectedNode = node; 137 } 138 }, 139 }), 140 ]; 141 } 142 143 view({attrs}: m.CVnode<ExplorePageAttrs>) { 144 const {trace, state} = attrs; 145 146 return m( 147 '.page.explore-page', 148 m( 149 '.explore-page__header', 150 m('h1', `${ExplorePageModeToLabel[state.mode]}`), 151 m('span', {style: {flexGrow: 1}}), 152 state.mode === ExplorePageModes.QUERY_BUILDER 153 ? m( 154 '', 155 m( 156 PopupMenu, 157 { 158 trigger: m(Button, { 159 label: 'Add new node', 160 icon: Icons.Add, 161 intent: Intent.Primary, 162 }), 163 }, 164 addSourcePopupMenu(attrs), 165 ), 166 m(Button, { 167 label: 'Clear All Query Nodes', 168 intent: Intent.Primary, 169 onclick: () => { 170 state.rootNodes = []; 171 state.selectedNode = undefined; 172 }, 173 style: {marginLeft: '10px'}, 174 }), 175 ) 176 : m(Button, { 177 label: 'Back to Query Builder', 178 intent: Intent.Primary, 179 onclick: () => { 180 state.mode = ExplorePageModes.QUERY_BUILDER; 181 }, 182 }), 183 ), 184 185 state.mode === ExplorePageModes.QUERY_BUILDER && 186 m(QueryBuilder, { 187 trace, 188 sqlModules: attrs.sqlModulesPlugin.getSqlModules(), 189 onRootNodeCreated(arg) { 190 state.rootNodes.push(arg); 191 state.selectedNode = arg; 192 }, 193 onNodeSelected(arg) { 194 state.selectedNode = arg; 195 }, 196 renderNodeActionsMenuItems: (node: QueryNode) => 197 this.renderNodeActionsMenuItems(node, state), 198 rootNodes: state.rootNodes, 199 selectedNode: state.selectedNode, 200 addSourcePopupMenu: () => addSourcePopupMenu(attrs), 201 }), 202 state.mode === ExplorePageModes.DATA_VISUALISER && 203 state.rootNodes.length !== 0 && 204 m(DataVisualiser, { 205 trace, 206 state, 207 }), 208 ); 209 } 210} 211 212function addSourcePopupMenu(attrs: ExplorePageAttrs): m.Children { 213 const {trace, state} = attrs; 214 const sqlModules = attrs.sqlModulesPlugin.getSqlModules(); 215 return [ 216 m(MenuItem, { 217 label: 'Standard library table', 218 onclick: async () => { 219 const stdlibTableAttrs: StdlibTableAttrs = { 220 filters: [], 221 sourceCols: [], 222 groupByColumns: [], 223 aggregations: [], 224 trace, 225 sqlModules, 226 modal: () => 227 createModal( 228 'Standard library table', 229 () => m(StdlibTableSource, stdlibTableAttrs), 230 () => { 231 const newNode = new StdlibTableNode(stdlibTableAttrs); 232 state.rootNodes.push(newNode); 233 state.selectedNode = newNode; 234 }, 235 ), 236 }; 237 // Adding trivial modal to open the table selection. 238 createModal( 239 'Standard library table', 240 () => m(StdlibTableSource, stdlibTableAttrs), 241 () => {}, 242 ); 243 }, 244 }), 245 m(MenuItem, { 246 label: 'Custom slices', 247 onclick: () => { 248 const newSimpleSlicesAttrs: SlicesSourceAttrs = { 249 sourceCols: [], 250 filters: [], 251 groupByColumns: [], 252 aggregations: [], 253 }; 254 createModal( 255 'Slices', 256 () => m(SlicesSource, newSimpleSlicesAttrs), 257 () => { 258 const newNode = new SlicesSourceNode(newSimpleSlicesAttrs); 259 state.rootNodes.push(newNode); 260 state.selectedNode = newNode; 261 }, 262 ); 263 }, 264 }), 265 m(MenuItem, { 266 label: 'Custom SQL', 267 onclick: () => { 268 const newSqlSourceAttrs: SqlSourceAttrs = { 269 sourceCols: [], 270 filters: [], 271 groupByColumns: [], 272 aggregations: [], 273 }; 274 createModal( 275 'SQL', 276 () => m(SqlSource, newSqlSourceAttrs), 277 () => { 278 const newNode = new SqlSourceNode(newSqlSourceAttrs); 279 state.rootNodes.push(newNode); 280 state.selectedNode = newNode; 281 }, 282 ); 283 }, 284 }), 285 ]; 286} 287 288function cloneQueryNode(node: QueryNode): QueryNode { 289 const attrsCopy = node.getState(); 290 switch (node.type) { 291 case NodeType.kStdlibTable: 292 return new StdlibTableNode(attrsCopy as StdlibTableAttrs); 293 case NodeType.kSimpleSlices: 294 return new SlicesSourceNode(attrsCopy as SlicesSourceAttrs); 295 case NodeType.kSqlSource: 296 return new SqlSourceNode(attrsCopy as SqlSourceAttrs); 297 } 298} 299