feat(dashboards): support constant values and render modes for filters#2383
feat(dashboards): support constant values and render modes for filters#2383alex-fedotyev wants to merge 4 commits into
Conversation
🦋 Changeset detectedLatest commit: 4163d2c The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
E2E Test Results✅ All tests passed • 191 passed • 3 skipped • 1319s
Tests ran across 4 shards in parallel. |
1572113 to
6946b0a
Compare
🔴 Tier 4 — CriticalTouches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD. Why this tier:
Review process: Deep review from a domain expert. Synchronous walkthrough may be required. Stats
|
|
<!-- deep-review --> Deep Review🔴 P0/P1 -- must fix
🟡 P2 -- recommended
🔵 P3 nitpicks (12)
Reviewers (11): correctness, testing, maintainability, project-standards, security, learnings-researcher, api-contract, reliability, adversarial, kieran-typescript, julik-frontend-races, agent-native. Testing gaps:
|
E2E shard 2 was failing on three pre-existing dashboard tests because
`visibleFilters` in DashboardFilters.tsx was a new array reference every
render, churning useDashboardFilterValues' useQueries and leaving the
chip dropdown disabled while the values query never settled. Memoize
the array so a stable reference flows into the hook.
Deep review fixes:
- P0/P1: MCP hyperdx_save_dashboard inputSchema now exposes
savedFilterValues, paired with a usage example in the filters param
description so constant: true filters can actually lock to a value
via MCP. The body schema already accepts it; only the tool input
shape was missing.
- P2: DashboardFiltersModal snapshots initialDefaultValues via state
keyed on filter.id, so a background savedFilterValues refetch no
longer wipes in-progress edits in the default-value picker.
- P2: Schema-level coherence checks moved to common-utils via two
reusable refinements applied at the external API boundary:
refineDashboardFilterCoherence rejects readonly/hidden render modes
without constant: true; refineDashboardFiltersConstantSiblings
rejects two filter definitions sharing an expression that disagree
on constant. Internal callers (MCP, future migrations) get the
same protection through the shared schemas.
- P2: openapi.json + JSDoc no longer declare default: false /
default: "editable" on the new constant / renderMode property
schemas. The Zod schema is .optional() with no .default() so
generated SDKs would otherwise surface a default the wire format
never produces; the implied default now lives in the description
text instead.
- P2: page-level merge logic for "Save default" / URL hydration /
savedFilterValues upsert is extracted into dashboardFilterUtils
(normalizeExpression, buildConstantExpressionSet,
stripConstantsFromUrl, mergeConstantFiltersForSave,
upsertSavedDefault, removeSavedDefaultForExpression). The matrix
including bracket-notation expressions is covered by a new unit
test. useDashboardFilters and DBDashboardPage call the helpers.
- P2: bracket-notation constant filter is now covered in the
hook unit tests (SpanAttributes['k8s.pod.name'] resolves through
filterValues, blocks setFilterValue, and survives via
getFilterQueriesForSource).
P3 polish:
- handleRemoveFilter now strips the matching savedFilterValues entry
alongside the filter delete so deleting + recreating a filter on the
same expression doesn't silently re-lock to an orphaned value.
- applyFilterVisibility('editable') returns {} so the spread on save
no longer leaves constant: undefined / renderMode: undefined keys
in memory.
- defaultsChanged uses set equality so reordering the same values
doesn't fire a spurious save write.
- NEVER_USED_RANGE hoisted to module scope.
- FilterVisibility uses z.infer<typeof DashboardFilterRenderMode>
instead of a hand-rolled string union.
- as DashboardFilter cast dropped on filtersForQuery.
- usePresetDashboardFilters.handleSaveFilter widened to accept the
optional defaultValues arg so a future supportsConstantFilters=true
flip on presets doesn't silently drop the second arg.
- parseQuery import uses the @/searchFilters alias for consistency
with sibling files.
- dashboard?.filters dropped from the URL hydration effect's deps
(the initializedDashboard.current === dashboard.id guard already
prevents re-runs).
- HDX-4404 API round-trip test uses toEqual instead of toMatchObject
so unexpected extra fields fail the assertion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Pushed 21880e4 with the deep-review fix-pack plus the e2e shard-2 root cause. Summary: E2E shard 2 (3 failing tests) Three pre-existing dashboard tests ( Deep review
P3 nitpicks all addressed in the same commit:
|
Two fixes for #2383 CI: 1. DashboardFiltersModal default-value picker no longer issues a query when the resolved table name is empty. A metric source with no metricType picked yet resolves `getMetricTableName` to `source.from.tableName`, which is typically empty on a metric source. Firing the autocomplete in that state produces a malformed `DESCRIBE {db}.{}` (empty Identifier substitution) that the ClickHouse proxy rejects with a 500. The e2e shard 2 trace shows exactly this 500 firing during filter editing, which left the dashboard filter chip dropdowns disabled long enough to time out the click-the-option waits. Gating on `tableName` keeps the picker idle until the form is configured enough to name a real table. 2. The external API HDX-4404 round-trip test asserted `whereLanguage: 'sql'` on the response for filters whose input payload omits `whereLanguage`. The Zod schema is `.optional()` with no default, so the wire format is undefined-in / undefined-out. The previous expectation made the test fail by asserting a default the schema never emits. Dropped the field from those three filter expects and from the PUT-flip filter expect; added a comment so the omission is intentional.
|
Pushed 73d370d with two CI fixes after digging into the shard 2 trace: Integration test: E2E shard 2: trace.network shows a 500 on That Gated Local |
Deep Review🔴 P0/P1 -- must fix
🟡 P2 -- recommended
🔵 P3 nitpicks (12)
Pre-existing
Reviewers (10): correctness, testing, maintainability, project-standards, agent-native-reviewer, learnings-researcher, api-contract, kieran-typescript, performance, adversarial. Testing gaps:
|
…tion The DashboardFiltersModal's FilterDefaultValueSelect fires useDashboardFilterValues queries with a temporary 'new' filter ID. Since filterIds were computed inside queryFn and cached, the shared React Query cache entry had filterIds: [['new']]. When the chip's useDashboardFilterValues hook (same source/expression/dateRange, same queryKey) read from cache, filterValuesById.get(realUUID) returned undefined, leaving the chip's VirtualMultiSelect disabled indefinitely. Fix: compute filterIds outside queryFn (in a useMemo after useQueries returns) so they are derived from the current caller's filter list at render time instead of being frozen in the cache from whoever populated it first. Also add missing await to DashboardPage.clickFilterOption's serviceFilter.click() so the click is properly awaited before waiting for the dropdown option to appear.
Linear: HDX-4404.
Summary
DashboardFilterSchema:constant: boolean(locks the saved default; the viewer cannot clear it) andrenderMode: 'editable' | 'readonly' | 'hidden'(controls how the chip appears in the filter bar).(constant, renderMode)pair. The schema still admits all combinations so MCP and external API callers can express future variants.TagsInputso the author can set the locked value directly. Editable filters keep using the existing chip + Save default flow unchanged.FilterInput/FilterOpenAPI schemas and the MCPmcpDashboardFilterSchemacarry both new fields;openapi.jsonregenerated.useDashboardFilters) acceptssavedFilterValuesand overlays constant filter values on every read, regardless of URL state;setFilterValueis a no-op for constant expressions. Constant values are kept out of the URL on hydration so the lock doesn't leak into shared links, andhandleSaveQuerypreserves constant entries when saving so the editable Save default flow doesn't clobber them.?scope=...), inline operator/values on the filter definition, customer-facing docs in clickhouse-docs.Test plan
setFilterValueno-op, sibling editable filters still work,getFilterQueriesForSourcehonorsappliesToSourceIdsfor constant filters, hidden + constant resolves the saved value, missing saved value returns nothing.constant+renderModein every valid combination, rejects unknownrenderMode, rejectsnullfor either field.constantandrenderModeset; absent fields stay absent on read; unknownrenderModereturns 400.hyperdx_save_dashboardround-trip integration test: same coverage as the external API test.savedFilterValues).review/tier-4(touchespackages/api/src/routers/external-api/v2/dashboards.ts, expected per the implementation plan).Tagging as draft so the UI smoke can be done against this branch's build.