Skip to content

ref(dashboards): Migrate useWidgetBuilderState to nuqs#115345

Open
ryan953 wants to merge 1 commit into
masterfrom
ryan953/nuqs-migrate-widget-builder
Open

ref(dashboards): Migrate useWidgetBuilderState to nuqs#115345
ryan953 wants to merge 1 commit into
masterfrom
ryan953/nuqs-migrate-widget-builder

Conversation

@ryan953
Copy link
Copy Markdown
Member

@ryan953 ryan953 commented May 11, 2026

Summary

  • Replaces 14 useQueryParamState calls in useWidgetBuilderState with a single useQueryStates call from nuqs
  • Removes UrlParamBatchProvider wrapper from WidgetBuilderProvider (nuqs handles throttled URL writes natively with throttleMs: 300)
  • Implements localOverrides state pattern to support the updateUrl: false option (local state buffer without URL sync)
  • Updates all 15 widget builder test files to work with the nuqs navigate format (URL string instead of object)

Test plan

  • All 279 widget builder tests pass (pnpm test-ci static/app/views/dashboards/widgetBuilder/)
  • TypeScript typecheck passes
  • ESLint passes

…e to nuqs

Replace 14 useQueryParamState calls in useWidgetBuilderState with a single
useQueryStates call from nuqs, removing the dependency on UrlParamBatchProvider.
Update all widget builder test files to match the nuqs navigate format.
@ryan953 ryan953 requested a review from a team as a code owner May 11, 2026 22:40
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

📊 Type Coverage Diff

Metric Before After Delta
Coverage 93.47% 93.47% ±0%
Typed 134,844 134,917 🟢 +73
Untyped 9,424 9,426 🔴 +2
🔍 2 new type safety issues introduced

Type assertions (as) (2 new)

File Line Detail
static/app/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState.tsx 434 as Partial<UrlState>{[key]: nuqsValue} as Partial<UrlState>
static/app/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState.tsx 484 as Sort[]v as Sort[]

This is informational only and does not block the PR.

Comment on lines +307 to +314
const parseAsColumns = createParser<Column[]>({
parse(value) {
return deserializeFields(value.split(','));
},
serialize(columns) {
return serializeFields(columns).join(',');
},
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The parse functions in parseAsColumns and parseAsLinkedDashboards incorrectly split URL parameters by commas, breaking the JSON structure of aliased fields and linked dashboards, leading to corrupted or lost state.
Severity: HIGH

Suggested Fix

Replace the naive value.split(',') logic with a more robust serialization and deserialization method. For example, serialize the entire array of items into a single JSON string with JSON.stringify() and deserialize it with JSON.parse(). This avoids issues with delimiters that might appear within the serialized items themselves.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
static/app/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState.tsx#L307-L314

Potential issue: The `parseAsColumns` and `parseAsLinkedDashboards` functions serialize
arrays of JSON objects by joining them with commas. However, the deserialization logic
splits the resulting string by commas. Since the JSON objects for aliased fields (e.g.,
`{"field":"count()","alias":"my_alias"}`) and linked dashboards contain internal commas,
the split fragments the JSON. This causes `JSON.parse` to fail. For aliased fields, this
results in corrupted column state. For linked dashboards, the entries are dropped
entirely, causing them to disappear when loading a widget's state from a URL.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 575fa58. Configure here.

const selectedAggregate = merged.selectedAggregate ?? undefined;
const thresholds = merged.thresholds ?? undefined;
const linkedDashboards = merged.linkedDashboards ?? undefined;
const axisRange = merged.axisRange ?? undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RELEASE sort default not applied after nuqs migration

Medium Severity

The condition sort?.length === 0 on line 785 no longer triggers because sort is now undefined instead of [] when absent. With nuqs, setSort([]) writes null to the URL, and the derived sort state returns undefined (line 534-536). Since undefined?.length === 0 evaluates to false, the default RELEASE sort is never applied when switching from a table with non-sortable fields to a timeseries chart. The test was updated to expect the broken behavior instead of fixing the condition. The fix is to check (!sort || sort.length === 0).

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 575fa58. Configure here.

serialize(values) {
return values.join(',');
},
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comma-based array serialization corrupts multi-argument function fields

High Severity

parseAsColumns and parseAsStringList use comma as the array separator via value.split(',') and .join(','). This breaks any field containing commas in its arguments, such as count_if(transaction.duration,equals,value), count_web_vitals(measurements.lcp, good), trace metric functions like sum(value,my.metric,counter,-), JSON-aliased fields, and linked dashboards. On page reload or URL sharing, the split produces garbage entries. The tests were modified to avoid comma-containing fields rather than fixing the parser.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 575fa58. Configure here.

@gggritso
Copy link
Copy Markdown
Member

Be extremely careful with this, please! The last time I did this it tripled the number of re-renders of the Widget Builder per every field update, which caused horrible performance problems. Do the React Profiler traces look okay with this change? If you're feeling brave we can merge and check the metrics of the duration of opening the builder, but I'd rather be sure ahead of time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants