Defer offscreen GLQL dashboard panels until near view

What does this MR do and why?

Every GLQL panel on an analytics dashboard view queued its queries as soon as it mounted, whether it was on screen or not. Dashboard panels share one request queue that runs 4 requests at a time (!256168 (merged)), first in, first out. Panels far below the fold competed with the visible ones. On the DAP Impact dashboard's Work tab, that is 37 GLQL requests on gitlab.com.

This MR adds a feature flag, defer_offscreen_glql_dashboard_panels (type gitlab_com_derisk, default off). When it is on, a GLQL panel (app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/glql.vue) renders a GlIntersectionObserver in place of the GLQL resolver until the panel comes within one viewport of being visible. Once that happens, it mounts the resolver, which runs the queries as before. Embedded GLQL blocks in descriptions and comments already defer this way in facade.vue, so this brings dashboard panels in line with that.

Once a panel has loaded, it stays loaded. Scrolling away does not unmount it. Switching tabs remounts the panels, so each tab defers again on its own.

One detail worth calling out: the intersection observer uses the page's scroll panel (.js-static-panel-inner, found with getPanelElement from ~/lib/utils/panels) as its root, not the default viewport. GitLab's layout scrolls inside that panel, and its ancestors use overflow: clip, so a rootMargin set against the default viewport root never reaches past the panel's edge. Tested in Chrome: with the viewport root, a panel 2px below the fold did not load. With the panel as root, panels within one viewport below the fold load correctly. The observer options are memoized per root, so all panels on a page share one IntersectionObserver instance.

The flag is pushed from the Explore, group, and project analytics dashboards controllers, since all three render the same GLQL visualization.

The DAP Impact feature spec (ee/spec/features/explore/system_dashboards_spec.rb) checks that every panel on the Adoption and Work tabs settles without an error, including panels below the fold. Capybara does not scroll the page, so the spec turns the flag off before visiting the dashboard.

Known trade-offs, acceptable for a derisk flag:

  • A panel that has not started loading yet shows an empty body, with no skeleton. This is only visible if a user scrolls more than one viewport ahead faster than the panel can load.
  • Printing the dashboard shows empty bodies for any panel that never came within a viewport of being visible.

No changelog entry. The flag is off by default.

References

Screenshots or screen recordings

No visible UI change beyond panels below the fold starting their load later.

Measured on the local GDK DAP Impact Work tab (29 panels, 1322px tall scroll area), without scrolling:

Flag GLQL requests on load Panels waiting
Off 33 0
On 17 13

After scrolling to the bottom with the flag on, all 33 requests had run and no panel was left waiting.

How to set up and validate locally

  1. In a Rails console:

    Feature.enable(:defer_offscreen_glql_dashboard_panels)
  2. Open http://127.0.0.1:3000/explore/analytics_dashboards/dap_impact?scope=gitlab-org&view=2 (the Work tab). The DAP Impact dashboard needs ClickHouse and Duo analytics data to show values, but the request counts can be checked without that data.

  3. In the browser network tab, filter to api/glql. Without scrolling, only the panels on screen and within about one screen below it send requests.

  4. Scroll down. The remaining panels send their requests as they come within a screen of view.

  5. Disable the flag and reload. Every panel sends its requests on load, regardless of position.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Edited by Brandon Labuschagne

Merge request reports

Loading
Loading