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