• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
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
16import { BaseElement, element } from '../../base-ui/BaseElement.js';
17import { queryCustomizeSelect, querySelectTraceStats } from '../database/SqlLite.js';
18import { LitTable } from '../../base-ui/table/lit-table.js';
19import '../../base-ui/table/lit-table.js';
20import { LitTableColumn } from '../../base-ui/table/lit-table-column.js';
21import { info } from '../../log/Log.js';
22import { LitProgressBar } from '../../base-ui/progress-bar/LitProgressBar.js';
23import { PageNation } from '../../base-ui/chart/pagenation/PageNation.js';
24import { PaginationBox } from '../../base-ui/chart/pagenation/pagination-box.js';
25import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil.js';
26
27@element('sp-query-sql')
28export class SpQuerySQL extends BaseElement {
29  private queryTableEl: LitTable | undefined;
30  private queryText: string | undefined;
31  private resultText: string | undefined;
32  private notSupportList: Array<string> | undefined;
33  private querySize: HTMLElement | undefined;
34  private keyList: Array<string> | undefined;
35  private selector: HTMLTextAreaElement | undefined;
36  private isSupportSql: boolean = true;
37  private querySelectTables: string = '';
38  private response: HTMLDivElement | undefined;
39  private statDataArray: any = [];
40  private sliceData: any = [];
41  private querySqlErrorText: string = '';
42  private progressLoad: LitProgressBar | undefined;
43  private pagination: PaginationBox | undefined;
44  private pageSize: number = 200000;
45  private maxPageSize: number = 500000;
46
47  initElements(): void {
48    this.progressLoad = this.shadowRoot?.querySelector('.load-query-sql') as LitProgressBar;
49    this.selector = this.shadowRoot?.querySelector('.sql-select') as HTMLTextAreaElement;
50    this.queryTableEl = new LitTable();
51    this.querySize = this.shadowRoot?.querySelector('.query_size') as HTMLElement;
52    this.response = this.shadowRoot?.querySelector('#dataResult') as HTMLDivElement;
53    this.pagination = this.shadowRoot?.querySelector('.pagination-box') as PaginationBox;
54    this.notSupportList?.push('insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create');
55    let htmlDivElement = this.queryTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement;
56    htmlDivElement.style.overflowX = 'scroll';
57
58    window.addEventListener('resize', () => {
59      this.freshTableHeadResizeStyle();
60    });
61
62    let copyButtonEl = this.shadowRoot?.querySelector('#copy-button') as HTMLButtonElement;
63    copyButtonEl.addEventListener('click', () => {
64      this.copyTableData();
65    });
66
67    let closeButtonEl = this.shadowRoot?.querySelector('#close-button') as HTMLButtonElement;
68    closeButtonEl.addEventListener('click', () => {
69      this.querySize!.textContent = 'Query result - 0 counts';
70      this.queryTableEl!.dataSource = [];
71      this.response!.innerHTML = '';
72    });
73  }
74
75  freshTableHeadResizeStyle() {
76    let th = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.th');
77    if (th) {
78      let td = th.querySelectorAll<HTMLDivElement>('.td');
79      let firstChild = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.body')!.firstElementChild;
80      if (firstChild) {
81        let bodyList = firstChild.querySelectorAll<HTMLDivElement>('.td');
82        for (let index = 0; index < bodyList.length; index++) {
83          td[index].style.width = bodyList[index].offsetWidth + 'px';
84          td[index].style.overflow = 'hidden';
85        }
86      }
87    }
88  }
89
90  async copyTableData() {
91    let copyResult = '';
92    for (let keyListKey of this.keyList!) {
93      copyResult += keyListKey + '\t';
94    }
95    copyResult += '\n';
96    let copyData = [];
97    if (this.statDataArray.length > this.maxPageSize) {
98      copyData = this.sliceData;
99    } else {
100      copyData = this.statDataArray;
101    }
102    for (const value of copyData) {
103      this.keyList?.forEach((key) => {
104        copyResult += value[key] + '\t';
105      });
106      copyResult += '\n';
107    }
108    await navigator.clipboard.writeText(copyResult);
109  }
110
111  selectEventListener = (event: KeyboardEvent) => {
112    let that = this;
113    if (event.ctrlKey && event.keyCode == 13) {
114      SpStatisticsHttpUtil.addOrdinaryVisitAction({
115        event: 'query',
116        action: 'query',
117      });
118      if (!this.isSupportSql) {
119        this.querySize!.textContent = this.querySqlErrorText;
120        this.queryTableEl!.dataSource = [];
121        this.response!.innerHTML = '';
122        return;
123      }
124      this.progressLoad!.loading = true;
125      let startData = new Date().getTime();
126      this.getInputSqlResult(this.selector!.value).then((resultList) => {
127        let dur = new Date().getTime() - startData;
128        this.statDataArray = [];
129        this.keyList = [];
130        for (let index = 0; index < resultList.length; index++) {
131          const dataResult = resultList[index];
132          let keys = Object.keys(dataResult);
133          // @ts-ignore
134          let values = Object.values(dataResult);
135          let jsonText = '{';
136          for (let keyIndex = 0; keyIndex < keys.length; keyIndex++) {
137            let key = keys[keyIndex];
138            if (this.keyList.indexOf(key) <= -1) {
139              this.keyList.push(key);
140            }
141            let value = values[keyIndex];
142            if (typeof value == 'string') {
143              value = value.replace(/</gi, '&lt;').replace(/>/gi, '&gt;');
144            }
145            jsonText += '"' + key + '"' + ': ' + '"' + value + '"';
146            if (keyIndex != keys.length - 1) {
147              jsonText += ',';
148            } else {
149              jsonText += '}';
150            }
151          }
152          this.statDataArray.push(JSON.parse(jsonText));
153        }
154
155        this.queryTableEl!.innerHTML = '';
156        this.queryText = this.selector!.value;
157        this.initDataElement();
158        this.response!.appendChild(this.queryTableEl!);
159        setTimeout(() => {
160          let total = this.statDataArray.length;
161          if (total > this.maxPageSize) {
162            that.pagination!.style.opacity = '1';
163            new PageNation(this.pagination, {
164              current: 1,
165              total: total,
166              pageSize: this.pageSize,
167              change(num: number) {
168                that.sliceData = that.statDataArray!.slice((num - 1) * that.pageSize, num * that.pageSize);
169                that.queryTableEl!.recycleDataSource = that.sliceData;
170              },
171            });
172          } else {
173            that.pagination!.style.opacity = '0';
174            this.queryTableEl!.recycleDataSource = this.statDataArray;
175          }
176
177          this.freshTableHeadResizeStyle();
178          new ResizeObserver(() => {
179            if (this.parentElement?.clientHeight != 0) {
180              this.queryTableEl!.style.height = '100%';
181              this.queryTableEl!.reMeauseHeight();
182            }
183          }).observe(this.parentElement!);
184          info('metric query Sql result Data size is: ', this.statDataArray!.length);
185          this.initData();
186          this.progressLoad!.loading = false;
187        }, 200);
188      });
189    }
190  };
191
192  reset() {
193    this.pagination!.style.opacity = '0';
194    this.response!.innerHTML = '';
195    this.keyList = [];
196    this.statDataArray = [];
197    this.selector!.value = '';
198    this.querySize!.textContent = 'Query result - ' + ' 0 counts';
199    this.resizeSqlHeight().then(() => {});
200  }
201
202  initDataTableStyle(styleTable: HTMLDivElement): void {
203    for (let index = 0; index < styleTable.children.length; index++) {
204      // @ts-ignore
205      styleTable.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)';
206    }
207  }
208
209  async initMetricData(): Promise<any> {
210    if (!this.selector || this.selector.value == null) {
211      return [];
212    }
213    if (this.queryText == '' || this.queryText == null) {
214      let statList = await querySelectTraceStats();
215      for (let index = 0; index < statList.length; index++) {
216        const statsResult = statList[index];
217        let indexArray = {
218          event_name: statsResult.event_name,
219          start_type: statsResult.stat_type,
220          count: statsResult.count,
221          serverity: statsResult.serverity,
222          source: statsResult.source,
223        };
224      }
225      if (this.querySize) {
226        this.querySize!.textContent = 'Query result - ' + statList.length + ' counts';
227      }
228      this.resultText = 'Query result - ' + statList.length + ' counts';
229    } else {
230      return this.statDataArray;
231    }
232  }
233
234  checkSupportSqlAbility(): boolean {
235    let noSupportChart = ['insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create'];
236    let result = noSupportChart.filter((item) => {
237      return this.selector!.value.indexOf(item) > -1;
238    });
239    if (result.length > 0) {
240      this.querySqlErrorText =
241        'Error: Statement contains a change action keyword,The change operation is not supported.';
242      this.isSupportSql = false;
243      return true;
244    } else {
245      return false;
246    }
247  }
248
249  checkSafetySelectSql(): boolean {
250    let split = this.selector?.value.trim().split(' ');
251    if (split) {
252      this.querySqlErrorText = 'Error: Incomplete query statement:  ' + this.selector!.value;
253      this.isSupportSql = false;
254      return !split[0].toLowerCase().startsWith('select');
255    }
256    return false;
257  }
258
259  getSelectSqlField(): string {
260    if (this.selector!.value.indexOf('from') < 0) {
261      return '';
262    }
263    let splitSql = this.selector?.value.split('from');
264    if (splitSql) {
265      if (splitSql[0].indexOf('*') > -1) {
266        return '*';
267      } else {
268        let fields = splitSql[0].split(',');
269        return fields[0];
270      }
271    }
272    return '';
273  }
274
275  getSelectSqlTableName(str: string): Array<string> {
276    if (this.selector!.value.indexOf(str) < 0) {
277      return [];
278    }
279    let tableNameList = [];
280    let splitSql = this.selector?.value.split(str);
281    if (splitSql) {
282      for (let index = 1; index < splitSql?.length; index++) {
283        let splitSqlItem = splitSql[index].trim();
284        let tableItem = splitSqlItem.split(' ');
285        let tableName = tableItem[0].trim();
286        tableNameList.push(tableName);
287        if (tableName.indexOf('(') >= 0) {
288          tableNameList.pop();
289        } else if (tableName.indexOf(')') >= 0) {
290          tableNameList.pop();
291          let unitTableName = tableName.split(')');
292          let tableNewName = unitTableName[0];
293          tableNameList.push(tableNewName);
294        }
295      }
296    }
297    return tableNameList;
298  }
299
300  initDataElement() {
301    if (this.keyList) {
302      info('Metric query Table Colum size is: ', this.keyList.length);
303      this.keyList.forEach((item) => {
304        let htmlElement = document.createElement('lit-table-column') as LitTableColumn;
305        htmlElement.setAttribute('title', item);
306        htmlElement.setAttribute('data-index', item);
307        htmlElement.setAttribute('key', item);
308        htmlElement.setAttribute('align', 'flex-start');
309        htmlElement.setAttribute('height', '32px');
310        this.queryTableEl!.appendChild(htmlElement);
311      });
312    }
313  }
314
315  connectedCallback() {
316    let selectQuery = this.shadowRoot?.querySelector('.query_select');
317    if (selectQuery) {
318      let querySql = selectQuery.textContent;
319    }
320    // Listen to the sql execution of the query
321    this.addEventListener('keydown', this.selectEventListener);
322    this.selector!.addEventListener('input', this.inputSqlListener);
323    this.selector!.addEventListener('change', this.inputSqlListener);
324    this.selector!.addEventListener('keydown', this.deleteSqlListener);
325  }
326
327  deleteSqlListener = (event: KeyboardEvent) => {
328    if (event.key == 'Backspace') {
329      this.resizeSqlHeight().then(() => {});
330    }
331  };
332
333  async resizeSqlHeight() {
334    let valueLength = this.selector?.value.split('\n').length;
335    let rowNumber = Number(valueLength) - 1;
336    let selectHeight = '3.2em';
337    if (rowNumber > 0) {
338      if (rowNumber <= 10) {
339        let allLength = 1.2 * rowNumber + 2;
340        selectHeight = allLength + 'em';
341      } else {
342        selectHeight = '14em';
343      }
344    }
345    // @ts-ignore
346    this.selector?.style.height = selectHeight;
347  }
348
349  inputSqlListener = async (event: Event) => {
350    this.resizeSqlHeight().then(() => {});
351    let startData = new Date().getTime();
352    if (this.selector!.value.trim() == '') {
353      this.querySqlErrorText = 'Please enter a query';
354      this.querySize!.textContent = this.querySqlErrorText;
355      return;
356    }
357    this.checkSafetySelectSql();
358    this.checkSupportSqlAbility();
359    if (this.selector!.value.length < 15) {
360      return;
361    }
362    this.querySelectTables = this.getSelectSqlTableName('from')
363      .concat(this.getSelectSqlTableName('join'))
364      .toLocaleString();
365    info('metric query sql table size is: ', this.querySelectTables.length);
366    this.isSupportSql = true;
367  };
368
369  async getInputSqlResult(sql: string): Promise<any> {
370    return await queryCustomizeSelect(sql);
371  }
372
373  disconnectedCallback() {
374    this.removeEventListener('keydown', this.selectEventListener);
375    this.selector!.removeEventListener('input', this.inputSqlListener);
376    this.selector!.removeEventListener('change', this.inputSqlListener);
377    this.selector!.removeEventListener('keydown', this.deleteSqlListener);
378  }
379
380  initData() {
381    if (this.statDataArray.length > 0) {
382      this.querySize!.textContent = 'Error: ' + this.selector?.value;
383    }
384    if (this.isSupportSql) {
385      let sqlField = this.keyList?.length == 0 ? '*' : this.keyList?.toLocaleString();
386      this.querySize!.textContent = 'Query result - ' + this.statDataArray.length + ' counts';
387    } else {
388      this.querySize!.textContent = this.querySqlErrorText;
389    }
390
391    let queryHeadStyle: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
392      'div.th'
393    ) as HTMLDivElement;
394    if (queryHeadStyle && queryHeadStyle.hasChildNodes()) {
395      for (let index = 0; index < queryHeadStyle.children.length; index++) {
396        // @ts-ignore
397        queryHeadStyle.children[index].style.gridArea = null;
398      }
399    }
400
401    this.queryTableEl!.style.height = '100%';
402  }
403
404  static get observedAttributes() {
405    return ['queryStr'];
406  }
407
408  attributeChangedCallback(name: string, oldValue: string, newValue: string) {
409    let queryDataSty: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
410      'div.tbody'
411    ) as HTMLDivElement;
412    if (queryDataSty && queryDataSty.hasChildNodes()) {
413      for (let index = 0; index < queryDataSty.children.length; index++) {
414        // @ts-ignore
415        queryDataSty.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)';
416      }
417    }
418  }
419
420  private _queryStr?: string;
421
422  get queryStr(): string {
423    return this.queryStr;
424  }
425
426  set queryStr(value: string) {
427    this._queryStr = value;
428  }
429
430  initHtml(): string {
431    return `
432        <style>
433        :host{
434            width: 100%;
435            height: 100%;
436            font-size: 16px;
437            background-color: var(--dark-background5,#F6F6F6);
438            margin: 0;
439            padding: 0;
440        }
441
442        .sql-select{
443            box-sizing: border-box;
444            width: 95%;
445            font-family: Helvetica,serif;
446            font-size: inherit;
447            color: var(--dark-color1,#212121);
448            text-align: left;
449            line-height: 1.2em;
450            font-weight: 400;
451            height: 3.2em;
452            margin-left: 10px;
453            resize: vertical;
454            border-width: 2px;
455        }
456
457        .query{
458            display: flex;
459            flex-direction: column;
460            background-color: var(--dark-background5,#F6F6F6);
461            position: absolute;
462            top: 0;
463            bottom: 0;
464            left: 0;
465            right: 0;
466        }
467
468        .query-message{
469            background-color: var(--dark-background3,#FFFFFF);
470            padding: 1% 2%;
471            margin: 2% 2.5% 0 2.5%;
472            border-radius: 16px;
473            width: 90%;
474        }
475
476        .request{
477            display: flex;
478            flex-direction: column;
479            position: relative;
480        }
481
482        .response{
483            flex-grow: 1;
484            margin-bottom: 1%;
485            display: flex;
486            flex-direction: column;
487            min-height: inherit;
488            max-height: 70vh;
489        }
490
491        #dataResult{
492            flex-grow: 1;
493            overflow-y: auto;
494            overflow-x: visible;
495            margin-bottom: 1%;
496            border-radius: 16px;
497        }
498
499        p{
500            display: table-cell;
501            padding: 7px 10px;
502            font-size:0.875em;
503            line-height: 20px;
504            font-weight: 400;
505            text-align: left;
506        }
507
508        #response-json{
509             margin-top: 20px;
510             background-color: var(--dark-background5,#F6F6F6);
511             margin-left: 10px;
512             flex-grow: 1;
513             scroll-y: visible;
514        }
515
516        .sql-select{
517            background-color: var(--dark-background5, #F6F6F6);
518        }
519        ::-webkit-scrollbar
520        {
521          width: 8px;
522          background-color: var(--dark-background3,#FFFFFF);
523        }
524        ::-webkit-scrollbar-thumb
525        {
526          border-radius: 6px;
527          background-color: var(--dark-background7,rgba(0,0,0,0.1));
528        }
529
530        .load-query-sql{
531            width: 95%;
532            bottom: 0;
533        }
534
535        #copy-button{
536           margin-right: 10%;
537           cursor:pointer;
538           opacity: 0.6;
539        }
540
541        #close-button{
542           margin-right: 5%;
543           cursor:pointer;
544           opacity: 0.6;
545        }
546
547        .button-option{
548           border-radius: 15px;
549           background-color: #0A59F7;
550           width: 120px;
551           height: 25px;
552           font-family: Helvetica-Bold;
553           color: var(--dark-background3,#FFFFFF);
554           text-align: center;
555           line-height: 20px;
556           font-weight: 400;
557           border:0 solid;
558        }
559        .pagination-box {
560            opacity: 0;
561        }
562
563        </style>
564        <div class="query">
565            <div class="query-message request">
566                <p class="query_select" style="color: #999999">Enter query and press cmd/ctrl + Enter</p>
567                <textarea class="sql-select"></textarea>
568                <lit-progress-bar class="load-query-sql"></lit-progress-bar>
569            </div>
570            <div class="query-message response">
571                   <div style="display: flex;justify-content: space-between">
572                       <p class="query_size" style="color: #999999">Query result - 0 counts</p>
573                       <div style="display: flex; align-items: center">
574                           <button id="copy-button" class="button-option">Copy as.tsv</button>
575                           <button id="close-button" class="button-option">Close</button>
576                        </div>
577                    </div>
578                   <div id="dataResult"></div>
579                   <pagination-box class="pagination-box"></pagination-box>
580            </div>
581        </div>
582        `;
583  }
584}
585