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