go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/test_verdict/legacy/test_results_tab/search_box.ts (about) 1 // Copyright 2021 The LUCI Authors. 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 import '@material/mwc-icon'; 16 import { MobxLitElement } from '@adobe/lit-mobx'; 17 import { css, html } from 'lit'; 18 import { customElement } from 'lit/decorators.js'; 19 import { styleMap } from 'lit/directives/style-map.js'; 20 import { computed, makeObservable, observable } from 'mobx'; 21 22 import '@/common/components/auto_complete'; 23 import '@/generic_libs/components/hotkey'; 24 import { SuggestionEntry } from '@/common/components/auto_complete'; 25 import { suggestTestResultSearchQuery } from '@/common/queries/tr_search_query'; 26 import { 27 consumeInvocationState, 28 InvocationStateInstance, 29 } from '@/common/store/invocation_state'; 30 import { consumer } from '@/generic_libs/tools/lit_context'; 31 32 /** 33 * An element that let the user search tests in the test results tab with DSL. 34 */ 35 @customElement('milo-trt-search-box') 36 @consumer 37 export class TestResultTabSearchBoxElement extends MobxLitElement { 38 @observable.ref 39 @consumeInvocationState() 40 invState!: InvocationStateInstance; 41 42 @computed private get lastSubQuery() { 43 return this.invState.searchText.split(' ').pop() || ''; 44 } 45 @computed private get queryPrefix() { 46 const searchTextPrefixLen = 47 this.invState.searchText.length - this.lastSubQuery.length; 48 return this.invState.searchText.slice(0, searchTextPrefixLen); 49 } 50 @computed private get suggestions() { 51 return suggestTestResultSearchQuery(this.invState.searchText); 52 } 53 54 constructor() { 55 super(); 56 makeObservable(this); 57 } 58 59 protected render() { 60 return html` 61 <milo-hotkey 62 .key=${'/'} 63 .handler=${() => { 64 // Set a tiny timeout to ensure '/' isn't recorded by the input box. 65 setTimeout( 66 () => this.shadowRoot?.getElementById('search-box')!.focus(), 67 ); 68 }} 69 > 70 <milo-auto-complete 71 id="search-box" 72 .highlight=${true} 73 .value=${this.invState.searchText} 74 .placeHolder=${'Press / to search test results...'} 75 .suggestions=${this.suggestions} 76 .onValueUpdate=${(newVal: string) => 77 this.invState.setSearchText(newVal)} 78 .onSuggestionSelected=${(suggestion: SuggestionEntry) => { 79 this.invState.setSearchText( 80 this.queryPrefix + suggestion.value! + ' ', 81 ); 82 }} 83 > 84 <mwc-icon 85 style=${styleMap({ 86 color: 87 this.invState.searchText === '' ? '' : 'var(--active-color)', 88 })} 89 slot="pre-icon" 90 > 91 search 92 </mwc-icon> 93 <mwc-icon 94 id="clear-search" 95 slot="post-icon" 96 title="Clear" 97 style=${styleMap({ 98 display: this.invState.searchText === '' ? 'none' : '', 99 })} 100 @click=${() => this.invState.setSearchText('')} 101 > 102 close 103 </mwc-icon> 104 </milo-auto-complete> 105 </milo-hotkey> 106 `; 107 } 108 109 static styles = css` 110 :host { 111 display: inline-block; 112 } 113 114 @keyframes highlight { 115 from { 116 background-color: var(--highlight-background-color); 117 } 118 to { 119 background-color: inherit; 120 } 121 } 122 123 mwc-icon { 124 margin: 2px; 125 } 126 127 #clear-search { 128 color: var(--delete-color); 129 cursor: pointer; 130 } 131 `; 132 }