From c6a3625ae90ac490d71f2f1d75edc8aff0a0da9b Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 23 May 2025 11:21:22 +0100 Subject: [PATCH 1/3] refactor: change find to async --- .../modules/components/analysis-view/AnalysisView.ts | 10 ++++++---- .../modules/components/calltree-view/CalltreeView.ts | 8 +++++--- .../modules/components/calltree-view/module/Find.ts | 3 ++- log-viewer/modules/components/database-view/DMLView.ts | 4 ++-- .../modules/components/database-view/SOQLView.ts | 6 +++--- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/log-viewer/modules/components/analysis-view/AnalysisView.ts b/log-viewer/modules/components/analysis-view/AnalysisView.ts index 7607fe42..90208a33 100644 --- a/log-viewer/modules/components/analysis-view/AnalysisView.ts +++ b/log-viewer/modules/components/analysis-view/AnalysisView.ts @@ -185,7 +185,9 @@ export class AnalysisView extends LitElement { return (this.tableContainer ??= this.renderRoot?.querySelector('#analysis-table')); } - _findEvt = ((event: FindEvt) => this._find(event)) as EventListener; + _findEvt = ((event: FindEvt) => { + this._find(event); + }) as EventListener; _groupBy(event: Event) { const target = event.target as HTMLInputElement; @@ -208,7 +210,7 @@ export class AnalysisView extends LitElement { }); } - _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { + async _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { const isTableVisible = !!this.analysisTable?.element?.clientHeight; if (!isTableVisible && !this.totalMatches) { return; @@ -226,8 +228,8 @@ export class AnalysisView extends LitElement { } if (newSearch || clearHighlights) { this.blockClearHighlights = true; - //@ts-expect-error This is a custom function added in by Find custom module - const result = this.analysisTable.find(this.findArgs); + // @ts-expect-error This is a custom function added in by Find custom module + const result = await this.analysisTable?.find(this.findArgs); this.blockClearHighlights = false; this.totalMatches = result.totalMatches; this.findMap = result.matchIndexes; diff --git a/log-viewer/modules/components/calltree-view/CalltreeView.ts b/log-viewer/modules/components/calltree-view/CalltreeView.ts index 85e210c3..8cdde481 100644 --- a/log-viewer/modules/components/calltree-view/CalltreeView.ts +++ b/log-viewer/modules/components/calltree-view/CalltreeView.ts @@ -218,7 +218,9 @@ export class CalltreeView extends LitElement { `; } - _findEvt = ((event: FindEvt) => this._find(event)) as EventListener; + _findEvt = ((event: FindEvt) => { + this._find(event); + }) as EventListener; _getAllTypes(data: LogLine[]): string[] { const flattened = this._flatten(data); @@ -345,7 +347,7 @@ export class CalltreeView extends LitElement { this.calltreeTable.goToRow(treeRow, { scrollIfVisible: true, focusRow: true }); } - _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { + async _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { const isTableVisible = !!this.calltreeTable?.element?.clientHeight; if (!isTableVisible && !this.totalMatches) { return; @@ -365,7 +367,7 @@ export class CalltreeView extends LitElement { if (newSearch || clearHighlights) { this.blockClearHighlights = true; //@ts-expect-error This is a custom function added in by Find custom module - const result = this.calltreeTable.find(this.findArgs); + const result = await this.calltreeTable.find(this.findArgs); this.blockClearHighlights = false; this.totalMatches = result.totalMatches; this.findMap = result.matchIndexes; diff --git a/log-viewer/modules/components/calltree-view/module/Find.ts b/log-viewer/modules/components/calltree-view/module/Find.ts index bb7e72b2..7582bd50 100644 --- a/log-viewer/modules/components/calltree-view/module/Find.ts +++ b/log-viewer/modules/components/calltree-view/module/Find.ts @@ -22,7 +22,7 @@ export class Find extends Module { initialize() {} - _find(findArgs: FindArgs) { + async _find(findArgs: FindArgs) { const result: { totalMatches: number; matchIndexes: { [key: number]: RowComponent } } = { totalMatches: 0, matchIndexes: {}, @@ -30,6 +30,7 @@ export class Find extends Module { this._clearMatches(); + // We only do this when groups exist to get row order const flattenFromGrps = (row: GroupComponent): RowComponent[] => { const mergedArray: RowComponent[] = []; Array.prototype.push.apply(mergedArray, row.getRows()); diff --git a/log-viewer/modules/components/database-view/DMLView.ts b/log-viewer/modules/components/database-view/DMLView.ts index a994ab8f..b3e9d974 100644 --- a/log-viewer/modules/components/database-view/DMLView.ts +++ b/log-viewer/modules/components/database-view/DMLView.ts @@ -219,7 +219,7 @@ export class DMLView extends LitElement { this.oldIndex = highlightIndex; } - _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { + async _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { const isTableVisible = !!this.dmlTable?.element?.clientHeight; if (!isTableVisible && !this.totalMatches) { return; @@ -242,7 +242,7 @@ export class DMLView extends LitElement { if (newSearch || clearHighlights) { this.blockClearHighlights = true; //@ts-expect-error This is a custom function added in by Find custom module - const result = this.dmlTable.find(this.findArgs); + const result = await this.dmlTable.find(this.findArgs); this.blockClearHighlights = false; this.totalMatches = result.totalMatches; this.findMap = result.matchIndexes; diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index fa3affc0..5e9fdeda 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -252,7 +252,7 @@ export class SOQLView extends LitElement { this.oldIndex = highlightIndex; } - _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { + async _find(e: CustomEvent<{ text: string; count: number; options: { matchCase: boolean } }>) { const isTableVisible = !!this.soqlTable?.element?.clientHeight; if (!isTableVisible && !this.totalMatches) { return; @@ -271,9 +271,9 @@ export class SOQLView extends LitElement { if (newSearch || clearHighlights) { this.blockClearHighlights = true; //@ts-expect-error This is a custom function added in by Find custom module - const result = this.soqlTable.find(this.findArgs); + const result = await this.soqlTable.find(this.findArgs); this.blockClearHighlights = false; - this.totalMatches = 0; + this.totalMatches = result.totalMatches; this.findMap = result.matchIndexes; if (!clearHighlights) { From 7eeeb8a4e2444e70b591643fde0502fcb845ec6f Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 23 May 2025 11:22:34 +0100 Subject: [PATCH 2/3] refactor: split clear highlight + search logic --- .../components/calltree-view/module/Find.ts | 83 +++++++++---------- .../components/database-view/DMLView.ts | 4 +- .../components/database-view/SOQLView.ts | 4 +- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/log-viewer/modules/components/calltree-view/module/Find.ts b/log-viewer/modules/components/calltree-view/module/Find.ts index 7582bd50..d059ad01 100644 --- a/log-viewer/modules/components/calltree-view/module/Find.ts +++ b/log-viewer/modules/components/calltree-view/module/Find.ts @@ -1,13 +1,7 @@ /* * Copyright (c) 2024 Certinia Inc. All rights reserved. */ -import { - Module, - type CellComponent, - type GroupComponent, - type RowComponent, - type Tabulator, -} from 'tabulator-tables'; +import { Module, type GroupComponent, type RowComponent, type Tabulator } from 'tabulator-tables'; export class Find extends Module { static moduleName = 'FindModule'; @@ -53,51 +47,50 @@ export class Find extends Module { const regex = new RegExp(searchString, `g${findArgs.options.matchCase ? '' : 'i'}`); tbl.blockRedraw(); - let totalMatches = 0; - const rowsToReformat = []; - const len = flattenedRows.length; - for (let i = 0; i < len; i++) { - const row = flattenedRows[i]; - if (!row) { - continue; - } - - let clearHighlight = false; + for (const row of flattenedRows) { const data = row.getData(); - if (data.highlightIndexes?.length) { - clearHighlight = true; - rowsToReformat.push(row); + if (data.highlightIndexes?.length > 0) { + data.highlightIndexes.length = 0; + row.reformat(); + } else if (!data.highlightIndexes) { + data.highlightIndexes = []; } + } + tbl.restoreRedraw(); - data.highlightIndexes = []; - - if (!searchString) { - continue; - } - let reformat = false; - - row.getCells().forEach((cell: CellComponent) => { - const elem = cell.getElement(); - const matchCount = this._countMatches(elem, findArgs, regex); - if (matchCount) { - const kLen = matchCount; - for (let k = 0; k < kLen; k++) { - totalMatches++; - data.highlightIndexes.push(totalMatches); - result.matchIndexes[totalMatches] = row; - } - reformat = true; + await new Promise((resolve) => requestAnimationFrame(resolve)); + let totalMatches = 0; + if (searchString) { + const rowsToReformat = new Set(); + const len = flattenedRows.length; + for (let i = 0; i < len; i++) { + const row = flattenedRows[i]; + if (!row) { + continue; } - }); - if (reformat && !clearHighlight) { - rowsToReformat.push(row); + const data = row.getData(); + data.highlightIndexes = []; + row.getCells().forEach((cell) => { + const elem = cell.getElement(); + const matchCount = this._countMatches(elem, findArgs, regex); + if (matchCount) { + const kLen = matchCount; + for (let k = 0; k < kLen; k++) { + totalMatches++; + data.highlightIndexes.push(totalMatches); + result.matchIndexes[totalMatches] = row; + } + rowsToReformat.add(row); + } + }); } + tbl.blockRedraw(); + rowsToReformat.forEach((row) => { + row?.reformat(); + }); + tbl.restoreRedraw(); } - rowsToReformat.forEach((row) => { - row?.reformat(); - }); - tbl.restoreRedraw(); result.totalMatches = totalMatches; return result; diff --git a/log-viewer/modules/components/database-view/DMLView.ts b/log-viewer/modules/components/database-view/DMLView.ts index b3e9d974..aa882851 100644 --- a/log-viewer/modules/components/database-view/DMLView.ts +++ b/log-viewer/modules/components/database-view/DMLView.ts @@ -159,7 +159,9 @@ export class DMLView extends LitElement { this.dmlTable?.download('csv', 'dml.csv', { bom: true, delimiter: ',' }); } - _findEvt = ((event: FindEvt) => this._find(event)) as EventListener; + _findEvt = ((event: FindEvt) => { + this._find(event); + }) as EventListener; _dmlGroupBy(event: Event) { const target = event.target as HTMLInputElement; diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index 5e9fdeda..3711b749 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -191,7 +191,9 @@ export class SOQLView extends LitElement { this.soqlTable?.download('csv', 'soql.csv', { bom: true, delimiter: ',' }); } - _findEvt = ((event: FindEvt) => this._find(event)) as EventListener; + _findEvt = ((event: FindEvt) => { + this._find(event); + }) as EventListener; _soqlGroupBy(event: Event) { if (!this.soqlTable) { From b45097a7bfd1a6ed305c786f6ae639cff7e69d83 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 23 May 2025 11:23:08 +0100 Subject: [PATCH 3/3] fix: find to work in grouped rows --- .../components/calltree-view/module/Find.ts | 15 ++++-- .../components/database-view/DMLView.ts | 52 +++++++++++-------- .../components/database-view/DatabaseView.ts | 4 -- .../components/database-view/SOQLView.ts | 48 +++++++++-------- 4 files changed, 68 insertions(+), 51 deletions(-) diff --git a/log-viewer/modules/components/calltree-view/module/Find.ts b/log-viewer/modules/components/calltree-view/module/Find.ts index d059ad01..3bc46b2b 100644 --- a/log-viewer/modules/components/calltree-view/module/Find.ts +++ b/log-viewer/modules/components/calltree-view/module/Find.ts @@ -206,21 +206,28 @@ export class Find extends Module { } export function formatter(row: RowComponent, findArgs: FindArgs) { - const { text, count } = findArgs; - if (!text || !count || !row.getData() || !row.getData().highlightIndexes?.length) { + const { text } = findArgs; + if (!text) { return; } - const data = row.getData(); + if (!data || !data.highlightIndexes?.length) { + return; + } + const highlights = { indexes: data.highlightIndexes, currentMatch: 0, }; - row.getCells().forEach((cell) => { const cellElem = cell.getElement(); _highlightText(cellElem, findArgs, highlights); }); + + //@ts-expect-error This is private to tabulator, but we have no other choice atm. + if (row._getSelf().type === 'row') { + row.normalizeHeight(); + } } function _highlightText( diff --git a/log-viewer/modules/components/database-view/DMLView.ts b/log-viewer/modules/components/database-view/DMLView.ts index aa882851..f8b7eb3a 100644 --- a/log-viewer/modules/components/database-view/DMLView.ts +++ b/log-viewer/modules/components/database-view/DMLView.ts @@ -272,7 +272,6 @@ export class DMLView extends LitElement { }); } } - const dmlText = this.sortByFrequency(dmlData || [], 'dml'); this.dmlTable = new Tabulator(dmlTableContainer, { height: '100%', @@ -298,8 +297,6 @@ export class DMLView extends LitElement { groupSort: true, groupClosedShowCalcs: true, groupStartOpen: false, - groupValues: [dmlText], - groupBy: ['dml'], groupToggleElement: false, selectableRowsCheck: function (row: RowComponent) { return !row.getData().isDetail; @@ -381,7 +378,6 @@ export class DMLView extends LitElement { if (data.isDetail && data.timestamp) { const detailContainer = this.createDetailPanel(data.timestamp); row.getElement().replaceChildren(detailContainer); - row.normalizeHeight(); } requestAnimationFrame(() => { @@ -424,15 +420,11 @@ export class DMLView extends LitElement { row.getCell('dml').getElement().style.height = origRowHeight + 'px'; }); - this.dmlTable.on('renderStarted', () => { - if (!this.blockClearHighlights && this.totalMatches > 0) { - this._resetFindWidget(); - this._clearSearchHighlights(); - } - + this.dmlTable.on('tableBuilt', () => { const holder = this._getTableHolder(); - holder.style.minHeight = holder.clientHeight + 'px'; holder.style.overflowAnchor = 'none'; + //@ts-expect-error This is a custom function added in the GroupSort custom module + this.dmlTable?.setSortedGroupBy('dml'); }); this.dmlTable.on('renderComplete', () => { @@ -440,6 +432,27 @@ export class DMLView extends LitElement { const table = this._getTable(); holder.style.minHeight = Math.min(holder.clientHeight, table.clientHeight) + 'px'; }); + + this.dmlTable.on('dataSorted', () => { + if (!this.blockClearHighlights && this.totalMatches > 0) { + this._resetFindWidget(); + this._clearSearchHighlights(); + } + }); + + this.dmlTable.on('dataGrouped', () => { + if (!this.blockClearHighlights && this.totalMatches > 0) { + this._resetFindWidget(); + this._clearSearchHighlights(); + } + }); + + this.dmlTable.on('dataFiltering', () => { + if (!this.blockClearHighlights && this.totalMatches > 0) { + this._resetFindWidget(); + this._clearSearchHighlights(); + } + }); } _resetFindWidget() { @@ -457,6 +470,12 @@ export class DMLView extends LitElement { this.dmlTable.clearFindHighlights(Object.values(this.findMap)); this.findMap = {}; this.totalMatches = 0; + + document.dispatchEvent( + new CustomEvent('db-find-results', { + detail: { totalMatches: this.totalMatches, type: 'dml' }, + }), + ); } _getTable() { @@ -477,17 +496,6 @@ export class DMLView extends LitElement { return detailContainer; } - sortByFrequency(dataArray: DMLRow[], field: keyof DMLRow) { - const map = new Map(); - dataArray.forEach((row) => { - const val = row[field]; - map.set(val, (map.get(val) || 0) + 1); - }); - const newMap = new Map([...map.entries()].sort((a, b) => b[1] - a[1])); - - return [...newMap.keys()]; - } - downlodEncoder(defaultFileName: string) { return function (fileContents: string, mimeType: string) { const vscode = vscodeMessenger.getVsCodeAPI(); diff --git a/log-viewer/modules/components/database-view/DatabaseView.ts b/log-viewer/modules/components/database-view/DatabaseView.ts index 83fe0620..14e9eda3 100644 --- a/log-viewer/modules/components/database-view/DatabaseView.ts +++ b/log-viewer/modules/components/database-view/DatabaseView.ts @@ -84,10 +84,6 @@ export class DatabaseView extends LitElement { }; _findResults = (e: CustomEvent<{ totalMatches: number; type: 'dml' | 'soql' }>) => { - if (!this.shadowRoot?.host.clientHeight) { - return; - } - if (e.detail.type === 'dml') { this.dmlMatches = e.detail.totalMatches; } else if (e.detail.type === 'soql') { diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index 3711b749..6707ec1b 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -313,8 +313,6 @@ export class SOQLView extends LitElement { } } - const soqlText = this.sortByFrequency(soqlData || [], 'soql'); - this.soqlTable = new Tabulator(soqlTableContainer, { height: '100%', rowKeyboardNavigation: true, @@ -339,8 +337,6 @@ export class SOQLView extends LitElement { groupSort: true, groupClosedShowCalcs: true, groupStartOpen: false, - groupBy: 'soql', - groupValues: [soqlText], groupToggleElement: false, selectableRows: 'highlight', selectableRowsCheck: function (row: RowComponent) { @@ -507,9 +503,7 @@ export class SOQLView extends LitElement { const data = row.getData(); if (data.isDetail && data.timestamp) { const detailContainer = this.createSOQLDetailPanel(data.timestamp, timestampToSOQl); - row.getElement().replaceChildren(detailContainer); - row.normalizeHeight(); } requestAnimationFrame(() => { @@ -552,15 +546,32 @@ export class SOQLView extends LitElement { row.getCell('soql').getElement().style.height = origRowHeight + 'px'; }); - this.soqlTable.on('renderStarted', () => { + this.soqlTable.on('tableBuilt', () => { + const holder = this._getTableHolder(); + holder.style.overflowAnchor = 'none'; + //@ts-expect-error This is a custom function added in the GroupSort custom module + this.soqlTable?.setSortedGroupBy('soql'); + }); + + this.soqlTable.on('dataSorted', () => { if (!this.blockClearHighlights && this.totalMatches > 0) { this._resetFindWidget(); this._clearSearchHighlights(); } + }); - const holder = this._getTableHolder(); - holder.style.minHeight = holder.clientHeight + 'px'; - holder.style.overflowAnchor = 'none'; + this.soqlTable.on('dataGrouped', () => { + if (!this.blockClearHighlights && this.totalMatches > 0) { + this._resetFindWidget(); + this._clearSearchHighlights(); + } + }); + + this.soqlTable.on('dataFiltering', () => { + if (!this.blockClearHighlights && this.totalMatches > 0) { + this._resetFindWidget(); + this._clearSearchHighlights(); + } }); this.soqlTable.on('renderComplete', () => { @@ -585,6 +596,12 @@ export class SOQLView extends LitElement { this.soqlTable.clearFindHighlights(Object.values(this.findMap)); this.findMap = {}; this.totalMatches = 0; + + document.dispatchEvent( + new CustomEvent('db-find-results', { + detail: { totalMatches: this.totalMatches, type: 'dml' }, + }), + ); } _getTable() { @@ -613,17 +630,6 @@ export class SOQLView extends LitElement { return detailContainer; } - sortByFrequency(dataArray: GridSOQLData[], field: keyof GridSOQLData) { - const map = new Map(); - dataArray.forEach((row) => { - const val = row[field]; - map.set(val, (map.get(val) || 0) + 1); - }); - const newMap = new Map([...map.entries()].sort((a, b) => b[1] - a[1])); - - return [...newMap.keys()]; - } - downlodEncoder(defaultFileName: string) { return function (fileContents: string, mimeType: string) { const vscode = vscodeMessenger.getVsCodeAPI();