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  }