Skip to content

Commit 3c9d61c

Browse files
Merge pull request #860 from lukecotter/feat-298-column-views
feat(log-viewer): configurable column views across grids
2 parents e0db54e + 5d7b5fd commit 3c9d61c

39 files changed

Lines changed: 2708 additions & 339 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- 🔴 **Timeline exception markers**: exceptions show as red lines, with a **Throws** count in method tooltips. ([#828])
13+
- 🧩 **Call Tree, Analysis and Database tables** gain configurable columns and views. ([#298])
14+
- 🗂️ **Column views**: switch each table between column sets (`General`, `Time`, `Governor Limits`, `Database`, `Memory`); edit a view by showing/hiding columns from the **Columns** toolbar button or the header right-click menu, with inline **reset** to restore defaults; choices persist per view.
15+
- 📊 **New columns**: **SOSL Count/Rows**, **Avg Self Time**, and optional **Self** variants for every governor metric.
16+
- 🧠 **Heap / memory analysis**: **Heap Allocated** (+ Self) columns, surfaced through the `Memory` view and the Timeline governor limit strip.
17+
- 🔎 **SOQL Query Plan** view: Relative Cost, Leading Operation, SObject Type and Cardinality.
1318

1419
### Changed
1520

@@ -505,6 +510,8 @@ Skipped due to adopting odd numbering for pre releases and even number for relea
505510
[#832]: https://github.com/certinia/debug-log-analyzer/issues/832
506511
[#848]: https://github.com/certinia/debug-log-analyzer/issues/848
507512
[#827]: https://github.com/certinia/debug-log-analyzer/issues/827
513+
[#298]: https://github.com/certinia/debug-log-analyzer/issues/298
514+
[#32]: https://github.com/certinia/debug-log-analyzer/issues/32
508515

509516
<!-- v1.20.0 -->
510517

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ Also: Frame Selection & Navigation, Dynamic Frame Labels, Adaptive Frame Detail,
9999

100100
Explore nested method calls with performance metrics:
101101

102-
- **Metrics**: Self Time, Total Time, SOQL/DML/Thrown Counts, SOQL/DML/Rows
103-
- **Views**: Use Time Order for sequence, Aggregated for repeated hot paths, Bottom-Up for caller attribution
102+
- **Metrics**: Self Time, Total Time, SOQL/DML/SOSL Counts + Rows, Heap, Governor Limit Avg + Peak, Thrown
103+
- **Call Tree Views**: Use Time Order for sequence, Aggregated for repeated hot paths, Bottom-Up for caller attribution
104+
- **Column Views** – Switch preset column sets (General, Time, Governor Limits, Database, Memory), show/hide columns from the header menu, reset to defaults
104105
- **Group Bottom-Up by Namespace or Type**
105106
- **Filter by Namespace, Type or Duration**
106107
- **Toggle Debug-Only + Detail Events**
@@ -114,6 +115,7 @@ Explore nested method calls with performance metrics:
114115
See which methods are the slowest, most frequent. or expensive.
115116

116117
- **Group by Type, Namespace, or Caller Namespace **
118+
- **Column Views** – Preset column sets, show/hide columns, reset to defaults
117119
- **Sort by Duration, Count, Name, Type or Namespace**
118120
- **Filter to specific event types**
119121
- **Copy or Export to CSV**
@@ -126,6 +128,7 @@ Highlight slow Salesforce SOQL queries, non-selective filters, and DML issues.
126128

127129
- **SOQL + DML Duration, Selectivity, Aggregates, Row Count**
128130
- **Group by Namespace, Caller Namespace or Query**
131+
- **Column Views** – Preset column sets (incl. a SOQL Query Plan view), show/hide columns, reset to defaults
129132
- **View the Call Stack**
130133
- **SOQL Optimization Tips**
131134
- **Sort by SOQL or DML, Duration, Selectivity, Aggregates, Row Count**

apex-log-parser/__tests__/ApexLogParser.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,32 @@ describe('parseLog tests', () => {
381381
expect(thrown?.thrownCount).toEqual({ self: 1, total: 1 });
382382
});
383383

384+
it('heapAllocated is seeded on the allocation leaf and rolled up as total on ancestors', async () => {
385+
const log =
386+
'09:18:22.6 (100)|EXECUTION_STARTED\n\n' +
387+
'15:20:52.222 (200)|METHOD_ENTRY|[185]|01p4J00000FpS6t|Outer.run()\n' +
388+
'15:20:52.222 (300)|HEAP_ALLOCATE|[52]|Bytes:10\n' +
389+
'15:20:52.222 (400)|METHOD_ENTRY|[190]|01p4J00000FpS6u|Inner.work()\n' +
390+
'15:20:52.222 (500)|HEAP_ALLOCATE|[60]|Bytes:32\n' +
391+
'15:20:52.222 (600)|METHOD_EXIT|[190]|01p4J00000FpS6u|Inner.work()\n' +
392+
'15:20:52.222 (700)|METHOD_EXIT|[185]|01p4J00000FpS6t|Outer.run()\n' +
393+
'09:19:13.82 (2000)|EXECUTION_FINISHED\n';
394+
395+
const apexLog = parse(log);
396+
397+
// children[0] is the EXECUTION_STARTED wrapper; Outer.run() is its child.
398+
const outer = apexLog.children[0]!.children[0]!;
399+
const inner = outer.children.find((child) => child.type === 'METHOD_ENTRY')!;
400+
401+
// The allocation leaf carries self === total === bytes.
402+
const outerAlloc = outer.children.find((child) => child.type === 'HEAP_ALLOCATE');
403+
expect(outerAlloc?.heapAllocated).toEqual({ self: 10, total: 10 });
404+
405+
// Inner rolls up its own 32 bytes; Outer rolls up both (10 + 32).
406+
expect(inner.heapAllocated).toEqual({ self: 0, total: 32 });
407+
expect(outer.heapAllocated).toEqual({ self: 0, total: 42 });
408+
});
409+
384410
it('Methods should have line-numbers', async () => {
385411
const log =
386412
'09:18:22.6 (0)|EXECUTION_STARTED\n\n' +

apex-log-parser/src/ApexLogParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ export class ApexLogParser {
452452
parent.soslRowCount.total += child.soslRowCount.total;
453453
parent.duration.self -= child.duration.total;
454454
parent.thrownCount.total += child.thrownCount.total;
455+
parent.heapAllocated.total += child.heapAllocated.total;
455456
}
456457
}
457458
}

apex-log-parser/src/LogEvents.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,24 @@ export abstract class LogEvent {
246246
total: 0,
247247
};
248248

249+
/**
250+
* Total + self heap bytes allocated (HEAP_ALLOCATE / BULK_HEAP_ALLOCATE).
251+
*
252+
* `self` is seeded on the allocation leaf node (like DML/SOQL counts), so a method's
253+
* `heapAllocated.self` is typically 0 and `heapAllocated.total` is the sum of allocations
254+
* in this node and its children.
255+
*/
256+
heapAllocated: SelfTotal = {
257+
/**
258+
* The net bytes allocated directly by this node.
259+
*/
260+
self: 0,
261+
/**
262+
* The total bytes allocated in this node and child nodes
263+
*/
264+
total: 0,
265+
};
266+
249267
/**
250268
* The line types which would legitimately end this method
251269
*/
@@ -515,6 +533,7 @@ export class BulkHeapAllocateLine extends LogEvent {
515533
super(parser, parts);
516534
this.text = parts[2] || '';
517535
this.bytes = parseBytes(parts[2]);
536+
this.heapAllocated.self = this.heapAllocated.total = this.bytes;
518537
}
519538
}
520539

@@ -1103,6 +1122,7 @@ export class HeapAllocateLine extends LogEvent {
11031122
this.lineNumber = this.parseLineNumber(parts[2]);
11041123
this.text = parts[3] || '';
11051124
this.bytes = parseBytes(parts[3]);
1125+
this.heapAllocated.self = this.heapAllocated.total = this.bytes;
11061126
}
11071127
}
11081128

lana-docs/docs/docs/features/analysis.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Each column can be sorted by clicking the column header, this will sort the rows
3131

3232
1. Show Log events for specific namespaces using the namespace column filter
3333

34+
### Column Views
35+
36+
The same presets as the Call Tree (General, Time, Governor Limits, Database, Memory), from the **Columns** button in the toolbar (or the header right-click menu). Show or hide individual columns there; an edited view shows a **reset** icon. Choices persist per view.
37+
3438
### Group
3539

3640
The rows can be grouped by Type, Namespace, or Caller Namespace.

lana-docs/docs/docs/features/calltree.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ Clicking the link in the event column will open the corresponding file and line,
5050

5151
Each column can be sorted by clicking the column header, this will sort the rows within the tree structure e.g sorting by self time will sort the children within a parent with the largest self time to the top but only within that parent.
5252

53+
### Column Views
54+
55+
Switch column sets from the **Columns** button in the toolbar (or the header right-click menu):
56+
57+
1. **General** – everyday overview.
58+
1. **Time** – call count and self/total/average time.
59+
1. **Governor Limits** – DML/SOQL/SOSL counts + rows, throws, heap, and governor limit usage (avg + peak).
60+
1. **Database** – DML/SOQL/SOSL counts + rows.
61+
1. **Memory** – heap self and total.
62+
63+
Show or hide individual columns from the same menu; an edited view shows a **reset** icon. Choices persist per view.
64+
5365
### Filtering
5466

5567
1. Details (events with 0 time) are hidden by default but can be shown/ hidden.

lana-docs/docs/docs/features/database.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ If the grouping is removed the sorting applies the same but across all rows inst
3737

3838
1. In the SOQL view show Log events for specific namespaces using the namespace column filter
3939

40+
### Column Views
41+
42+
Switch column sets from the **Columns** button in the toolbar (or the header right-click menu). SOQL offers **General**, **Performance** and **Query Plan** (Relative Cost, Leading Operation, SObject Type, Cardinality); DML offers **General** and **Timing**. Show or hide individual columns there; an edited view shows a **reset** icon. Choices persist per table.
43+
4044
### Group
4145

4246
By default rows are grouped by the SOQL/ DML text, grouping can be removed and the rows shows as a flat list using the _Group by_ item in the header menu. The groups are default sorted with the groups with the most items at the top.

lana/package.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@
138138
"default": false,
139139
"markdownDescription": "Tint the Call Tree Name column by event category, matching the Timeline colors, instead of showing a small color chip. Default: `false`",
140140
"order": 0
141+
},
142+
"lana.callTree.columnView": {
143+
"title": "Column view",
144+
"type": "string",
145+
"default": "General",
146+
"enum": [
147+
"General",
148+
"Time",
149+
"Governor Limits",
150+
"Database",
151+
"Memory"
152+
],
153+
"markdownDescription": "The column view applied to the Call Tree and Analysis tables. Default: `General`",
154+
"order": 1
141155
}
142156
}
143157
},
@@ -326,6 +340,37 @@
326340
}
327341
}
328342
}
343+
},
344+
{
345+
"type": "object",
346+
"id": "lana",
347+
"title": "Database",
348+
"order": 2,
349+
"properties": {
350+
"lana.database.soql.columnView": {
351+
"title": "SOQL column view",
352+
"type": "string",
353+
"default": "General",
354+
"enum": [
355+
"General",
356+
"Performance",
357+
"Query Plan"
358+
],
359+
"markdownDescription": "The column view applied to the Database SOQL table. Default: `General`",
360+
"order": 0
361+
},
362+
"lana.database.dml.columnView": {
363+
"title": "DML column view",
364+
"type": "string",
365+
"default": "General",
366+
"enum": [
367+
"General",
368+
"Timing"
369+
],
370+
"markdownDescription": "The column view applied to the Database DML table. Default: `General`",
371+
"order": 2
372+
}
373+
}
329374
}
330375
]
331376
},

lana/src/commands/LogView.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import type { Context } from '../Context.js';
1111
import { OpenFileInPackage } from '../display/OpenFileInPackage.js';
1212
import { WebView } from '../display/WebView.js';
1313
import { RawLogNavigation } from '../log-features/RawLogNavigation.js';
14-
import { getConfig } from '../workspace/AppConfig.js';
14+
import {
15+
COLUMN_OVERRIDE_SECTIONS,
16+
getColumnOverrides,
17+
getConfig,
18+
updateColumnOverride,
19+
updateConfig,
20+
} from '../workspace/AppConfig.js';
1521

1622
interface WebViewLogFileRequest<T = unknown> {
1723
requestId: string;
@@ -102,14 +108,31 @@ export class LogView {
102108
}
103109

104110
case 'getConfig': {
111+
const config = getConfig();
112+
const overrides = getColumnOverrides(context.context.globalState);
113+
config.callTree.columnOverrides = overrides['callTree.columnOverrides'] ?? {};
114+
config.database.soql.columnOverrides = overrides['database.soql.columnOverrides'] ?? {};
115+
config.database.dml.columnOverrides = overrides['database.dml.columnOverrides'] ?? {};
105116
panel.webview.postMessage({
106117
requestId,
107118
cmd: 'getConfig',
108-
payload: getConfig(),
119+
payload: config,
109120
});
110121
break;
111122
}
112123

124+
case 'updateConfig': {
125+
const { section, value } = payload as { section: string; value: unknown };
126+
if (section) {
127+
if ((COLUMN_OVERRIDE_SECTIONS as readonly string[]).includes(section)) {
128+
updateColumnOverride(context.context.globalState, section, value);
129+
} else {
130+
updateConfig(section, value);
131+
}
132+
}
133+
break;
134+
}
135+
113136
case 'saveFile': {
114137
const { fileContent, options } = payload as {
115138
fileContent: string;

0 commit comments

Comments
 (0)