Skip to content

Commit 449f4ea

Browse files
committed
perf: extract and optimise call tree name column formatter
Extracts the inline name formatter into CalltreeNameFormatter.ts using a factory/closure pattern. Key hot-path improvements: replaces createElement('span') with document.createTextNode() (no element allocation for plain text rows), eliminates private row._row.modules.dataTree access by computing treeLevel once in _toCallTree and storing it in row data, and reads dataTreeChildIndent via this.table on first render only.
1 parent f8ebd42 commit 449f4ea

2 files changed

Lines changed: 46 additions & 31 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2022 Certinia Inc. All rights reserved.
3+
*/
4+
import type { LogEvent, LogEventType } from 'apex-log-parser';
5+
import type { CellComponent, EmptyCallback } from 'tabulator-tables';
6+
7+
export function createCalltreeNameFormatter(excludedTypes: Set<LogEventType>) {
8+
let childIndent: number = 9;
9+
10+
return function calltreeNameFormatter(
11+
cell: CellComponent,
12+
_formatterParams: object,
13+
_onRendered: EmptyCallback,
14+
): string | HTMLElement {
15+
const data = cell.getData() as { originalData: LogEvent; treeLevel: number };
16+
const { originalData: node, treeLevel } = data;
17+
// @ts-expect-error this.table is added by tabulator when the formatter is called, but isn't in the types for some reason
18+
childIndent ??= this.table.options.dataTreeChildIndent ?? 9;
19+
const levelIndent = treeLevel * childIndent;
20+
21+
const cellElem = cell.getElement();
22+
cellElem.style.paddingLeft = `${levelIndent + 4}px`;
23+
cellElem.style.textIndent = `-${levelIndent}px`;
24+
25+
if (node.hasValidSymbols) {
26+
const link = document.createElement('a');
27+
link.setAttribute('href', '#!');
28+
link.textContent = node.text;
29+
return link;
30+
}
31+
32+
let text = node.text;
33+
if (node.type && node.type !== text && !excludedTypes.has(node.type)) {
34+
text = node.type + ': ' + text;
35+
}
36+
37+
return document.createTextNode(text) as unknown as HTMLElement;
38+
};
39+
}

log-viewer/src/features/call-tree/components/CalltreeView.ts

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { MiddleRowFocus } from '../../../tabulator/module/MiddleRowFocus.js';
2929
import { RowKeyboardNavigation } from '../../../tabulator/module/RowKeyboardNavigation.js';
3030
import { RowNavigation } from '../../../tabulator/module/RowNavigation.js';
3131
import dataGridStyles from '../../../tabulator/style/DataGrid.scss';
32+
import { createCalltreeNameFormatter } from './CalltreeNameFormatter.js';
3233

3334
// styles
3435
import { globalStyles } from '../../../styles/global.styles.js';
@@ -570,7 +571,7 @@ export class CalltreeView extends LitElement {
570571
const excludedTypes = new Set<LogEventType>(['SOQL_EXECUTE_BEGIN', 'DML_BEGIN']);
571572
const governorLimits = rootMethod.governorLimits;
572573

573-
let childIndent;
574+
const nameFormatter = createCalltreeNameFormatter(excludedTypes);
574575
this.calltreeTable = new Tabulator(callTreeTableContainer, {
575576
data: this._toCallTree(rootMethod.children),
576577
layout: 'fitColumns',
@@ -617,34 +618,7 @@ export class CalltreeView extends LitElement {
617618
return 'Total';
618619
},
619620
cssClass: 'datagrid-textarea datagrid-code-text',
620-
formatter: (cell, _formatterParams, _onRendered) => {
621-
const cellElem = cell.getElement();
622-
const row = cell.getRow();
623-
// @ts-expect-error: _row is private. This is temporary and I will patch the text wrap behaviour in the library.
624-
const dataTree = row._row.modules.dataTree;
625-
const treeLevel = dataTree?.index ?? 0;
626-
childIndent ??= row.getTable().options.dataTreeChildIndent || 0;
627-
const levelIndent = treeLevel * childIndent;
628-
cellElem.style.paddingLeft = `${levelIndent + 4}px`;
629-
cellElem.style.textIndent = `-${levelIndent}px`;
630-
631-
const node = (cell.getData() as CalltreeRow).originalData;
632-
let text = node.text;
633-
if (node.hasValidSymbols) {
634-
const link = document.createElement('a');
635-
link.setAttribute('href', '#!');
636-
link.textContent = text;
637-
return link;
638-
}
639-
640-
if (node.type && !excludedTypes.has(node.type) && node.type !== text) {
641-
text = node.type + ': ' + text;
642-
}
643-
644-
const textSpan = document.createElement('span');
645-
textSpan.textContent = text;
646-
return textSpan;
647-
},
621+
formatter: nameFormatter,
648622
variableHeight: true,
649623
cellClick: (e, cell) => {
650624
const { type } = window.getSelection() ?? {};
@@ -977,7 +951,7 @@ export class CalltreeView extends LitElement {
977951
}
978952
}
979953

980-
private _toCallTree(nodes: LogEvent[]): CalltreeRow[] | undefined {
954+
private _toCallTree(nodes: LogEvent[], treeLevel = 0): CalltreeRow[] | undefined {
981955
const len = nodes.length;
982956
if (!len) {
983957
return undefined;
@@ -989,12 +963,13 @@ export class CalltreeView extends LitElement {
989963
if (!node) {
990964
continue;
991965
}
992-
const children = node.children.length ? this._toCallTree(node.children) : null;
966+
const children = node.children.length ? this._toCallTree(node.children, treeLevel + 1) : null;
993967
results.push({
994968
id: node.timestamp + '-' + i,
995969
originalData: node,
996970
_children: children,
997971
text: node.text,
972+
treeLevel,
998973
namespace: node.namespace,
999974
duration: node.duration,
1000975
dmlCount: node.dmlCount,
@@ -1070,6 +1045,7 @@ interface CalltreeRow {
10701045
originalData: LogEvent;
10711046
_children: CalltreeRow[] | undefined | null;
10721047
text: string;
1048+
treeLevel: number;
10731049
duration: CountTotals;
10741050
namespace: string;
10751051
dmlCount: CountTotals;

0 commit comments

Comments
 (0)