1// Copyright (C) 2020 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 {Disposable} from '../base/disposable'; 18import {SimpleResizeObserver} from '../base/resize_observer'; 19import {undoCommonChatAppReplacements} from '../base/string_utils'; 20import {QueryResponse, runQuery} from '../common/queries'; 21import {raf} from '../core/raf_scheduler'; 22import {Engine} from '../trace_processor/engine'; 23import {Callout} from '../widgets/callout'; 24import {Editor} from '../widgets/editor'; 25 26import {globals} from './globals'; 27import {createPage} from './pages'; 28import {QueryHistoryComponent, queryHistoryStorage} from './query_history'; 29import {addQueryResultsTab} from './query_result_tab'; 30import {QueryTable} from './query_table'; 31 32interface QueryPageState { 33 enteredText: string; 34 executedQuery?: string; 35 queryResult?: QueryResponse; 36 heightPx: string; 37 generation: number; 38} 39 40const state: QueryPageState = { 41 enteredText: '', 42 heightPx: '100px', 43 generation: 0, 44}; 45 46function runManualQuery(query: string) { 47 state.executedQuery = query; 48 state.queryResult = undefined; 49 const engine = getEngine(); 50 if (engine) { 51 runQuery(undoCommonChatAppReplacements(query), engine).then( 52 (resp: QueryResponse) => { 53 addQueryResultsTab( 54 { 55 query: query, 56 title: 'Standalone Query', 57 prefetchedResponse: resp, 58 }, 59 'analyze_page_query', 60 ); 61 // We might have started to execute another query. Ignore it in that 62 // case. 63 if (state.executedQuery !== query) { 64 return; 65 } 66 state.queryResult = resp; 67 raf.scheduleFullRedraw(); 68 }, 69 ); 70 } 71 raf.scheduleDelayedFullRedraw(); 72} 73 74function getEngine(): Engine | undefined { 75 const engineId = globals.getCurrentEngine()?.id; 76 if (engineId === undefined) { 77 return undefined; 78 } 79 const engine = globals.engines.get(engineId)?.getProxy('QueryPage'); 80 return engine; 81} 82 83class QueryInput implements m.ClassComponent { 84 private resize?: Disposable; 85 86 oncreate({dom}: m.CVnodeDOM): void { 87 this.resize = new SimpleResizeObserver(dom, () => { 88 state.heightPx = (dom as HTMLElement).style.height; 89 }); 90 (dom as HTMLElement).style.height = state.heightPx; 91 } 92 93 onremove(): void { 94 if (this.resize) { 95 this.resize.dispose(); 96 this.resize = undefined; 97 } 98 } 99 100 view() { 101 return m(Editor, { 102 generation: state.generation, 103 initialText: state.enteredText, 104 105 onExecute: (text: string) => { 106 if (!text) { 107 return; 108 } 109 queryHistoryStorage.saveQuery(text); 110 runManualQuery(text); 111 }, 112 113 onUpdate: (text: string) => { 114 state.enteredText = text; 115 }, 116 }); 117 } 118} 119 120export const QueryPage = createPage({ 121 view() { 122 return m( 123 '.query-page', 124 m(Callout, 'Enter query and press Cmd/Ctrl + Enter'), 125 m(QueryInput), 126 state.executedQuery === undefined 127 ? null 128 : m(QueryTable, { 129 query: state.executedQuery, 130 resp: state.queryResult, 131 fillParent: false, 132 }), 133 m(QueryHistoryComponent, { 134 runQuery: runManualQuery, 135 setQuery: (q: string) => { 136 state.enteredText = q; 137 state.generation++; 138 raf.scheduleFullRedraw(); 139 }, 140 }), 141 ); 142 }, 143}); 144