fix(dashboard,charts): resolve {current_user_id} in widget filters (framework #3574) - #2857
Merged
Merged
Conversation
…ramework #3574)
A dashboard widget filtered on `{current_user_id}` rendered `0`. The token
reached SQL as a literal, matched no row, and nothing was logged on the client
or the server — a silent zero that reads as "you have no work" rather than
"this filter did not resolve". The same token in a list-view filter resolved
correctly, so a user-scoped list and a user-scoped widget over the same data
disagreed.
There was no shared resolver. Three ad-hoc implementations had grown up
independently — ObjectView for list views, ObjectDataPage for URL filter
triples, NavigationRenderer for hrefs — each understanding only the filter shape
its own surface used. ObjectView's opened with
`if (!Array.isArray(filter)) return filter`, so it could not have been reused by
dashboard widgets even in principle: widget filters are MongoDB-style objects.
Widgets therefore got no resolution at all — DatasetWidget called
resolveDateMacros and nothing else, which is why `{today}` worked in a widget
and `{current_user_id}` silently did not.
- core: new utils/filter-tokens.ts with resolveContextTokens and
resolveFilterPlaceholders. The latter expands EVERY placeholder vocabulary in
one call and is what surfaces should use; resolving only some of them is the
whole defect. The walk handles arrays and plain objects uniformly, so one
resolver covers both platform filter shapes.
- react: new FilterScopeProvider / useFilterScope. The renderer packages
deliberately do not depend on @object-ui/auth, so the shell supplies the
session values. Separate from PredicateScopeContext, which is the expression
evaluation scope and carries no organization.
- plugin-dashboard / plugin-charts: all six widgets that previously resolved
date macros only now resolve both vocabularies — DatasetWidget,
ObjectMetricWidget, ObjectDataTable, ObjectPivotTable, and ObjectChart
(dataset-bound and inline paths). The chart's compareTo comparison filter gets
the session pass too, or the overlay series silently ignores the owner clause
the primary series honours.
- app-shell: ObjectView's local substituteFilterTokens and ObjectDataPage's
inline `=== '{current_user_id}'` ternary now delegate to the shared resolver,
so both also gain {current_org_id} and date macros. Two of the three ad-hoc
implementations are gone rather than joined by a fourth.
An unresolvable token is left intact rather than dropped: leaving it yields an
empty result, whereas dropping the clause would WIDEN the result set and show a
signed-out viewer everyone's data. It is no longer silent — the resolver warns,
naming the token, and suggests the intended spelling for known near-misses.
Verified end-to-end against a live app-todo stack: a metric widget filtered on
`{ owner: '{current_user_id}' }` read 3 against Total Tasks 8, matching the
three records assigned to the signed-in user.
Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runtime half of objectstack-ai/objectstack#3574. Authoring-time gate + spec vocabulary: objectstack-ai/objectstack#3594.
Why widgets never resolved the token
A dashboard widget filtered on
{current_user_id}rendered0— the token reached SQL as a literal, matched no row, and nothing was logged anywhere. The same token in a list-view filter resolved fine, so a user-scoped list and a user-scoped widget over the same data disagreed.There was no shared resolver. Three ad-hoc implementations had grown up independently, each understanding only the filter shape its own surface used:
ObjectView.substituteFilterTokensif (!Array.isArray(filter)) return filterObjectDataPageinline ternary[field, op, value]triplesNavigationRenderer.applyNavTemplateThat first one is the crux: widget filters are MongoDB-style objects, so
ObjectView's helper would have been a silent no-op even if someone had thought to call it. Widgets therefore got nothing —DatasetWidgetcalledresolveDateMacrosalone, which is precisely why{today}worked in a widget and{current_user_id}silently did not.Six widgets were affected, not one:
DatasetWidget,ObjectMetricWidget,ObjectDataTable,ObjectPivotTable,ObjectChart(dataset-bound and inline paths).What changed
@object-ui/core—utils/filter-tokens.ts:resolveContextTokensplusresolveFilterPlaceholders, which expands every placeholder vocabulary in one call. That combined entry point is the actual fix for the class of bug: resolving only some vocabularies is what happened here. The walk handles arrays and plain objects uniformly, so one resolver covers both platform filter shapes.@object-ui/react—FilterScopeProvider/useFilterScope. The renderer packages deliberately don't depend on@object-ui/auth, so the shell supplies the session values. Kept separate fromPredicateScopeContext, which is the expression evaluation scope and carries no organization — widening it for filter resolution would couple two unrelated contracts.plugin-dashboard/plugin-charts— all six widgets now resolve both vocabularies. The chart'scompareTocomparison filter gets the session pass too; otherwise the overlay series silently ignored the owner clause the primary series honoured.app-shell—ObjectViewandObjectDataPagenow delegate to the shared resolver, gaining{current_org_id}and date macros. Two of the three ad-hoc implementations are deleted rather than joined by a fourth.Behaviour on an unresolvable token
Left intact, not dropped. Leaving it yields an empty result; dropping the clause would widen the result set and show a signed-out viewer everyone's data. It is no longer silent — the resolver warns naming the token, and suggests the intended spelling for known near-misses (
{current_user},{user_id},{organization_id}), each of which is a correct spelling somewhere else in the platform.Verification
Live browser, real stack (app-todo on a fresh wasm-SQLite DB, console dev server on workspace packages): a metric widget filtered on
{ owner: '{current_user_id}' }reads 3 against Total Tasks 8, matching exactly the three records assigned to the signed-in user. Console clean, no errors.Regression test proven red.
DatasetWidget.filterTokens.test.tsxasserts on theruntimeFilteractually handed toqueryDataset— the seam the bug lived at. RevertingDatasetWidgettoresolveDateMacrosturns 4 of its 5 cases red.No regressions.
core+react+plugin-dashboard+plugin-charts: 1581 passing.app-shell: 1848 passing.tsc --noEmitclean on all five changed packages.🤖 Generated with Claude Code