Skip to content

Commit 907a91c

Browse files
committed
feat(spec): dashboard filter pairing — GlobalFilterSchema.name + DashboardWidgetSchema.filterBindings (#2501)
Land the two dashboard-level-filter properties the objectui runtime already ships (objectui#2576, follow-up tracked in objectui#2578): - GlobalFilterSchema.name — stable filter name used as the dashboard-variable key (page.<name> in widget expressions) and the key widgets reference in filterBindings; defaults to field. "dateRange" is reserved for the built-in dashboard date range. - DashboardWidgetSchema.filterBindings — per-widget map from a dashboard filter name to one of THIS widget's fields (string override / false opt-out / absent = default to the filter's own field). Purely additive; the metadata-admin dashboard inspector derives its form from this schema via z.toJSONSchema, so both properties surface there automatically once objectui bumps @objectstack/spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HG5LQnPbQbjAAyofghzz3Z
1 parent 55bc68d commit 907a91c

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
Dashboard-level filters spec pairing (framework#2501, objectui#2578) — land the
6+
two properties the objectui runtime already ships (objectui#2576) so the
7+
protocol and the renderer agree:
8+
9+
- **`GlobalFilterSchema.name`** (optional string) — stable filter name used as
10+
the dashboard-variable key (readable in widget expressions as `page.<name>`)
11+
and as the key widgets reference in `filterBindings`. Defaults to `field`;
12+
`"dateRange"` is reserved for the built-in dashboard date range.
13+
- **`DashboardWidgetSchema.filterBindings`** (optional
14+
`Record<string, string | false>`) — per-widget binding from a dashboard
15+
filter name to one of THIS widget's fields: a string re-targets the filter to
16+
that field, `false` opts the widget out, absent falls back to the filter's
17+
own `field`.
18+
19+
Purely additive — existing dashboards parse unchanged. The metadata-admin
20+
dashboard inspector (objectui `dashboard-schema.ts`) derives its form from this
21+
schema via `z.toJSONSchema`, so both properties surface there automatically
22+
once objectui picks up this spec version.

packages/spec/src/ui/dashboard.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,24 @@ describe('Dashboard presentation sub-schemas', () => {
143143
expect(f.scope).toBe('dashboard');
144144
expect(GlobalFilterOptionsFromSchema.parse({ object: 'user', valueField: 'id', labelField: 'name' }).object).toBe('user');
145145
});
146+
147+
it('GlobalFilterSchema.name — optional stable variable key (framework#2501)', () => {
148+
const named = GlobalFilterSchema.parse({ name: 'region', field: 'sales_region', type: 'select' });
149+
expect(named.name).toBe('region');
150+
// name stays optional — runtime defaults it to `field`.
151+
expect(GlobalFilterSchema.parse({ field: 'region' }).name).toBeUndefined();
152+
});
153+
154+
it('DashboardWidgetSchema.filterBindings — field override / opt-out (framework#2501)', () => {
155+
const w = DashboardWidgetSchema.parse({
156+
id: 'accounts_signed', type: 'line', dataset: 'accounts', values: ['count'],
157+
filterBindings: { dateRange: 'signed_at', region: 'sales_region', status: false },
158+
});
159+
expect(w.filterBindings).toEqual({ dateRange: 'signed_at', region: 'sales_region', status: false });
160+
// Only string (field name) or literal false are valid binding values.
161+
expect(() => DashboardWidgetSchema.parse({
162+
id: 'w_bad', type: 'metric', dataset: 'sales', values: ['revenue'],
163+
filterBindings: { region: true },
164+
})).toThrow();
165+
});
146166
});

packages/spec/src/ui/dashboard.zod.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ export const DashboardWidgetSchema = lazySchema(() => z.object({
184184
/** Widget specific options (colors, legend, etc.) */
185185
options: z.unknown().optional().describe('Widget specific configuration'),
186186

187+
/**
188+
* Per-widget bindings from a dashboard-level filter (referenced by its
189+
* `name`, or the reserved name `"dateRange"` for the built-in date range)
190+
* to one of THIS widget's fields (framework#2501):
191+
* - string → apply the filter to that field (e.g. `{ dateRange: 'signed_at' }`)
192+
* - false → opt this widget out of that filter
193+
* - absent → default binding: the filter's own `field`
194+
* (dateRange: `dateRange.field ?? 'created_at'`)
195+
*/
196+
filterBindings: z.record(z.string(), z.union([z.string(), z.literal(false)])).optional()
197+
.describe("Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out"),
198+
187199
/**
188200
* Rule ids of build diagnostics intentionally suppressed on this widget
189201
* (e.g. `'table-count-only'` when a single-row summary table is deliberate).
@@ -223,6 +235,15 @@ export const GlobalFilterOptionsFromSchema = lazySchema(() => z.object({
223235
* Defines a single global filter control for the dashboard filter bar.
224236
*/
225237
export const GlobalFilterSchema = lazySchema(() => z.object({
238+
/**
239+
* Stable filter name (framework#2501) — the dashboard-variable key under
240+
* which the filter's value is published (readable in widget expressions as
241+
* `page.<name>`) and the key widgets reference in `filterBindings`.
242+
* Defaults to `field`. The name `"dateRange"` is reserved for the built-in
243+
* dashboard date range.
244+
*/
245+
name: z.string().optional().describe('Stable filter name (variable key); defaults to field'),
246+
226247
/** Field name to filter on */
227248
field: z.string().describe('Field name to filter on'),
228249

0 commit comments

Comments
 (0)