Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- πŸ—‚οΈ **Column views** (Call Tree, Analysis, Database): switch preset column sets, show/hide columns from the **Columns** button or the header right-click menu, inline **reset** to restore defaults; choices persist per view.
- 🏷️ **New columns**: **Object** (queried/target SObject, with group-by) on SOQL/DML; **SOSL Count/Rows**, **Avg Self Time** and optional **Self** variants for every governor metric; and a SOQL **Query Plan** view (Relative Cost, Leading Operation, SObject Type, Cardinality).
- πŸ”΄ **Timeline exception markers**: exceptions show as red lines, with a **Throws** count in method tooltips. ([#828])
- 🧰 **Filter bar** (Call Tree, Database): filters now live in one toolbar above each table instead of in the column headers. Keep only the rows you care about β€” by **Namespace**, **Object** or **Caller Namespace** (multi-select, showing how many are picked), or by a **Row Count** / **Time Taken** min–max range; active filters are highlighted. On a narrow window the filters collapse behind a **Filter** button that opens them in a panel. ([#873])

### Changed

Expand All @@ -30,6 +31,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- πŸ—‚οΈ **Call Tree + Database styling**: VS Code style tree icons, and rows indent under their group headings. ([#832]).
- πŸŽ›οΈ **Modernised dropdowns**: searchable, compact controls that carry the field and value in one place (e.g. `Group: Namespace`, `Type: All`) ([#848]).
- ♻️ Replaced the deprecated `webview-ui-toolkit` with [vscode-elements](https://github.com/vscode-elements/elements) for all UI controls. ([#576]).
- πŸ—„οΈ **Database table columns** (DML, SOQL, SOSL): consolidated onto the shared Call Tree column/sort styling for a consistent look across all tables.
- 🧱 **Data grids**: a crisper header/content separator and tidied grid styling across all tables.

### Fixed

- πŸ“Š **Database usage bars** (Row Count, Time Taken): the usage bar was hidden whenever the rounded percentage was 0% (the common case for small row counts against large governor limits), so it rarely appeared; it now fills relative to the grid's own column total rather than a governor limit, shows on grouped summary rows, and Time Taken (ms) now shows a bar too.

## [1.20.1] 2026-07-23

Expand Down Expand Up @@ -523,6 +530,7 @@ Skipped due to adopting odd numbering for pre releases and even number for relea

<!-- Unreleased -->

[#873]: https://github.com/certinia/debug-log-analyzer/issues/873
[#576]: https://github.com/certinia/debug-log-analyzer/issues/576
[#832]: https://github.com/certinia/debug-log-analyzer/issues/832
[#848]: https://github.com/certinia/debug-log-analyzer/issues/848
Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Apex Log Analyzer is a blazing-fast VS Code extension for Salesforce developers.
- **🌲 [Interactive Call Tree](#-call-tree)** – Dive into execution stacks with timing, row counts, and DML/ SOQL metrics.
- **πŸ“Š [Apex](#-apex-analysis) + [Database](#%EF%B8%8F-database-analysis) Analysis** – Identify slow-performing SOQL, high-impact DML, and time-heavy Apex methods.
- **πŸ“„ [Raw Log Navigation](#-raw-log-navigation)** – Jump between analysis views and raw log files with code folding, hover details, and bidirectional navigation.
- **🧠 Smart Filtering + Sorting** – Focus on what matters: filter by namespace, event type, or duration.
- **🧠 Smart Filtering + Sorting** – Focus on what matters: filter by namespace, object, type, or a value range.
- **πŸ” Deep Search** – Find events across the flame chart, call tree, and database tables.
- **πŸ“€ Export + Share** – Copy or Export Salesforce debug log insights for analysis or collaboration.

Expand Down Expand Up @@ -103,8 +103,7 @@ Explore nested method calls with performance metrics:
- **Call Tree Views**: Use Time Order for sequence, Aggregated for repeated hot paths, Bottom-Up for caller attribution
- **Column Views** – Switch preset column sets (General, Time, Governor Limits, Database, Memory), show/hide columns from the header menu, reset to defaults
- **Group Bottom-Up by Namespace or Type**
- **Filter by Namespace, Type or Duration**
- **Toggle Debug-Only + Detail Events**
- **Filter bar** – Namespace, event Type, and a Total/Self Time range, plus **Details** and **Debug Only** toggles
- **Keyboard Navigation**
- **Click to go to Code** – Jump to the source method in your project

Expand All @@ -117,7 +116,7 @@ See which methods are the slowest, most frequent. or expensive.
- **Group by Type, Namespace, or Caller Namespace **
- **Column Views** – Preset column sets, show/hide columns, reset to defaults
- **Sort by Duration, Count, Name, Type or Namespace**
- **Filter to specific event types**
- **Filter** – show or hide zero-time events (Details)
- **Copy or Export to CSV**

![Analysis](https://raw.githubusercontent.com/certinia/debug-log-analyzer/main/lana/assets/1_20/analysis.png)
Expand All @@ -130,12 +129,11 @@ Highlight slow Salesforce SOQL queries, non-selective filters, and DML issues, a
- **Tracked vs consumed** – each section reconciles statements seen in the log against the governor-counted total, so you can spot queries that didn't count (e.g. custom metadata).
- **Separate SOQL, DML and SOSL tables** – SOSL is fully searchable, with its rows metered against the 2,000-per-query cap.
- **Object column + Group by Object / Namespace / Caller Namespace / Query**
- **Filter bar** – Namespace, Object, Caller Namespace, and a Row Count / Time Taken range
- **SOQL Duration, Selectivity, Aggregates, Row Count**
- **Column Views** – Preset column sets (incl. a SOQL Query Plan view), show/hide columns, reset to defaults
- **View the Call Stack**, **SOQL Optimization Tips**, **Sort**, **Copy or Export to CSV**

<!-- TODO: re-capture database.png for the redesigned Database tab (governor strip, DML/SOQL/SOSL sections). Optionally add a sosl.png. -->

![Database](https://raw.githubusercontent.com/certinia/debug-log-analyzer/main/lana/assets/1_20/database.png)

## πŸ“Š Governor Limit Tracking
Expand Down
2 changes: 1 addition & 1 deletion lana-docs/docs/docs/features/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Each column can be sorted by clicking the column header, this will sort the rows

### Filtering

1. Show Log events for specific namespaces using the namespace column filter
1. **Details** – events with 0 time are hidden by default; toggle to show them from the **filters** .

### Column Views

Expand Down
11 changes: 5 additions & 6 deletions lana-docs/docs/docs/features/calltree.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,11 @@ Heap is a tricky governor limit, since memory gets freed as well as allocated an

### Filtering

1. Details (events with 0 time) are hidden by default but can be shown/ hidden.
1. Show only debug statements using the Debug Only filter.
1. Show Log events for specific namespaces using the namespace column filter
1. Min and Max filtering can be done on the _Total Time_ and _Self Time_ columns.

Debug Only and Type filtering are available in **Time Order** and **Aggregated**. The Details filter is available in all three views. The **Bottom-Up** view has its own grouping controls (None, Namespace, Caller Namespace, Type).
1. **Details** – events with 0 time are hidden by default; toggle to show them (all views).
1. **Debug Only** – show only debug statements (**Time Order** and **Aggregated**).
1. **Type** – keep a single event type (**Time Order** and **Aggregated**).
1. **Namespace** – keep one or more namespaces from the dropdown.
1. **Total Time** / **Self Time** – set a _min–max_ range.

### SOQL Formatting

Expand Down
3 changes: 2 additions & 1 deletion lana-docs/docs/docs/features/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ If the grouping is removed the sorting applies the same but across all rows inst

### Filtering

1. In the SOQL view show Log events for specific namespaces using the namespace column filter
1. **Namespace**, **Object** or **Caller Namespace** β€” pick one or more values to keep.
1. **Row Count** and **Time Taken** β€” set a _min–max_ range.

### Column Views

Expand Down
191 changes: 117 additions & 74 deletions log-viewer/src/components/ContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import '#vscode-elements/vscode-icon.js';
import { LitElement, css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

import { globalStyles } from '../styles/global.styles.js';

/** Gutter kept between the menu and the viewport edges (px). Shared by the CSS
* height cap and the off-screen position clamp so a shifted menu still fits. */
const GUTTER = 12;

export interface ContextMenuItem {
/** Unique identifier for the menu item */
id: string;
Expand All @@ -44,6 +50,12 @@ export interface ContextMenuItem {
disabled?: boolean;
/** If true, selecting the item (or its action) leaves the menu open (multi-toggle). */
keepOpen?: boolean;
/**
* If set, renders a real `.vs-checkbox` in place of the label's checkmark
* glyph β€” for multiselect rows (e.g. per-column visibility toggles). Leave
* unset for single-select rows (view presets), which keep the checkmark.
*/
checked?: boolean;
/**
* Optional trailing action icon (e.g. per-row reset). Clicking it emits
* `menu-select` with `action.id` instead of the row's own id.
Expand All @@ -59,76 +71,85 @@ export interface ContextMenuItem {
*/
@customElement('context-menu')
export class ContextMenu extends LitElement {
static styles = css`
:host {
position: fixed;
z-index: 10000;
display: none;
}

:host([visible]) {
display: block;
}

.menu {
min-width: 180px;
padding: 6px 0;
background-color: var(--vscode-menu-background, #252526);
border: 1px solid var(--vscode-menu-border, #454545);
border-radius: 6px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif);
font-size: 13px;
color: var(--vscode-menu-foreground, #cccccc);
outline: none;
}

.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 20px 6px 12px;
cursor: pointer;
user-select: none;
border-radius: 4px;
margin: 0 6px;
}

.menu-item:hover:not(.disabled) {
background-color: var(--vscode-menu-selectionBackground, #094771);
color: var(--vscode-menu-selectionForeground, #ffffff);
}

.menu-item.disabled {
color: var(--vscode-disabledForeground, #6e6e6e);
cursor: default;
}

.label {
flex: 1;
}

.shortcut {
margin-left: 32px;
opacity: 0.7;
font-size: 12px;
}

.item-action {
margin-left: 12px;
opacity: 0.7;
}

.item-action:hover {
opacity: 1;
}

.separator {
height: 1px;
margin: 6px 12px;
background-color: var(--vscode-menu-separatorBackground, #454545);
}
`;
static styles = [
globalStyles,
css`
:host {
position: fixed;
z-index: 10000;
display: none;
}

:host([visible]) {
display: block;
}

.menu {
min-width: 180px;
/* Never exceed the viewport (leave a gutter top + bottom); scroll the
overflow so every field stays reachable on a short screen. border-box
keeps the padding inside the cap; keep the 24px (2 * GUTTER) in sync
with the constant used by adjustPosition(). */
box-sizing: border-box;
max-height: calc(100vh - 24px);
overflow-y: auto;
padding: 6px 0;
font-size: var(--filter-popover-row-font-size);
outline: none;
}

.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 20px 6px 12px;
cursor: pointer;
user-select: none;
border-radius: 4px;
margin: 0 6px;
}

.menu-item:hover:not(.disabled) {
background-color: var(--vscode-list-hoverBackground);
}

.menu-item.disabled {
color: var(--vscode-disabledForeground, #6e6e6e);
cursor: default;
}

.label {
flex: 1;
}

.shortcut {
margin-left: 32px;
opacity: 0.7;
font-size: 12px;
}

.item-action {
margin-left: 12px;
opacity: 0.7;
}

.item-action:hover {
opacity: 1;
}

/* Purely visual β€” the row's own click handles the toggle. */
.menu-item .vs-checkbox {
margin-right: 8px;
pointer-events: none;
}

.separator {
height: 1px;
margin: 6px 12px;
background-color: var(--vscode-menu-separatorBackground, #454545);
}
`,
];

@property({ type: Array }) items: ContextMenuItem[] = [];
@property({ type: Number }) x = 0;
Expand All @@ -137,6 +158,8 @@ export class ContextMenu extends LitElement {

private boundHandleClickOutside = this.handleClickOutside.bind(this);
private boundHandleKeyDown = this.handleKeyDown.bind(this);
/** Re-clamp the open menu when the window/webview resizes so it can't drift off-screen. */
private boundHandleResize = (): void => this.adjustPosition();

/**
* Show the context menu at the specified screen coordinates.
Expand All @@ -161,6 +184,7 @@ export class ContextMenu extends LitElement {
requestAnimationFrame(() => {
document.addEventListener('mousedown', this.boundHandleClickOutside, true);
document.addEventListener('keydown', this.boundHandleKeyDown, true);
window.addEventListener('resize', this.boundHandleResize);

// Adjust position if menu goes off-screen (after render)
this.updateComplete.then(() => this.adjustPosition());
Expand All @@ -181,6 +205,7 @@ export class ContextMenu extends LitElement {

document.removeEventListener('mousedown', this.boundHandleClickOutside, true);
document.removeEventListener('keydown', this.boundHandleKeyDown, true);
window.removeEventListener('resize', this.boundHandleResize);

this._visible = false;
this.removeAttribute('visible');
Expand Down Expand Up @@ -252,19 +277,25 @@ export class ContextMenu extends LitElement {
return;
}

// Reset to the anchor first so repeated calls (e.g. on resize) re-clamp from
// the original point rather than compounding a previous shift.
this.style.left = `${this.x}px`;
this.style.top = `${this.y}px`;

const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;

// Adjust horizontal position if menu goes off right edge
if (this.x + rect.width > viewportWidth) {
const newLeft = Math.max(0, viewportWidth - rect.width - 8);
const newLeft = Math.max(GUTTER, viewportWidth - rect.width - GUTTER);
this.style.left = `${newLeft}px`;
}

// Adjust vertical position if menu goes off bottom edge
// Adjust vertical position if menu goes off bottom edge. The CSS max-height
// caps rect.height at viewportHeight - 2*GUTTER, so this always fits.
if (this.y + rect.height > viewportHeight) {
const newTop = Math.max(0, viewportHeight - rect.height - 8);
const newTop = Math.max(GUTTER, viewportHeight - rect.height - GUTTER);
this.style.top = `${newTop}px`;
}
}
Expand All @@ -275,7 +306,9 @@ export class ContextMenu extends LitElement {
}

return html`
<div class="menu" role="menu">${this.items.map((item) => this.renderItem(item))}</div>
<div class="filter-popover menu" role="menu">
${this.items.map((item) => this.renderItem(item))}
</div>
`;
}

Expand All @@ -291,6 +324,16 @@ export class ContextMenu extends LitElement {
data-id="${item.id}"
@click="${() => this.handleItemClick(item)}"
>
${
item.checked !== undefined
? html`<input
type="checkbox"
class="vs-checkbox"
tabindex="-1"
.checked="${item.checked}"
/>`
: nothing
}
<span class="label">${item.label}</span>
${item.shortcut ? html`<span class="shortcut">${item.shortcut}</span>` : nothing}
${
Expand Down
Loading