1<!-- Copyright (C) 2017 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--> 15<template> 16 <md-content class="searchbar"> 17 18 <div class="tabs"> 19 <div class="search-timestamp" v-if="isTimestampSearch()"> 20 <md-field md-inline class="search-input"> 21 <label>Enter timestamp</label> 22 <md-input 23 v-model="searchInput" 24 v-on:focus="updateInputMode(true)" 25 v-on:blur="updateInputMode(false)" 26 @keyup.enter.native="updateSearchForTimestamp" 27 /> 28 </md-field> 29 <md-button 30 class="md-dense md-primary search-timestamp-button" 31 @click="updateSearchForTimestamp" 32 > 33 Go to timestamp 34 </md-button> 35 </div> 36 37 <div class="dropdown-content" v-if="isTransitionSearch()"> 38 <table> 39 <tr class="header"> 40 <th style="width: 10%">Global Start</th> 41 <th style="width: 10%">Global End</th> 42 <th style="width: 80%">Transition</th> 43 </tr> 44 45 <tr v-for="item in filteredTransitionsAndErrors" :key="item.id"> 46 <td 47 v-if="isTransition(item)" 48 class="inline-time" 49 @click=" 50 setCurrentTimestamp(transitionStart(transitionTags(item.id))) 51 " 52 > 53 <span>{{ transitionTags(item.id)[0].desc }}</span> 54 </td> 55 <td 56 v-if="isTransition(item)" 57 class="inline-time" 58 @click="setCurrentTimestamp(transitionEnd(transitionTags(item.id)))" 59 > 60 <span>{{ transitionTags(item.id)[1].desc }}</span> 61 </td> 62 <td 63 v-if="isTransition(item)" 64 class="inline-transition" 65 :style="{color: transitionTextColor(item.transition)}" 66 @click="setCurrentTimestamp(transitionStart(transitionTags(item.id)))" 67 > 68 {{ transitionDesc(item.transition) }} 69 </td> 70 </tr> 71 </table> 72 <md-field md-inline class="search-input"> 73 <label> 74 Filter by transition name. Click to navigate to closest 75 timestamp in active timeline. 76 </label> 77 <md-input 78 v-model="searchInput" 79 v-on:focus="updateInputMode(true)" 80 v-on:blur="updateInputMode(false)" 81 /> 82 </md-field> 83 </div> 84 85 <div class="dropdown-content" v-if="isErrorSearch()"> 86 <table> 87 <tr class="header"> 88 <th style="width: 10%">Timestamp</th> 89 <th style="width: 90%">Error Message</th> 90 </tr> 91 92 <tr v-for="item in filteredTransitionsAndErrors" :key="item.id"> 93 <td 94 v-if="!isTransition(item)" 95 class="inline-time" 96 @click="setCurrentTimestamp(item.timestamp)" 97 > 98 {{ errorDesc(item.timestamp) }} 99 </td> 100 <td 101 v-if="!isTransition(item)" 102 class="inline-error" 103 @click="setCurrentTimestamp(item.timestamp)" 104 > 105 {{item.message}} 106 </td> 107 </tr> 108 </table> 109 <md-field md-inline class="search-input"> 110 <label> 111 Filter by error message. Click to navigate to closest 112 timestamp in active timeline. 113 </label> 114 <md-input 115 v-model="searchInput" 116 v-on:focus="updateInputMode(true)" 117 v-on:blur="updateInputMode(false)" 118 /> 119 </md-field> 120 </div> 121 </div> 122 123 <div class="tab-container" v-if="searchTypes.length > 0"> 124 Search mode: 125 <md-button 126 v-for="searchType in searchTypes" 127 :key="searchType" 128 @click="setSearchType(searchType)" 129 :class="tabClass(searchType)" 130 > 131 {{ searchType }} 132 </md-button> 133 </div> 134 </md-content> 135</template> 136<script> 137import { transitionMap, SEARCH_TYPE } from "./utils/consts"; 138import { nanos_to_string, getClosestTimestamp } from "./transform"; 139 140export default { 141 name: "searchbar", 142 props: ["store", "presentTags", "timeline", "presentErrors", "searchTypes"], 143 data() { 144 return { 145 searchType: SEARCH_TYPE.TIMESTAMP, 146 searchInput: "", 147 }; 148 }, 149 methods: { 150 /** Set search type depending on tab selected */ 151 setSearchType(searchType) { 152 this.searchType = searchType; 153 }, 154 /** Set tab class to determine color highlight for active tab */ 155 tabClass(searchType) { 156 var isActive = (this.searchType === searchType) ? 'active' : 'inactive'; 157 return ['tab', isActive]; 158 }, 159 160 /** Filter all the tags present in the trace by the searchbar input */ 161 filteredTags() { 162 var tags = []; 163 var filter = this.searchInput.toUpperCase(); 164 this.presentTags.forEach((tag) => { 165 const tagTransition = tag.transition.toUpperCase(); 166 if (tagTransition.includes(filter)) tags.push(tag); 167 }); 168 return tags; 169 }, 170 /** Add filtered errors to filtered tags to integrate both into table*/ 171 filteredTagsAndErrors() { 172 var tagsAndErrors = [...this.filteredTags()]; 173 var filter = this.searchInput.toUpperCase(); 174 this.presentErrors.forEach((error) => { 175 const errorMessage = error.message.toUpperCase(); 176 if (errorMessage.includes(filter)) tagsAndErrors.push(error); 177 }); 178 // sort into chronological order 179 tagsAndErrors.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)); 180 181 return tagsAndErrors; 182 }, 183 /** Each transition has two tags present 184 * Isolate the tags for the desire transition 185 * Add a desc to display the timestamps as strings 186 */ 187 transitionTags(id) { 188 var tags = this.filteredTags().filter((tag) => tag.id === id); 189 tags.forEach((tag) => { 190 tag.desc = nanos_to_string(tag.timestamp); 191 }); 192 return tags; 193 }, 194 195 /** Find the start as minimum timestamp in transition tags */ 196 transitionStart(tags) { 197 var times = tags.map((tag) => tag.timestamp); 198 return times[0]; 199 }, 200 /** Find the end as maximum timestamp in transition tags */ 201 transitionEnd(tags) { 202 var times = tags.map((tag) => tag.timestamp); 203 return times[times.length - 1]; 204 }, 205 /** 206 * Upon selecting a start/end tag in the dropdown; 207 * navigates to that timestamp in the timeline 208 */ 209 setCurrentTimestamp(timestamp) { 210 this.$store.dispatch("updateTimelineTime", timestamp); 211 }, 212 213 /** Colour codes text of transition in dropdown */ 214 transitionTextColor(transition) { 215 return transitionMap.get(transition).color; 216 }, 217 /** Displays transition description rather than variable name */ 218 transitionDesc(transition) { 219 return transitionMap.get(transition).desc; 220 }, 221 /** Add a desc to display the error timestamps as strings */ 222 errorDesc(timestamp) { 223 return nanos_to_string(timestamp); 224 }, 225 226 /** Navigates to closest timestamp in timeline to search input*/ 227 updateSearchForTimestamp() { 228 const closestTimestamp = getClosestTimestamp(this.searchInput, this.timeline); 229 this.setCurrentTimestamp(closestTimestamp); 230 }, 231 232 isTransitionSearch() { 233 return this.searchType === SEARCH_TYPE.TRANSITIONS; 234 }, 235 isErrorSearch() { 236 return this.searchType === SEARCH_TYPE.ERRORS; 237 }, 238 isTimestampSearch() { 239 return this.searchType === SEARCH_TYPE.TIMESTAMP; 240 }, 241 isTransition(item) { 242 return item.stacktrace === undefined; 243 }, 244 245 /** determines whether left/right arrow keys should move cursor in input field */ 246 updateInputMode(isInputMode) { 247 this.store.isInputMode = isInputMode; 248 }, 249 }, 250 computed: { 251 filteredTransitionsAndErrors() { 252 var ids = []; 253 return this.filteredTagsAndErrors().filter((item) => { 254 if (this.isTransition(item) && !ids.includes(item.id)) { 255 item.transitionStart = true; 256 ids.push(item.id); 257 } 258 return !this.isTransition(item) || this.isTransition(item) && item.transitionStart; 259 }); 260 }, 261 }, 262 destroyed() { 263 this.updateInputMode(false); 264 }, 265}; 266</script> 267<style scoped> 268.searchbar { 269 background-color: rgb(250, 243, 233) !important; 270 top: 0; 271 left: 0; 272 right: 0; 273 width: 100%; 274 margin-left: auto; 275 margin-right: auto; 276 bottom: 1px; 277} 278 279.tabs { 280 padding-top: 1rem; 281} 282 283.tab-container { 284 padding-left: 20px; 285 display: flex; 286 align-items: center; 287} 288 289.tab.active { 290 background-color: rgb(236, 222, 202); 291} 292 293.tab.inactive { 294 background-color: rgb(250, 243, 233); 295} 296 297.search-timestamp { 298 padding: 5px 20px 0px 20px; 299 display: inline-flex; 300 width: 100%; 301} 302 303.search-timestamp > .search-input { 304 margin-top: -5px; 305 max-width: 200px; 306} 307 308.search-timestamp-button { 309 left: 0; 310 padding: 0 15px; 311} 312 313.dropdown-content { 314 padding: 5px 20px 0px 20px; 315 display: block; 316} 317 318.dropdown-content table { 319 overflow-y: scroll; 320 max-height: 150px; 321 display: block; 322} 323 324.dropdown-content table td { 325 padding: 5px; 326} 327 328.dropdown-content table th { 329 text-align: left; 330 padding: 5px; 331} 332 333.inline-time:hover { 334 background: rgb(216, 250, 218); 335 cursor: pointer; 336} 337 338.inline-transition { 339 font-weight: bold; 340} 341 342.inline-transition:hover { 343 background: rgb(216, 250, 218); 344 cursor: pointer; 345} 346 347.inline-error { 348 font-weight: bold; 349 color: red; 350} 351 352.inline-error:hover { 353 background: rgb(216, 250, 218); 354 cursor: pointer; 355} 356</style> 357