Skip to content

Commit fc2e9b7

Browse files
authored
Merge pull request #1671 from objectstack-ai/adr-0021-phase2-dataset-migration
ADR-0021 Phase 2: dataset dual-form migration + reconciliation harness (#1658)
2 parents e7f8e56 + 7f2510c commit fc2e9b7

29 files changed

Lines changed: 1135 additions & 77 deletions

File tree

examples/app-crm/objectstack.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as objects from './src/objects/index.js';
66
import * as views from './src/views/index.js';
77
import * as apps from './src/apps/index.js';
88
import * as dashboards from './src/dashboards/index.js';
9+
import * as datasets from './src/datasets/index.js';
910
import * as reports from './src/reports/index.js';
1011
import * as pages from './src/pages/index.js';
1112
import * as actions from './src/actions/index.js';
@@ -98,6 +99,7 @@ export default defineStack({
9899
views: Object.values(views),
99100
pages: Object.values(pages),
100101
dashboards: Object.values(dashboards),
102+
datasets: Object.values(datasets),
101103
reports: Object.values(reports),
102104
actions: Object.values(actions),
103105
themes: [CrmLightTheme, CrmDarkTheme],

examples/app-crm/src/dashboards/pipeline.dashboard.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const PipelineDashboard: Dashboard = {
3838
aggregate: 'sum',
3939
valueField: 'amount',
4040
filter: { stage: { $nin: ['closed_won', 'closed_lost'] } },
41+
dataset: 'opportunity_metrics',
42+
values: ['total_amount'],
4143
options: { format: 'currency', currency: 'USD' },
4244
layout: { x: 0, y: 0, w: 4, h: 2 },
4345
},
@@ -57,6 +59,8 @@ export const PipelineDashboard: Dashboard = {
5759
},
5860
},
5961
compareTo: 'previousPeriod',
62+
dataset: 'opportunity_metrics',
63+
values: ['total_amount'],
6064
options: { format: 'currency', currency: 'USD' },
6165
layout: { x: 4, y: 0, w: 4, h: 2 },
6266
},
@@ -76,6 +80,8 @@ export const PipelineDashboard: Dashboard = {
7680
},
7781
},
7882
compareTo: 'previousYear',
83+
dataset: 'opportunity_metrics',
84+
values: ['avg_amount'],
7985
options: { format: 'currency', currency: 'USD' },
8086
layout: { x: 8, y: 0, w: 4, h: 2 },
8187
},
@@ -94,6 +100,9 @@ export const PipelineDashboard: Dashboard = {
94100
close_date: { $gte: '{1_years_ago}', $lte: '{today}' },
95101
},
96102
compareTo: 'previousYear',
103+
dataset: 'opportunity_metrics',
104+
dimensions: ['close_date'],
105+
values: ['opp_count'],
97106
layout: { x: 0, y: 2, w: 8, h: 4 },
98107
},
99108
{
@@ -111,6 +120,9 @@ export const PipelineDashboard: Dashboard = {
111120
},
112121
},
113122
compareTo: 'previousPeriod',
123+
dataset: 'opportunity_metrics',
124+
dimensions: ['stage'],
125+
values: ['opp_count'],
114126
layout: { x: 8, y: 2, w: 4, h: 4 },
115127
},
116128

@@ -125,6 +137,9 @@ export const PipelineDashboard: Dashboard = {
125137
valueField: 'amount',
126138
categoryField: 'stage',
127139
filter: { stage: { $nin: ['closed_won', 'closed_lost'] } },
140+
dataset: 'opportunity_metrics',
141+
dimensions: ['stage'],
142+
values: ['total_amount'],
128143
layout: { x: 0, y: 6, w: 6, h: 4 },
129144
},
130145
],
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
export { OpportunityDataset } from './opportunity.dataset.js';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineDataset } from '@objectstack/spec/ui';
4+
5+
/**
6+
* Opportunity analytics dataset (ADR-0021).
7+
*
8+
* The single semantic source of truth for pipeline metrics. The Pipeline
9+
* dashboard binds every widget to this dataset and selects measures BY NAME
10+
* (`total_amount`, `avg_amount`, `opp_count`) so "pipeline revenue" means the
11+
* same thing on every tile. Single-object dataset (no `include`).
12+
*/
13+
export const OpportunityDataset = defineDataset({
14+
name: 'opportunity_metrics',
15+
label: 'Opportunity Metrics',
16+
description: 'Semantic layer for sales-pipeline counts and amounts',
17+
object: 'crm_opportunity',
18+
19+
dimensions: [
20+
{ name: 'stage', label: 'Stage', field: 'stage', type: 'string' },
21+
{ name: 'close_date', label: 'Close Date', field: 'close_date', type: 'date' },
22+
],
23+
24+
measures: [
25+
{ name: 'opp_count', label: 'Opportunities', aggregate: 'count' },
26+
{ name: 'total_amount', label: 'Total Amount', aggregate: 'sum', field: 'amount', format: '$0,0' },
27+
{ name: 'avg_amount', label: 'Avg Deal Size', aggregate: 'avg', field: 'amount', format: '$0,0' },
28+
],
29+
});

examples/app-crm/src/reports/sales-by-stage.report.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import type { UI } from '@objectstack/spec';
44

55
/**
66
* Example report — total opportunity amount grouped by stage.
7+
*
8+
* ADR-0021 Phase 2: bound to the `opportunity_metrics` dataset (rows = stage,
9+
* values = total_amount) alongside the legacy inline query. The legacy form was
10+
* also corrected to actually group + sum (it previously listed rows flat despite
11+
* its "grouped by stage" label), so both forms compute the same number and the
12+
* reconciliation harness can verify them.
713
*/
814
export const SalesByStageReport: UI.Report = {
915
name: 'crm_sales_by_stage',
@@ -13,7 +19,10 @@ export const SalesByStageReport: UI.Report = {
1319
type: 'summary',
1420
columns: [
1521
{ field: 'stage', label: 'Stage' },
16-
{ field: 'name', label: 'Opportunity' },
17-
{ field: 'amount', label: 'Amount' },
22+
{ field: 'amount', label: 'Amount', aggregate: 'sum' },
1823
],
24+
groupingsDown: [{ field: 'stage', sortOrder: 'asc' }],
25+
dataset: 'opportunity_metrics',
26+
rows: ['stage'],
27+
values: ['total_amount'],
1928
};

examples/app-showcase/objectstack.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as objects from './src/objects/index.js';
88
import { TaskViews, ProjectViews } from './src/views/index.js';
99
import { ShowcaseApp } from './src/apps/index.js';
1010
import { ChartGalleryDashboard } from './src/dashboards/index.js';
11+
import { ShowcaseTaskDataset, ShowcaseProjectDataset } from './src/datasets/index.js';
1112
import { allReports } from './src/reports/index.js';
1213
import { allActions } from './src/actions/index.js';
1314
import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage } from './src/pages/index.js';
@@ -115,6 +116,7 @@ export default defineStack({
115116
views: [TaskViews, ProjectViews],
116117
pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage],
117118
dashboards: [ChartGalleryDashboard],
119+
datasets: [ShowcaseTaskDataset, ShowcaseProjectDataset],
118120
reports: allReports,
119121
actions: allActions,
120122
themes: allThemes,

examples/app-showcase/src/apps/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const ShowcaseApp = App.create({
3737
icon: 'chart-bar',
3838
children: [
3939
{ id: 'nav_charts', type: 'dashboard', dashboardName: 'showcase_chart_gallery', label: 'Chart Gallery', icon: 'layout-dashboard' },
40-
{ id: 'nav_report_tabular', type: 'report', reportName: 'showcase_task_list', label: 'Task List', icon: 'table' },
40+
{ id: 'nav_report_tabular', type: 'object', objectName: 'showcase_task', viewName: 'tabular', label: 'Task List', icon: 'table' },
4141
{ id: 'nav_report_summary', type: 'report', reportName: 'showcase_hours_by_status', label: 'Hours by Status', icon: 'sigma' },
4242
{ id: 'nav_report_matrix', type: 'report', reportName: 'showcase_status_priority_matrix', label: 'Status × Priority', icon: 'grid-3x3' },
4343
{ id: 'nav_report_joined', type: 'report', reportName: 'showcase_task_overview', label: 'Task Overview', icon: 'layers' },

0 commit comments

Comments
 (0)