Skip to content

Add Query Monitor 4.0 support#357

Open
JanJakes wants to merge 6 commits intotrunkfrom
query-monitor-4-support
Open

Add Query Monitor 4.0 support#357
JanJakes wants to merge 6 commits intotrunkfrom
query-monitor-4-support

Conversation

@JanJakes
Copy link
Copy Markdown
Member

@JanJakes JanJakes commented Apr 8, 2026

Summary

Query Monitor 4.0 switched from server-side PHP rendering to client-side Preact rendering inside a shadow DOM. This broke the existing SQLite query display integration, which overrides output_query_row() — a method that QM 4.0 no longer calls.

This PR adds QM 4.0 support while maintaining backward compatibility with QM 3.x:

  • Version detection in boot.php using QM_VERSION to choose between QM 3.x and 4.0+ integration paths.
  • Custom QM collector and outputter (qm4.php) that extracts SQLite queries from $wpdb->queries. With $client_side_rendered = true, QM auto-serializes the collector data into window.QueryMonitorData.data.sqlite, so the outputter's only job is to emit an inline JS module.
  • Shadow DOM injection JS (query-monitor-sqlite.js) that reads from QueryMonitorData and injects <details> elements into the DB queries panel. It runs on DOMContentLoaded (after QM attaches the shadow root) and uses a debounced MutationObserver to re-inject after Preact re-renders (panel switches, filters, etc.). Each injected element tracks its SQL key to handle Preact's DOM recycling on filter/sort changes.
  • E2E tests for both QM 3.x and 4.0+, each auto-skipping when the other version is detected.

Fixes the CI failure introduced by QM 4.0 release.

Test Plan

  • E2E test passes with QM 4.0+ (latest): <details> elements appear in shadow DOM, SQLite queries display correctly
  • E2E test passes with QM 3.x (3.16.4): existing server-side integration works unchanged
  • PHPCS passes clean
  • CI end-to-end tests pass

Detect QM 4.0+ via the QM_VERSION constant and load the collector-based
integration. Fall back to the existing HTML output override for QM 3.x.
@JanJakes JanJakes force-pushed the query-monitor-4-support branch 3 times, most recently from 23cbf68 to 7755e17 Compare April 9, 2026 15:37
Register a custom QM collector that extracts SQLite queries from
$wpdb->queries, re-indexed to match QM's rendered row order, and a
client-side HTML outputter that emits the inline JS module used by
the QM 4.0 shadow DOM integration. QM auto-serializes the collector
data into window.QueryMonitorData.data.sqlite.
@JanJakes JanJakes force-pushed the query-monitor-4-support branch from 691a188 to ab2e2ea Compare April 10, 2026 09:14
Inject <details> elements into QM 4.0's Preact-rendered DB queries
panel inside the shadow DOM. Waits for DOMContentLoaded so QM has
attached the shadow root, then uses a debounced MutationObserver to
re-inject after panel switches and re-renders.
Add separate test cases for QM 3.x (server-side HTML) and QM 4.0+
(Preact shadow DOM). Each test auto-skips when the other QM version
is detected.
plugin.php → qm3.php (QM 3.x server-side HTML override)
collector.php → qm4.php (QM 4.0+ collector and JS injection)
@JanJakes JanJakes force-pushed the query-monitor-4-support branch from ab2e2ea to 567490f Compare April 10, 2026 09:35
Copy link
Copy Markdown
Member

@ashfame ashfame left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean and well-architected PR. The QM 3.x/4.0 split is sound, the SQL-text-based matching is a smart approach for surviving Preact re-renders, and the E2E tests are thorough. Left a few minor notes inline — nothing blocking.

$mapped[ $sql ] = array_column( $query['sqlite_queries'], 'sql' );
}
}
$this->data->queries = $mapped;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Setting ->queries dynamically on $this->data could trigger a PHP 8.2+ "Creation of dynamic property" deprecation if QM 4.0's base data object isn't a plain stdClass. It may be worth defining a small SQLite_QM_Data extends QM_Data class with a typed $queries property to be safe.

new MutationObserver( () => {
clearTimeout( timer );
timer = setTimeout( () => inject( shadowRoot, sqliteData ), 100 );
} ).observe( shadowRoot, { childList: true, subtree: true } );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: The observer watches the entire shadow root subtree, so any QM panel interaction (not just DB queries) triggers a re-injection cycle. Since inject() early-exits when #qm-db_queries isn't found the overhead is minimal, but if the panel element is stable in the DOM, scoping the observer to it would cut down on unnecessary callbacks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The observer needs to watch the full shadow root because #qm-db_queries doesn't exist until the user navigates to the Database Queries tab — scoping to it would miss its creation. The early-exit in inject() keeps the overhead negligible.

};
} );
expect( counts.rows ).toBeGreaterThan( 0 );
expect( counts.details ).toBe( counts.rows );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This asserts that every visible row after the get_option filter has an SQLite <details> element. It holds today because all DB queries on SQLite carry sqlite_queries metadata, but it could become fragile if QM ever injects synthetic/internal rows into the table. Low risk — just flagging for awareness.

Define SQLite_QM_Data with a typed $queries property instead of
relying on dynamic property assignment on QM_Data_Fallback. This
aligns with QM 4.0's QM_DataCollector pattern and avoids depending
on #[AllowDynamicProperties].
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants