• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {NgTemplateOutlet} from '@angular/common';
18import {
19  Component,
20  ElementRef,
21  EventEmitter,
22  Inject,
23  Input,
24  Output,
25  ViewChild,
26} from '@angular/core';
27import {FormControl, Validators} from '@angular/forms';
28import {assertDefined} from 'common/assert_utils';
29import {Analytics} from 'logging/analytics';
30
31@Component({
32  selector: 'active-search',
33  template: `
34    <span class="header">
35      <span class="mat-body-2"> {{label}} </span>
36      <button
37        mat-button
38        class="query-button end-align-button clear-button"
39        color="primary"
40        (click)="clearQueryClick.emit()"
41        *ngIf="canClear">
42        <mat-icon> delete </mat-icon>
43        <span> Clear </span>
44      </button>
45    </span>
46    <mat-form-field appearance="outline" class="query-field padded-field">
47      <textarea matInput [formControl]="searchQueryControl" (keydown)="onTextAreaKeydown($event)" [readonly]="runningQuery"></textarea>
48      <mat-error *ngIf="searchQueryControl.invalid && searchQueryControl.value">Enter valid SQL query.</mat-error>
49    </mat-form-field>
50
51    <div class="query-actions">
52      <div *ngIf="runningQuery" class="running-query-message">
53        <mat-icon class="material-symbols-outlined"> timer </mat-icon>
54        <span class="mat-body-2 message-with-spinner">
55          <span>Calculating results </span>
56          <mat-spinner [diameter]="20"></mat-spinner>
57        </span>
58      </div>
59      <span *ngIf="lastQueryExecutionTime" class="query-execution-time mat-body-1">
60       Executed in {{lastQueryExecutionTime}}
61      </span>
62      <button
63        mat-flat-button
64        class="query-button search-button"
65        color="primary"
66        (click)="onSearchQueryClick()"
67        [disabled]="searchQueryDisabled()"> Run Search Query </button>
68    </div>
69    <div class="current-search" *ngIf="executedQuery">
70      <span class="query">
71        <span class="mat-body-2"> Last executed: </span>
72        <span class="mat-body-1"> {{executedQuery}} </span>
73      </span>
74      <ng-container
75        *ngIf="!lastTraceFailed"
76        [ngTemplateOutlet]="saveQueryField"
77        [ngTemplateOutletContext]="{query: executedQuery, control: saveQueryNameControl}"></ng-container>
78    </div>
79    <button
80      *ngIf="canAdd"
81      [disabled]="!executedQuery || lastTraceFailed"
82      mat-stroked-button
83      class="query-button add-button"
84      color="primary"
85      (click)="addQueryClick.emit()"> + Add Query </button>
86  `,
87  styles: [
88    `
89      .header {
90        justify-content: space-between;
91        display: flex;
92        align-items: center;
93      }
94      .query-field {
95        height: fit-content;
96      }
97      .query-field textarea {
98        height: 300px;
99      }
100      .query-button {
101        width: fit-content;
102        line-height: 24px;
103        padding: 0 10px;
104      }
105      .end-align-button {
106        align-self: end;
107      }
108      .query-actions {
109        display: flex;
110        flex-direction: row;
111        justify-content: end;
112        column-gap: 10px;
113        align-items: center;
114      }
115      .running-query-message {
116        display: flex;
117        flex-direction: row;
118        align-items: center;
119        color: #FF8A00;
120      }
121      .current-search {
122        padding: 10px 0px;
123      }
124      .current-search .query {
125        display: flex;
126        flex-direction: column;
127      }
128      .message-with-spinner {
129        display: flex;
130        flex-direction: row;
131        align-items: center;
132        justify-content: space-between;
133      }
134    `,
135  ],
136})
137export class ActiveSearchComponent {
138  @Input() canClear = false;
139  @Input() canAdd = false;
140  @Input() isSearchInitialized = false;
141  @Input() lastTraceFailed = false;
142  @Input() executedQuery: string | undefined;
143  @Input() saveQueryField: NgTemplateOutlet | undefined;
144  @Input() label: string | undefined;
145  @Input() lastQueryExecutionTime: string | undefined;
146  @Input() saveQueryNameControl: FormControl | undefined;
147  @Input() runningQuery = false;
148
149  @Output() clearQueryClick = new EventEmitter();
150  @Output() searchQueryClick = new EventEmitter<string>();
151  @Output() addQueryClick = new EventEmitter();
152
153  @ViewChild(HTMLTextAreaElement) textArea: HTMLTextAreaElement | undefined;
154
155  searchQueryControl = new FormControl('', Validators.required);
156
157  constructor(
158    @Inject(ElementRef) readonly elementRef: ElementRef<HTMLElement>,
159  ) {}
160
161  updateText(text: string) {
162    this.searchQueryControl.setValue(text);
163    this.textArea?.focus();
164  }
165
166  searchQueryDisabled(): boolean {
167    return (
168      this.searchQueryControl.invalid ||
169      this.runningQuery ||
170      !this.isSearchInitialized
171    );
172  }
173
174  onTextAreaKeydown(event: KeyboardEvent) {
175    event.stopPropagation();
176    if (
177      event.key === 'Enter' &&
178      !event.shiftKey &&
179      !this.searchQueryDisabled()
180    ) {
181      event.preventDefault();
182      this.onSearchQueryClick();
183    }
184  }
185
186  onSearchQueryClick() {
187    Analytics.TraceSearch.logQueryRequested('new');
188    this.searchQueryClick.emit(assertDefined(this.searchQueryControl.value));
189  }
190}
191