Skip to content

Commit f4ed51a

Browse files
authored
Merge pull request #825 from objectstack-ai/copilot/fix-dashboard-widget-metadata
2 parents cbb6fb6 + 32e153e commit f4ed51a

File tree

5 files changed

+131
-1
lines changed

5 files changed

+131
-1
lines changed

ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
849849
- [x] **P2: App Branding**`logo`, `favicon`, `backgroundColor` fields on CRM app
850850
- [x] **P3: Pages** — Settings page (utility) and Getting Started page (onboarding)
851851
- [x] **P2: Spec Compliance Audit** — Fixed `variant: 'danger'``'destructive'` (4 actions), `columns: string``number` (33 form sections), added `type: 'dashboard'` to dashboard
852+
- [x] **P2: Dashboard Widget Spec Alignment** — Added `id`, `title`, `object`, `categoryField`, `valueField`, `aggregate` to all dashboard widgets across CRM, Todo, and Kitchen Sink examples (5 new spec-compliance tests)
852853
- [x] **P2: i18n (10 locales)** — Full CRM metadata translations for en, zh, ja, ko, de, fr, es, pt, ru, ar — objects, fields, fieldOptions, navigation, actions, views, formSections, dashboard, reports, pages (24 tests)
853854

854855
### Ecosystem & Marketplace

examples/crm/src/__tests__/crm-metadata.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,47 @@ describe('CRM Metadata Spec Compliance', () => {
175175
expect(widget.layout).toHaveProperty('h');
176176
}
177177
});
178+
179+
it('all widgets have unique id', () => {
180+
const ids = CrmDashboard.widgets.map((w) => w.id);
181+
for (const id of ids) {
182+
expect(typeof id).toBe('string');
183+
expect(id!.length).toBeGreaterThan(0);
184+
}
185+
expect(new Set(ids).size).toBe(ids.length);
186+
});
187+
188+
it('all widgets have title', () => {
189+
for (const widget of CrmDashboard.widgets) {
190+
expect(typeof widget.title).toBe('string');
191+
expect(widget.title!.length).toBeGreaterThan(0);
192+
}
193+
});
194+
195+
it('metric widgets have title matching options.label', () => {
196+
const metrics = CrmDashboard.widgets.filter((w) => w.type === 'metric');
197+
for (const widget of metrics) {
198+
const opts = widget.options as { label?: string };
199+
expect(widget.title).toBe(opts.label);
200+
}
201+
});
202+
203+
it('all widgets have object data binding', () => {
204+
for (const widget of CrmDashboard.widgets) {
205+
expect(typeof widget.object).toBe('string');
206+
expect(widget.object!.length).toBeGreaterThan(0);
207+
}
208+
});
209+
210+
it('chart widgets have categoryField, valueField, and aggregate', () => {
211+
const chartTypes = ['bar', 'area', 'donut', 'line', 'pie'];
212+
const charts = CrmDashboard.widgets.filter((w) => chartTypes.includes(w.type));
213+
for (const widget of charts) {
214+
expect(typeof widget.categoryField).toBe('string');
215+
expect(typeof widget.valueField).toBe('string');
216+
expect(typeof widget.aggregate).toBe('string');
217+
}
218+
});
178219
});
179220

180221
describe('Reports', () => {

examples/crm/src/dashboards/crm.dashboard.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ export const CrmDashboard = {
55
widgets: [
66
// --- KPI Row ---
77
{
8+
id: 'crm_total_revenue',
9+
title: 'Total Revenue',
810
type: 'metric' as const,
11+
object: 'opportunity',
912
layout: { x: 0, y: 0, w: 1, h: 1 },
1013
options: {
1114
label: 'Total Revenue',
@@ -15,7 +18,10 @@ export const CrmDashboard = {
1518
}
1619
},
1720
{
21+
id: 'crm_active_deals',
22+
title: 'Active Deals',
1823
type: 'metric' as const,
24+
object: 'opportunity',
1925
layout: { x: 1, y: 0, w: 1, h: 1 },
2026
options: {
2127
label: 'Active Deals',
@@ -25,7 +31,10 @@ export const CrmDashboard = {
2531
}
2632
},
2733
{
34+
id: 'crm_win_rate',
35+
title: 'Win Rate',
2836
type: 'metric' as const,
37+
object: 'opportunity',
2938
layout: { x: 2, y: 0, w: 1, h: 1 },
3039
options: {
3140
label: 'Win Rate',
@@ -35,7 +44,10 @@ export const CrmDashboard = {
3544
}
3645
},
3746
{
47+
id: 'crm_avg_deal_size',
48+
title: 'Avg Deal Size',
3849
type: 'metric' as const,
50+
object: 'opportunity',
3951
layout: { x: 3, y: 0, w: 1, h: 1 },
4052
options: {
4153
label: 'Avg Deal Size',
@@ -47,8 +59,13 @@ export const CrmDashboard = {
4759

4860
// --- Row 2: Charts ---
4961
{
62+
id: 'crm_revenue_trends',
5063
title: 'Revenue Trends',
51-
type: 'area' as const,
64+
type: 'area' as const,
65+
object: 'opportunity',
66+
categoryField: 'month',
67+
valueField: 'revenue',
68+
aggregate: 'sum',
5269
layout: { x: 0, y: 1, w: 3, h: 2 },
5370
options: {
5471
xField: 'month',
@@ -68,8 +85,13 @@ export const CrmDashboard = {
6885
},
6986
},
7087
{
88+
id: 'crm_lead_source',
7189
title: 'Lead Source',
7290
type: 'donut' as const,
91+
object: 'opportunity',
92+
categoryField: 'source',
93+
valueField: 'value',
94+
aggregate: 'count',
7395
layout: { x: 3, y: 1, w: 1, h: 2 },
7496
options: {
7597
xField: 'source',
@@ -88,8 +110,13 @@ export const CrmDashboard = {
88110

89111
// --- Row 3: More Charts ---
90112
{
113+
id: 'crm_pipeline_by_stage',
91114
title: 'Pipeline by Stage',
92115
type: 'bar' as const,
116+
object: 'opportunity',
117+
categoryField: 'stage',
118+
valueField: 'amount',
119+
aggregate: 'sum',
93120
layout: { x: 0, y: 3, w: 2, h: 2 },
94121
options: {
95122
xField: 'stage',
@@ -107,8 +134,13 @@ export const CrmDashboard = {
107134
},
108135
},
109136
{
137+
id: 'crm_top_products',
110138
title: 'Top Products',
111139
type: 'bar' as const,
140+
object: 'product',
141+
categoryField: 'name',
142+
valueField: 'sales',
143+
aggregate: 'sum',
112144
layout: { x: 2, y: 3, w: 2, h: 2 },
113145
options: {
114146
xField: 'name',
@@ -127,8 +159,10 @@ export const CrmDashboard = {
127159

128160
// --- Row 4: Table ---
129161
{
162+
id: 'crm_recent_opportunities',
130163
title: 'Recent Opportunities',
131164
type: 'table' as const,
165+
object: 'opportunity',
132166
layout: { x: 0, y: 5, w: 4, h: 2 },
133167
options: {
134168
columns: [
@@ -152,8 +186,13 @@ export const CrmDashboard = {
152186

153187
// --- Row 5: Dynamic KPI from Object Data ---
154188
{
189+
id: 'crm_revenue_by_account',
155190
title: 'Revenue by Account',
156191
type: 'bar' as const,
192+
object: 'opportunity',
193+
categoryField: 'account',
194+
valueField: 'total',
195+
aggregate: 'sum',
157196
layout: { x: 0, y: 7, w: 4, h: 2 },
158197
options: {
159198
xField: 'account',

examples/kitchen-sink/objectstack.config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ export default defineStack({
7373
widgets: [
7474
// --- KPI Row ---
7575
{
76+
id: 'ks_total_records',
77+
title: 'Total Records',
7678
type: 'metric',
79+
object: 'kitchen_sink',
7780
layout: { x: 0, y: 0, w: 1, h: 1 },
7881
options: {
7982
label: 'Total Records',
@@ -82,7 +85,10 @@ export default defineStack({
8285
},
8386
},
8487
{
88+
id: 'ks_active_items',
89+
title: 'Active Items',
8590
type: 'metric',
91+
object: 'kitchen_sink',
8692
layout: { x: 1, y: 0, w: 1, h: 1 },
8793
options: {
8894
label: 'Active Items',
@@ -92,7 +98,10 @@ export default defineStack({
9298
},
9399
},
94100
{
101+
id: 'ks_total_value',
102+
title: 'Total Value',
95103
type: 'metric',
104+
object: 'kitchen_sink',
96105
layout: { x: 2, y: 0, w: 1, h: 1 },
97106
options: {
98107
label: 'Total Value',
@@ -102,7 +111,10 @@ export default defineStack({
102111
},
103112
},
104113
{
114+
id: 'ks_avg_rating',
115+
title: 'Avg Rating',
105116
type: 'metric',
117+
object: 'kitchen_sink',
106118
layout: { x: 3, y: 0, w: 1, h: 1 },
107119
options: {
108120
label: 'Avg Rating',
@@ -114,8 +126,13 @@ export default defineStack({
114126

115127
// --- Charts Row ---
116128
{
129+
id: 'ks_records_by_category',
117130
title: 'Records by Category',
118131
type: 'donut',
132+
object: 'kitchen_sink',
133+
categoryField: 'category',
134+
valueField: 'count',
135+
aggregate: 'count',
119136
layout: { x: 0, y: 1, w: 2, h: 2 },
120137
options: {
121138
xField: 'category',
@@ -131,8 +148,13 @@ export default defineStack({
131148
},
132149
},
133150
{
151+
id: 'ks_value_distribution',
134152
title: 'Value Distribution',
135153
type: 'bar',
154+
object: 'kitchen_sink',
155+
categoryField: 'name',
156+
valueField: 'amount',
157+
aggregate: 'sum',
136158
layout: { x: 2, y: 1, w: 2, h: 2 },
137159
options: {
138160
xField: 'name',
@@ -152,8 +174,13 @@ export default defineStack({
152174

153175
// --- Trend Row ---
154176
{
177+
id: 'ks_monthly_trend',
155178
title: 'Monthly Trend',
156179
type: 'area',
180+
object: 'kitchen_sink',
181+
categoryField: 'month',
182+
valueField: 'value',
183+
aggregate: 'sum',
157184
layout: { x: 0, y: 3, w: 4, h: 2 },
158185
options: {
159186
xField: 'month',

examples/todo/objectstack.config.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ export default defineStack({
111111
label: 'Task Overview',
112112
widgets: [
113113
{
114+
id: 'todo_total_tasks',
115+
title: 'Total Tasks',
114116
type: 'metric',
117+
object: 'todo_task',
115118
layout: { x: 0, y: 0, w: 1, h: 1 },
116119
options: {
117120
label: 'Total Tasks',
@@ -120,7 +123,10 @@ export default defineStack({
120123
},
121124
},
122125
{
126+
id: 'todo_in_progress',
127+
title: 'In Progress',
123128
type: 'metric',
129+
object: 'todo_task',
124130
layout: { x: 1, y: 0, w: 1, h: 1 },
125131
options: {
126132
label: 'In Progress',
@@ -130,7 +136,10 @@ export default defineStack({
130136
},
131137
},
132138
{
139+
id: 'todo_completed',
140+
title: 'Completed',
133141
type: 'metric',
142+
object: 'todo_task',
134143
layout: { x: 2, y: 0, w: 1, h: 1 },
135144
options: {
136145
label: 'Completed',
@@ -140,7 +149,10 @@ export default defineStack({
140149
},
141150
},
142151
{
152+
id: 'todo_overdue',
153+
title: 'Overdue',
143154
type: 'metric',
155+
object: 'todo_task',
144156
layout: { x: 3, y: 0, w: 1, h: 1 },
145157
options: {
146158
label: 'Overdue',
@@ -150,8 +162,13 @@ export default defineStack({
150162
},
151163
},
152164
{
165+
id: 'todo_tasks_by_status',
153166
title: 'Tasks by Status',
154167
type: 'donut',
168+
object: 'todo_task',
169+
categoryField: 'status',
170+
valueField: 'count',
171+
aggregate: 'count',
155172
layout: { x: 0, y: 1, w: 2, h: 2 },
156173
options: {
157174
xField: 'status',
@@ -169,8 +186,13 @@ export default defineStack({
169186
},
170187
},
171188
{
189+
id: 'todo_tasks_by_category',
172190
title: 'Tasks by Category',
173191
type: 'bar',
192+
object: 'todo_task',
193+
categoryField: 'category',
194+
valueField: 'count',
195+
aggregate: 'count',
174196
layout: { x: 2, y: 1, w: 2, h: 2 },
175197
options: {
176198
xField: 'category',

0 commit comments

Comments
 (0)