Skip to content

Feature/refactor allowed widgets - #68

Merged
imenattatra merged 15 commits into
metr-mainfrom
feature/refactor-allowed-widgets
Jul 8, 2026
Merged

Feature/refactor allowed widgets#68
imenattatra merged 15 commits into
metr-mainfrom
feature/refactor-allowed-widgets

Conversation

@imenattatra

@imenattatra imenattatra commented Jun 10, 2026

Copy link
Copy Markdown

Closes https://github.com/metr-systems/backlog/issues/4778

AI Disclaimer: Yes. Claude Code was used to plan and assist

Summary

Dashboards can now reference the query that has their allowed-widgets mapping by a stable, org-unique identifier instead of relying on the naming convention allowed_widgets_{dashboard_id}.

Previously, the only way a dashboard could declare which widgets are allowed per parameter value was to create a query literally named allowed_widgets_<dashboard_id>. This would limit us when working with global redash since we need to be able to link the dynamic widgets query to many sub-dashboard of the final composed dashbaord.

This PR introduces:

  • A new MetrQuery table (mirroring the existing MetrDashboard pattern) carrying a query_identifier string that is unique per org.
  • A new allowed_widget_query_identifier column on MetrDashboard.
  • At dashboard render time the backend resolves MetrDashboard.allowed_widget_query_identifierMetrQuery.query_identifier (org-scoped) → underlying Query → its latest_query_data, and builds the same {parameter_value: [widget_ids]} mapping as before.
  • Full backward compatibility: when the identifier is unset or unresolvable, it falls back to the legacy allowed_widgets_{dashboard_id} name lookup, so existing dashboards keep working with no migration.

The Redash Query and Dashboard schemas are left untouched — all new state lives on the Metr* tables, so upstream merges shouldn't conflict.

It also fixes a pre-existing bug: requesting a dashboard via its legacy slug URL (?legacy) used to pass a string into an integer-column filter and 500 with invalid input syntax for type integer. The decorator now resolves the integer id from the serialized result.

Code Strategy

  • Metr specific over upstream changes. query_identifier lives in new MetrQuery table rather than in Query itself, exactly mirroring how MetrDashboard.url_identifier works. This keeps upstream models clean and reuses an established pattern reviewers already know.
  • Resolution changed. get_allowed_widgets_info() was modified to have the new resolution strategy.
  • Org-scoping is enforced server-side the MetrQuery lookup is filtered by org_id preventing any cross-org leakage of widget mappings.
  • Usage of a dropdown on the dashboard side. The dashboard's allowed_widget_query_identifier is selected from a dropdown populated by a new org-scoped GET /api/queries/query_identifiers endpoint, so there's no server-side existence check in the main POST — mirroring how url_identifier is handled (dedicated /validate endpoint for the query side, main POST trusts client-picked values). Stale/missing identifiers degrade gracefully via the resolver fallback.
  • MetrQuery.query is a relationship attribute, so it shadows the default SQLAlchemy model .query proxy. Because of that, we use db.session.query(MetrQuery) for lookups to make it explicit that we are querying the MetrQuery model, not accessing the relationship field.

Extra notes:

  • This is backend-driven resolution of the mapping. The mapping is still applied client-side after a parameter is selected, because the active parameter selection is a client concern.
  • The @add_allowed_widgets_info decorator is still not applied to PublicDashboardResource; public dashboards are out of the scope.
  • A query must be saved before a query_identifier can be assigned. The identifier field is hidden entirely for unsaved queries and appears only once the query has been saved. Additionally, the identifier is write-once from the UI: once set it renders read-only (cleared/changed only via the API).

QA

How did you test it? Keep your future self in mind to help debug things later.

Loading
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.

4 participants