Skip to content

Commit 073b390

Browse files
Copilothotlong
andcommitted
fix: move dashboard widget inline data into options to survive Zod validation
The @objectstack/spec DashboardWidgetSchema Zod schema strips unknown fields during defineStack() validation. The `data` field at widget level was not part of the schema and got removed, causing charts and tables to render with empty data. Moving data inside `options` (which uses z.unknown().optional()) preserves it through validation. Also updated DashboardRenderer to look for data in both widget.data and options.data for backward compatibility. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 8e3222b commit 073b390

4 files changed

Lines changed: 112 additions & 117 deletions

File tree

examples/crm/objectstack.config.ts

Lines changed: 55 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -198,40 +198,38 @@ export default defineStack({
198198
layout: { x: 0, y: 1, w: 3, h: 2 },
199199
options: {
200200
xField: 'month',
201-
yField: 'revenue'
201+
yField: 'revenue',
202+
data: {
203+
provider: 'value',
204+
items: [
205+
{ month: 'Jan', revenue: 155000 },
206+
{ month: 'Feb', revenue: 87000 },
207+
{ month: 'Mar', revenue: 48000 },
208+
{ month: 'Apr', revenue: 61000 },
209+
{ month: 'May', revenue: 55000 },
210+
{ month: 'Jun', revenue: 67000 },
211+
{ month: 'Jul', revenue: 72000 }
212+
]
213+
}
202214
},
203-
// @ts-ignore
204-
data: {
205-
provider: 'value',
206-
items: [
207-
{ month: 'Jan', revenue: 155000 },
208-
{ month: 'Feb', revenue: 87000 },
209-
{ month: 'Mar', revenue: 48000 },
210-
{ month: 'Apr', revenue: 61000 },
211-
{ month: 'May', revenue: 55000 },
212-
{ month: 'Jun', revenue: 67000 },
213-
{ month: 'Jul', revenue: 72000 }
214-
]
215-
}
216215
},
217216
{
218217
title: 'Lead Source',
219218
type: 'donut',
220219
layout: { x: 3, y: 1, w: 1, h: 2 },
221220
options: {
222221
xField: 'source',
223-
yField: 'value'
222+
yField: 'value',
223+
data: {
224+
provider: 'value',
225+
items: [
226+
{ source: 'Web', value: 2 },
227+
{ source: 'Referral', value: 1 },
228+
{ source: 'Partner', value: 1 },
229+
{ source: 'Existing Business', value: 3 }
230+
]
231+
}
224232
},
225-
// @ts-ignore
226-
data: {
227-
provider: 'value',
228-
items: [
229-
{ source: 'Web', value: 2 },
230-
{ source: 'Referral', value: 1 },
231-
{ source: 'Partner', value: 1 },
232-
{ source: 'Existing Business', value: 3 }
233-
]
234-
}
235233
},
236234

237235
// --- Row 3: More Charts ---
@@ -241,38 +239,36 @@ export default defineStack({
241239
layout: { x: 0, y: 3, w: 2, h: 2 },
242240
options: {
243241
xField: 'stage',
244-
yField: 'amount'
242+
yField: 'amount',
243+
data: {
244+
provider: 'value',
245+
items: [
246+
{ stage: 'Prospecting', amount: 250000 },
247+
{ stage: 'Qualification', amount: 35000 },
248+
{ stage: 'Proposal', amount: 85000 },
249+
{ stage: 'Negotiation', amount: 45000 },
250+
{ stage: 'Closed Won', amount: 225000 }
251+
]
252+
}
245253
},
246-
// @ts-ignore
247-
data: {
248-
provider: 'value',
249-
items: [
250-
{ stage: 'Prospecting', amount: 250000 },
251-
{ stage: 'Qualification', amount: 35000 },
252-
{ stage: 'Proposal', amount: 85000 },
253-
{ stage: 'Negotiation', amount: 45000 },
254-
{ stage: 'Closed Won', amount: 225000 }
255-
]
256-
}
257254
},
258255
{
259256
title: 'Top Products',
260257
type: 'bar',
261258
layout: { x: 2, y: 3, w: 2, h: 2 },
262259
options: {
263260
xField: 'name',
264-
yField: 'sales'
261+
yField: 'sales',
262+
data: {
263+
provider: 'value',
264+
items: [
265+
{ name: 'Workstation Pro Laptop', sales: 45000 },
266+
{ name: 'Implementation Service', sales: 32000 },
267+
{ name: 'Premium Support', sales: 21000 },
268+
{ name: 'Executive Mesh Chair', sales: 15000 }
269+
]
270+
}
265271
},
266-
// @ts-ignore
267-
data: {
268-
provider: 'value',
269-
items: [
270-
{ name: 'Workstation Pro Laptop', sales: 45000 },
271-
{ name: 'Implementation Service', sales: 32000 },
272-
{ name: 'Premimum Support', sales: 21000 },
273-
{ name: 'Executive Mesh Chair', sales: 15000 }
274-
]
275-
}
276272
},
277273

278274
// --- Row 4: Table ---
@@ -286,19 +282,18 @@ export default defineStack({
286282
{ header: 'Amount', accessorKey: 'amount' },
287283
{ header: 'Stage', accessorKey: 'stage' },
288284
{ header: 'Close Date', accessorKey: 'date' }
289-
]
285+
],
286+
data: {
287+
provider: 'value',
288+
items: [
289+
{ name: 'Berlin Automation Project', amount: '$250,000', stage: 'Prospecting', date: '2024-09-01' },
290+
{ name: 'ObjectStack Enterprise License', amount: '$150,000', stage: 'Closed Won', date: '2024-01-15' },
291+
{ name: 'London Annual Renewal', amount: '$85,000', stage: 'Proposal', date: '2024-05-15' },
292+
{ name: 'SF Tower Expansion', amount: '$75,000', stage: 'Closed Won', date: '2024-02-28' },
293+
{ name: 'Global Fin Q1 Upsell', amount: '$45,000', stage: 'Negotiation', date: '2024-03-30' }
294+
]
295+
}
290296
},
291-
// @ts-ignore
292-
data: {
293-
provider: 'value',
294-
items: [
295-
{ name: 'Berlin Automation Project', amount: '$250,000', stage: 'Prospecting', date: '2024-09-01' },
296-
{ name: 'ObjectStack Enterprise License', amount: '$150,000', stage: 'Closed Won', date: '2024-01-15' },
297-
{ name: 'London Annual Renewal', amount: '$85,000', stage: 'Proposal', date: '2024-05-15' },
298-
{ name: 'SF Tower Expansion', amount: '$75,000', stage: 'Closed Won', date: '2024-02-28' },
299-
{ name: 'Global Fin Q1 Upsell', amount: '$45,000', stage: 'Negotiation', date: '2024-03-30' }
300-
]
301-
}
302297
}
303298
]
304299
}

examples/kitchen-sink/objectstack.config.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,14 @@ export default defineStack({
120120
options: {
121121
xField: 'category',
122122
yField: 'count',
123-
},
124-
// @ts-ignore
125-
data: {
126-
provider: 'value',
127-
items: [
128-
{ category: 'Option A', count: 2 },
129-
{ category: 'Option B', count: 2 },
130-
{ category: 'Option C', count: 1 },
131-
],
123+
data: {
124+
provider: 'value',
125+
items: [
126+
{ category: 'Option A', count: 2 },
127+
{ category: 'Option B', count: 2 },
128+
{ category: 'Option C', count: 1 },
129+
],
130+
},
132131
},
133132
},
134133
{
@@ -138,17 +137,16 @@ export default defineStack({
138137
options: {
139138
xField: 'name',
140139
yField: 'amount',
141-
},
142-
// @ts-ignore
143-
data: {
144-
provider: 'value',
145-
items: [
146-
{ name: 'Alpha', amount: 1500 },
147-
{ name: 'Beta', amount: 3200 },
148-
{ name: 'Gamma', amount: 800 },
149-
{ name: 'Delta', amount: 5000 },
150-
{ name: 'Epsilon', amount: 12000 },
151-
],
140+
data: {
141+
provider: 'value',
142+
items: [
143+
{ name: 'Alpha', amount: 1500 },
144+
{ name: 'Beta', amount: 3200 },
145+
{ name: 'Gamma', amount: 800 },
146+
{ name: 'Delta', amount: 5000 },
147+
{ name: 'Epsilon', amount: 12000 },
148+
],
149+
},
152150
},
153151
},
154152

@@ -160,18 +158,17 @@ export default defineStack({
160158
options: {
161159
xField: 'month',
162160
yField: 'value',
163-
},
164-
// @ts-ignore
165-
data: {
166-
provider: 'value',
167-
items: [
168-
{ month: 'Jan', value: 3200 },
169-
{ month: 'Feb', value: 4500 },
170-
{ month: 'Mar', value: 4100 },
171-
{ month: 'Apr', value: 5800 },
172-
{ month: 'May', value: 6200 },
173-
{ month: 'Jun', value: 7500 },
174-
],
161+
data: {
162+
provider: 'value',
163+
items: [
164+
{ month: 'Jan', value: 3200 },
165+
{ month: 'Feb', value: 4500 },
166+
{ month: 'Mar', value: 4100 },
167+
{ month: 'Apr', value: 5800 },
168+
{ month: 'May', value: 6200 },
169+
{ month: 'Jun', value: 7500 },
170+
],
171+
},
175172
},
176173
},
177174
],

examples/todo/objectstack.config.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,16 @@ export default defineStack({
156156
options: {
157157
xField: 'status',
158158
yField: 'count',
159-
},
160-
// @ts-ignore
161-
data: {
162-
provider: 'value',
163-
items: [
164-
{ status: 'Backlog', count: 2 },
165-
{ status: 'Todo', count: 2 },
166-
{ status: 'In Progress', count: 4 },
167-
{ status: 'Review', count: 1 },
168-
{ status: 'Done', count: 3 },
169-
],
159+
data: {
160+
provider: 'value',
161+
items: [
162+
{ status: 'Backlog', count: 2 },
163+
{ status: 'Todo', count: 2 },
164+
{ status: 'In Progress', count: 4 },
165+
{ status: 'Review', count: 1 },
166+
{ status: 'Done', count: 3 },
167+
],
168+
},
170169
},
171170
},
172171
{
@@ -176,17 +175,16 @@ export default defineStack({
176175
options: {
177176
xField: 'category',
178177
yField: 'count',
179-
},
180-
// @ts-ignore
181-
data: {
182-
provider: 'value',
183-
items: [
184-
{ category: 'Feature', count: 4 },
185-
{ category: 'Bug', count: 3 },
186-
{ category: 'Documentation', count: 2 },
187-
{ category: 'Design', count: 1 },
188-
{ category: 'Chore', count: 2 },
189-
],
178+
data: {
179+
provider: 'value',
180+
items: [
181+
{ category: 'Feature', count: 4 },
182+
{ category: 'Bug', count: 3 },
183+
{ category: 'Documentation', count: 2 },
184+
{ category: 'Design', count: 1 },
185+
{ category: 'Chore', count: 2 },
186+
],
187+
},
190188
},
191189
},
192190
],

packages/plugin-dashboard/src/DashboardRenderer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
6868
// Handle Shorthand Registry Mappings
6969
const widgetType = (widget as any).type;
7070
if (widgetType === 'bar' || widgetType === 'line' || widgetType === 'area' || widgetType === 'pie' || widgetType === 'donut') {
71-
const dataItems = Array.isArray((widget as any).data) ? (widget as any).data : (widget as any).data?.items || [];
7271
const options = (widget as any).options || {};
72+
// Support data at widget level or nested inside options
73+
const widgetData = (widget as any).data || options.data;
74+
const dataItems = Array.isArray(widgetData) ? widgetData : widgetData?.items || [];
7375
const xAxisKey = options.xField || 'name';
7476
const yField = options.yField || 'value';
7577

@@ -85,10 +87,13 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
8587
}
8688

8789
if (widgetType === 'table') {
90+
const options = (widget as any).options || {};
91+
// Support data at widget level or nested inside options
92+
const widgetData = (widget as any).data || options.data;
8893
return {
8994
type: 'data-table',
90-
...(widget as any).options,
91-
data: (widget as any).data?.items || [],
95+
...options,
96+
data: widgetData?.items || [],
9297
searchable: false,
9398
pagination: false,
9499
className: "border-0"

0 commit comments

Comments
 (0)