@growthub/cli 0.9.11 → 0.9.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +389 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +78 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +120 -24
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +433 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +58 -7
- package/package.json +1 -1
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
wrapWorkspaceTemplateExport
|
|
58
58
|
} from "@/lib/workspace-schema";
|
|
59
59
|
import { governedWorkspaceIntegrationCatalog } from "@/lib/domain/integrations";
|
|
60
|
+
import { listWorkspaceDataModelTables } from "@/lib/workspace-data-model";
|
|
60
61
|
|
|
61
62
|
const DEFAULT_CHART_TYPE = "bar-vertical";
|
|
62
63
|
const DEFAULT_FILTER_OP = "and";
|
|
@@ -65,6 +66,7 @@ const DEFAULT_SORT_DIRECTION = "asc";
|
|
|
65
66
|
const SUB_PANEL_ROOT = "root";
|
|
66
67
|
const MANAGED_INTEGRATION_SOURCE_TYPE = "managed-integrations";
|
|
67
68
|
const CUSTOM_API_SOURCE_TYPE = "custom-api-webhooks";
|
|
69
|
+
const DATA_MODEL_SOURCE_TYPE = "workspace-data-model";
|
|
68
70
|
|
|
69
71
|
const SOURCE_TYPE_OBJECTS = [
|
|
70
72
|
{
|
|
@@ -147,7 +149,7 @@ function generateId(prefix) {
|
|
|
147
149
|
function defaultTitleFor(kind) {
|
|
148
150
|
switch (kind) {
|
|
149
151
|
case "chart": return "Untitled chart";
|
|
150
|
-
case "view": return "
|
|
152
|
+
case "view": return "Untitled view";
|
|
151
153
|
case "iframe": return "Untitled iFrame";
|
|
152
154
|
case "rich-text": return "Untitled Rich Text";
|
|
153
155
|
default: return "Untitled widget";
|
|
@@ -641,6 +643,7 @@ function getChartStyle(widget) {
|
|
|
641
643
|
|
|
642
644
|
function summarizeSource(widget) {
|
|
643
645
|
const binding = widget?.config?.binding;
|
|
646
|
+
if (binding?.sourceType === DATA_MODEL_SOURCE_TYPE) return binding.source || widget?.config?.source || "Data Model object";
|
|
644
647
|
if (binding?.mode === "integration") {
|
|
645
648
|
const source = binding.source || "Integration";
|
|
646
649
|
if (binding.entityLabel) return `${source} · ${binding.entityLabel}`;
|
|
@@ -652,6 +655,7 @@ function summarizeSource(widget) {
|
|
|
652
655
|
}
|
|
653
656
|
|
|
654
657
|
function summarizeSourceType(binding) {
|
|
658
|
+
if (binding?.sourceType === DATA_MODEL_SOURCE_TYPE) return "Data Model";
|
|
655
659
|
if (binding?.sourceType === CUSTOM_API_SOURCE_TYPE) return "Custom APIs/Webhooks";
|
|
656
660
|
if (binding?.mode === "integration" || binding?.sourceType === MANAGED_INTEGRATION_SOURCE_TYPE) return "Managed Integrations";
|
|
657
661
|
return "Static data";
|
|
@@ -663,6 +667,27 @@ function resolveBindingSourceType(binding) {
|
|
|
663
667
|
return "static";
|
|
664
668
|
}
|
|
665
669
|
|
|
670
|
+
function resolveDataModelTable(dataModelTables, binding) {
|
|
671
|
+
if (binding?.sourceType !== DATA_MODEL_SOURCE_TYPE) return null;
|
|
672
|
+
const tables = Array.isArray(dataModelTables) ? dataModelTables : [];
|
|
673
|
+
return tables.find((table) => table.objectId === binding.objectId || table.id === binding.objectId || table.source === binding.source) || null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function resolveViewWidget(widget, dataModelTables) {
|
|
677
|
+
if (widget?.kind !== "view") return widget;
|
|
678
|
+
const table = resolveDataModelTable(dataModelTables, widget.config?.binding);
|
|
679
|
+
if (!table) return widget;
|
|
680
|
+
return {
|
|
681
|
+
...widget,
|
|
682
|
+
config: {
|
|
683
|
+
...(widget.config || {}),
|
|
684
|
+
source: table.source,
|
|
685
|
+
columns: table.columns,
|
|
686
|
+
rows: table.rows
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
666
691
|
function summarizeFields(widget) {
|
|
667
692
|
const total = getColumnList(widget).length;
|
|
668
693
|
const hidden = getHiddenColumnSet(widget).size;
|
|
@@ -1061,7 +1086,7 @@ function EntitySelector({ integration, entities, selectedEntityId, selectedEntit
|
|
|
1061
1086
|
</div>;
|
|
1062
1087
|
}
|
|
1063
1088
|
|
|
1064
|
-
function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
1089
|
+
function SourceSubPanel({ widget, integrations, dataModelTables, onChange, onBack }) {
|
|
1065
1090
|
const binding = widget.config?.binding || {};
|
|
1066
1091
|
const currentMode = binding.mode || (widget.kind === "view" ? "manual" : "json");
|
|
1067
1092
|
const activeSourceType = resolveBindingSourceType(binding);
|
|
@@ -1070,6 +1095,7 @@ function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
|
1070
1095
|
const hasConnectedSource = Boolean(
|
|
1071
1096
|
binding.integrationId ||
|
|
1072
1097
|
binding.endpointRef ||
|
|
1098
|
+
binding.sourceType === DATA_MODEL_SOURCE_TYPE ||
|
|
1073
1099
|
binding.sourceType === MANAGED_INTEGRATION_SOURCE_TYPE ||
|
|
1074
1100
|
binding.sourceType === CUSTOM_API_SOURCE_TYPE
|
|
1075
1101
|
);
|
|
@@ -1098,6 +1124,16 @@ function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
|
1098
1124
|
};
|
|
1099
1125
|
}, [integrations, laneFilter, query]);
|
|
1100
1126
|
|
|
1127
|
+
const availableDataObjects = useMemo(() => {
|
|
1128
|
+
const list = Array.isArray(dataModelTables) ? dataModelTables : [];
|
|
1129
|
+
const trimmed = query.trim().toLowerCase();
|
|
1130
|
+
return list.filter((table) => {
|
|
1131
|
+
if (table.storage !== "manual-object") return false;
|
|
1132
|
+
if (!trimmed) return true;
|
|
1133
|
+
return `${table.label} ${table.source}`.toLowerCase().includes(trimmed);
|
|
1134
|
+
});
|
|
1135
|
+
}, [dataModelTables, query]);
|
|
1136
|
+
|
|
1101
1137
|
const selectStatic = useCallback(() => {
|
|
1102
1138
|
if (!confirmSourceChange("Static rows")) return;
|
|
1103
1139
|
if (widget.kind === "chart") {
|
|
@@ -1105,12 +1141,34 @@ function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
|
1105
1141
|
} else {
|
|
1106
1142
|
onChange({
|
|
1107
1143
|
...widget.config,
|
|
1108
|
-
source: widget.config?.source || "
|
|
1109
|
-
binding:
|
|
1144
|
+
source: widget.config?.source || "Static rows",
|
|
1145
|
+
binding: { mode: "manual", source: "Static rows", rows: Array.isArray(widget.config?.rows) ? widget.config.rows : [] }
|
|
1110
1146
|
});
|
|
1111
1147
|
}
|
|
1112
1148
|
}, [confirmSourceChange, onChange, widget.config, widget.kind]);
|
|
1113
1149
|
|
|
1150
|
+
const selectDataModelObject = useCallback((table) => {
|
|
1151
|
+
if (!table || !confirmSourceChange(table.label)) return;
|
|
1152
|
+
onChange({
|
|
1153
|
+
...widget.config,
|
|
1154
|
+
source: table.source,
|
|
1155
|
+
columns: table.columns,
|
|
1156
|
+
rows: [],
|
|
1157
|
+
binding: {
|
|
1158
|
+
mode: "manual",
|
|
1159
|
+
source: table.source,
|
|
1160
|
+
sourceType: DATA_MODEL_SOURCE_TYPE,
|
|
1161
|
+
sourceAuthority: "workspace-config",
|
|
1162
|
+
objectId: table.objectId,
|
|
1163
|
+
rows: []
|
|
1164
|
+
},
|
|
1165
|
+
fieldSettings: {
|
|
1166
|
+
hidden: [],
|
|
1167
|
+
order: table.columns
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}, [confirmSourceChange, onChange, widget.config]);
|
|
1171
|
+
|
|
1114
1172
|
const selectCustomApi = useCallback(() => {
|
|
1115
1173
|
if (!confirmSourceChange("Custom APIs/Webhooks")) return;
|
|
1116
1174
|
onChange({
|
|
@@ -1238,6 +1296,27 @@ function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
|
1238
1296
|
{activeSourceType === "static" ? <span className="workspace-source-tick" aria-hidden="true"><Check size={16} strokeWidth={2.4} /></span> : null}
|
|
1239
1297
|
</button>
|
|
1240
1298
|
</div>
|
|
1299
|
+
{widget.kind === "view" ? <>
|
|
1300
|
+
<p className="workspace-panel-label">Data Model objects</p>
|
|
1301
|
+
<div className="workspace-source-list">
|
|
1302
|
+
{availableDataObjects.length ? availableDataObjects.map((table) => {
|
|
1303
|
+
const isActive = binding.sourceType === DATA_MODEL_SOURCE_TYPE && binding.objectId === table.objectId;
|
|
1304
|
+
return <button
|
|
1305
|
+
key={table.id}
|
|
1306
|
+
type="button"
|
|
1307
|
+
className={`workspace-source-row${isActive ? " active" : ""}`}
|
|
1308
|
+
onClick={() => selectDataModelObject(table)}
|
|
1309
|
+
>
|
|
1310
|
+
<span className="workspace-source-icon" aria-hidden="true"><Database size={15} /></span>
|
|
1311
|
+
<span className="workspace-source-meta">
|
|
1312
|
+
<strong>{table.label}</strong>
|
|
1313
|
+
<em>{table.columns.length} fields · {table.rows.length} records · workspace config</em>
|
|
1314
|
+
</span>
|
|
1315
|
+
{isActive ? <span className="workspace-source-tick" aria-hidden="true"><Check size={16} strokeWidth={2.4} /></span> : null}
|
|
1316
|
+
</button>;
|
|
1317
|
+
}) : <p className="workspace-entity-empty">No manual Data Model objects yet.</p>}
|
|
1318
|
+
</div>
|
|
1319
|
+
</> : null}
|
|
1241
1320
|
{Object.entries(groups).map(([lane, items]) => items.length ? <div key={lane}>
|
|
1242
1321
|
<p className="workspace-panel-label">{lane === "data-source" ? "Data Sources" : "Workspace Tools"}</p>
|
|
1243
1322
|
<div className="workspace-source-list">
|
|
@@ -1289,22 +1368,24 @@ function SourceSubPanel({ widget, integrations, onChange, onBack }) {
|
|
|
1289
1368
|
</section>;
|
|
1290
1369
|
}
|
|
1291
1370
|
|
|
1292
|
-
function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
1293
|
-
const
|
|
1294
|
-
const
|
|
1371
|
+
function FieldsSubPanel({ widget, dataModelTable, onChange, onBack }) {
|
|
1372
|
+
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
1373
|
+
const ordered = getOrderedColumns(viewWidget);
|
|
1374
|
+
const hidden = getHiddenColumnSet(viewWidget);
|
|
1295
1375
|
const visible = ordered.filter((name) => !hidden.has(name));
|
|
1296
1376
|
const hiddenList = ordered.filter((name) => hidden.has(name));
|
|
1297
1377
|
const [hiddenOpen, setHiddenOpen] = useState(true);
|
|
1298
1378
|
const [draftField, setDraftField] = useState("");
|
|
1299
1379
|
const move = (fieldId, direction) => {
|
|
1300
|
-
const next = reorderColumn(
|
|
1380
|
+
const next = reorderColumn(viewWidget, fieldId, direction);
|
|
1301
1381
|
onChange({ ...widget.config, fieldSettings: next });
|
|
1302
1382
|
};
|
|
1303
1383
|
const toggle = (fieldId) => {
|
|
1304
|
-
const next = toggleColumnHidden(
|
|
1384
|
+
const next = toggleColumnHidden(viewWidget, fieldId);
|
|
1305
1385
|
onChange({ ...widget.config, fieldSettings: next });
|
|
1306
1386
|
};
|
|
1307
1387
|
const removeColumn = (fieldId) => {
|
|
1388
|
+
if (dataModelTable) return;
|
|
1308
1389
|
const nextColumns = ordered.filter((name) => name !== fieldId);
|
|
1309
1390
|
const fs = widget.config?.fieldSettings || {};
|
|
1310
1391
|
onChange({
|
|
@@ -1317,6 +1398,7 @@ function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
|
1317
1398
|
});
|
|
1318
1399
|
};
|
|
1319
1400
|
const addColumn = () => {
|
|
1401
|
+
if (dataModelTable) return;
|
|
1320
1402
|
const trimmed = draftField.trim();
|
|
1321
1403
|
if (!trimmed || ordered.includes(trimmed)) return;
|
|
1322
1404
|
onChange({ ...widget.config, columns: [...ordered, trimmed] });
|
|
@@ -1324,6 +1406,7 @@ function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
|
1324
1406
|
};
|
|
1325
1407
|
return <section className="workspace-widget-subpanel">
|
|
1326
1408
|
<SubPanelHeader title="Fields" breadcrumb={widget.title} onBack={onBack} />
|
|
1409
|
+
{dataModelTable ? <p className="workspace-panel-hint">This View is bound to a Data Model object. Field order and visibility are widget-local; add or remove object fields on the Data Model page.</p> : null}
|
|
1327
1410
|
<p className="workspace-panel-label">Visible fields</p>
|
|
1328
1411
|
<div className="workspace-field-rows">
|
|
1329
1412
|
{visible.length === 0 ? <p className="workspace-panel-hint">No visible fields. Add one below or unhide an existing field.</p> : null}
|
|
@@ -1335,7 +1418,7 @@ function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
|
1335
1418
|
<button type="button" aria-label={`Move ${name} up`} disabled={index === 0} onClick={() => move(name, "up")}>↑</button>
|
|
1336
1419
|
<button type="button" aria-label={`Move ${name} down`} disabled={index === visible.length - 1} onClick={() => move(name, "down")}>↓</button>
|
|
1337
1420
|
<button type="button" aria-label={`Hide ${name}`} onClick={() => toggle(name)}>👁</button>
|
|
1338
|
-
<button type="button" aria-label={`Remove ${name}`} onClick={() => removeColumn(name)}>✕</button>
|
|
1421
|
+
<button type="button" aria-label={`Remove ${name}`} disabled={Boolean(dataModelTable)} onClick={() => removeColumn(name)}>✕</button>
|
|
1339
1422
|
</span>
|
|
1340
1423
|
</div>)}
|
|
1341
1424
|
</div>
|
|
@@ -1355,7 +1438,7 @@ function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
|
1355
1438
|
<span className="workspace-field-row-name">{name}</span>
|
|
1356
1439
|
<span className="workspace-field-row-actions">
|
|
1357
1440
|
<button type="button" aria-label={`Show ${name}`} onClick={() => toggle(name)}>👁</button>
|
|
1358
|
-
<button type="button" aria-label={`Remove ${name}`} onClick={() => removeColumn(name)}>✕</button>
|
|
1441
|
+
<button type="button" aria-label={`Remove ${name}`} disabled={Boolean(dataModelTable)} onClick={() => removeColumn(name)}>✕</button>
|
|
1359
1442
|
</span>
|
|
1360
1443
|
</div>)}
|
|
1361
1444
|
</div> : null}
|
|
@@ -1372,14 +1455,15 @@ function FieldsSubPanel({ widget, onChange, onBack }) {
|
|
|
1372
1455
|
}
|
|
1373
1456
|
}}
|
|
1374
1457
|
/>
|
|
1375
|
-
<button type="button" onClick={addColumn} disabled={!draftField.trim()}>Add</button>
|
|
1458
|
+
<button type="button" onClick={addColumn} disabled={Boolean(dataModelTable) || !draftField.trim()}>Add</button>
|
|
1376
1459
|
</div>
|
|
1377
1460
|
</section>;
|
|
1378
1461
|
}
|
|
1379
1462
|
|
|
1380
|
-
function SortSubPanel({ widget, onChange, onBack }) {
|
|
1463
|
+
function SortSubPanel({ widget, dataModelTable, onChange, onBack }) {
|
|
1464
|
+
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
1381
1465
|
const sort = getSortClauses(widget);
|
|
1382
|
-
const columns = getColumnList(
|
|
1466
|
+
const columns = getColumnList(viewWidget);
|
|
1383
1467
|
const updateSort = (next) => onChange({ ...widget.config, sort: next });
|
|
1384
1468
|
const addClause = () => {
|
|
1385
1469
|
const fieldId = columns[0] || "";
|
|
@@ -1423,12 +1507,13 @@ function SortSubPanel({ widget, onChange, onBack }) {
|
|
|
1423
1507
|
</section>;
|
|
1424
1508
|
}
|
|
1425
1509
|
|
|
1426
|
-
function FilterSubPanel({ widget, integrations, onChange, onBack }) {
|
|
1510
|
+
function FilterSubPanel({ widget, integrations, dataModelTable, onChange, onBack }) {
|
|
1511
|
+
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
1427
1512
|
const binding = widget.config?.binding || {};
|
|
1428
1513
|
const filter = getFilterConfig(widget);
|
|
1429
1514
|
const [entities, setEntities] = useState([]);
|
|
1430
1515
|
const [entitiesLoading, setEntitiesLoading] = useState(false);
|
|
1431
|
-
const fieldChoices = getFilterFieldChoices(
|
|
1516
|
+
const fieldChoices = getFilterFieldChoices(viewWidget, entities);
|
|
1432
1517
|
const columns = fieldChoices.map((field) => field.id);
|
|
1433
1518
|
const setFilter = (next) => onChange({ ...widget.config, filter: next });
|
|
1434
1519
|
const setOp = (op) => setFilter({ ...filter, op });
|
|
@@ -2000,7 +2085,7 @@ function WorkspaceSettingsPanel({ config, persistence, adapterConfig, integratio
|
|
|
2000
2085
|
Inspect-only. Sourced from <code>growthub.config.json</code> + <code>GET /api/workspace</code>.
|
|
2001
2086
|
Edit branding by updating <code>growthub.config.json</code> inside your governed fork.
|
|
2002
2087
|
The builder itself never holds tokens, never executes hosted workflows, and never bypasses the PATCH allowlist
|
|
2003
|
-
(<code>dashboards</code>, <code>widgetTypes</code>, <code>canvas</code>).
|
|
2088
|
+
(<code>dashboards</code>, <code>widgetTypes</code>, <code>canvas</code>, <code>dataModel</code>).
|
|
2004
2089
|
</p>
|
|
2005
2090
|
<div className="workspace-readiness">
|
|
2006
2091
|
<article className="workspace-readiness-section">
|
|
@@ -2081,7 +2166,7 @@ function WorkspaceManagementPanel({ config, persistence, adapterConfig, onClose
|
|
|
2081
2166
|
</article>
|
|
2082
2167
|
<article className="workspace-readiness-section">
|
|
2083
2168
|
<h3>API</h3>
|
|
2084
|
-
<div className="workspace-readiness-row"><span>PATCH allowlist</span><code>dashboards | widgetTypes | canvas</code></div>
|
|
2169
|
+
<div className="workspace-readiness-row"><span>PATCH allowlist</span><code>dashboards | widgetTypes | canvas | dataModel</code></div>
|
|
2085
2170
|
<div className="workspace-readiness-row"><span>Unknown field</span><code>400</code></div>
|
|
2086
2171
|
<div className="workspace-readiness-row"><span>Read-only runtime</span><code>409 + guidance</code></div>
|
|
2087
2172
|
<div className="workspace-readiness-row"><span>Can save now</span>
|
|
@@ -2175,6 +2260,8 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
2175
2260
|
const addSlot = dragPreview || selectedPosition;
|
|
2176
2261
|
const selectedWidget = activeWidgets.find((widget) => widget.id === selectedWidgetId) || null;
|
|
2177
2262
|
const availableIntegrations = useMemo(() => flattenIntegrationSettings(integrationSettings), [integrationSettings]);
|
|
2263
|
+
const dataModelTables = useMemo(() => listWorkspaceDataModelTables(config), [config]);
|
|
2264
|
+
const selectedResolvedWidget = selectedWidget ? resolveViewWidget(selectedWidget, dataModelTables) : null;
|
|
2178
2265
|
const branding = config.branding || {};
|
|
2179
2266
|
const occupiedCells = useMemo(() => {
|
|
2180
2267
|
const cells = new Set();
|
|
@@ -3055,6 +3142,7 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3055
3142
|
</div>
|
|
3056
3143
|
<nav className="workspace-nav">
|
|
3057
3144
|
<button type="button" className={workspaceView === "dashboards" ? "active workspace-nav-button" : "workspace-nav-button"} onClick={showDashboardHome}>Dashboards</button>
|
|
3145
|
+
<Link href="/data-model">Data Model</Link>
|
|
3058
3146
|
<Link href="/settings/integrations">Integrations</Link>
|
|
3059
3147
|
<button type="button" className="workspace-nav-button" onClick={() => setSettingsOpen(true)}>Workspace Settings</button>
|
|
3060
3148
|
<button type="button" className="workspace-nav-button" onClick={() => setManagementOpen(true)}>Management</button>
|
|
@@ -3246,7 +3334,7 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3246
3334
|
onSelect={() => selectWidget(widget.id)}
|
|
3247
3335
|
onExpandIframe={setExpandedIframeWidget}
|
|
3248
3336
|
selected={widget.id === selectedWidgetId}
|
|
3249
|
-
widget={widget}
|
|
3337
|
+
widget={resolveViewWidget(widget, dataModelTables)}
|
|
3250
3338
|
/>)}
|
|
3251
3339
|
</div>
|
|
3252
3340
|
</section> : null}
|
|
@@ -3292,22 +3380,26 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3292
3380
|
{selectedWidget && inspectorPath === "source" ? <SourceSubPanel
|
|
3293
3381
|
widget={selectedWidget}
|
|
3294
3382
|
integrations={availableIntegrations}
|
|
3383
|
+
dataModelTables={dataModelTables}
|
|
3295
3384
|
onChange={replaceSelectedWidgetConfig}
|
|
3296
3385
|
onBack={() => setInspectorPath(SUB_PANEL_ROOT)}
|
|
3297
3386
|
/> : null}
|
|
3298
3387
|
{selectedWidget && inspectorPath === "fields" ? <FieldsSubPanel
|
|
3299
3388
|
widget={selectedWidget}
|
|
3389
|
+
dataModelTable={resolveDataModelTable(dataModelTables, selectedWidget.config?.binding)}
|
|
3300
3390
|
onChange={replaceSelectedWidgetConfig}
|
|
3301
3391
|
onBack={() => setInspectorPath(SUB_PANEL_ROOT)}
|
|
3302
3392
|
/> : null}
|
|
3303
3393
|
{selectedWidget && inspectorPath === "sort" ? <SortSubPanel
|
|
3304
3394
|
widget={selectedWidget}
|
|
3395
|
+
dataModelTable={resolveDataModelTable(dataModelTables, selectedWidget.config?.binding)}
|
|
3305
3396
|
onChange={replaceSelectedWidgetConfig}
|
|
3306
3397
|
onBack={() => setInspectorPath(SUB_PANEL_ROOT)}
|
|
3307
3398
|
/> : null}
|
|
3308
3399
|
{selectedWidget && inspectorPath === "filter" ? <FilterSubPanel
|
|
3309
3400
|
widget={selectedWidget}
|
|
3310
3401
|
integrations={availableIntegrations}
|
|
3402
|
+
dataModelTable={resolveDataModelTable(dataModelTables, selectedWidget.config?.binding)}
|
|
3311
3403
|
onChange={replaceSelectedWidgetConfig}
|
|
3312
3404
|
onBack={() => setInspectorPath(SUB_PANEL_ROOT)}
|
|
3313
3405
|
/> : null}
|
|
@@ -3370,7 +3462,11 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3370
3462
|
</small>
|
|
3371
3463
|
</label> : null}
|
|
3372
3464
|
{selectedWidget.kind === "view" ? <section className="workspace-field-stack">
|
|
3373
|
-
<
|
|
3465
|
+
{selectedWidget.config?.binding?.sourceType === DATA_MODEL_SOURCE_TYPE ? <div className="workspace-active-source-state">
|
|
3466
|
+
<span>Data Model object</span>
|
|
3467
|
+
<strong>{summarizeSource(selectedWidget)}</strong>
|
|
3468
|
+
<code>{selectedWidget.config?.binding?.objectId || "workspace-config"}</code>
|
|
3469
|
+
</div> : <label>
|
|
3374
3470
|
<span>Manual Rows</span>
|
|
3375
3471
|
<textarea
|
|
3376
3472
|
value={serializeManualRows(selectedWidget.config?.rows || [], selectedWidget.config?.columns || [])}
|
|
@@ -3382,7 +3478,7 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3382
3478
|
});
|
|
3383
3479
|
}}
|
|
3384
3480
|
/>
|
|
3385
|
-
</label>
|
|
3481
|
+
</label>}
|
|
3386
3482
|
<div className="workspace-settings-list" role="group" aria-label="View widget settings">
|
|
3387
3483
|
<p className="workspace-panel-label">Settings</p>
|
|
3388
3484
|
<button type="button" className="workspace-settings-row" disabled>
|
|
@@ -3392,13 +3488,13 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3392
3488
|
<span>Source</span><code>{summarizeSource(selectedWidget)}</code>
|
|
3393
3489
|
</button>
|
|
3394
3490
|
<button type="button" className="workspace-settings-row" onClick={() => setInspectorPath("fields")}>
|
|
3395
|
-
<span>Fields</span><code>{summarizeFields(selectedWidget)}</code>
|
|
3491
|
+
<span>Fields</span><code>{summarizeFields(selectedResolvedWidget || selectedWidget)}</code>
|
|
3396
3492
|
</button>
|
|
3397
3493
|
<button type="button" className="workspace-settings-row" onClick={() => setInspectorPath("filter")}>
|
|
3398
|
-
<span>Filter</span><code>{summarizeFilter(selectedWidget)}</code>
|
|
3494
|
+
<span>Filter</span><code>{summarizeFilter(selectedResolvedWidget || selectedWidget)}</code>
|
|
3399
3495
|
</button>
|
|
3400
3496
|
<button type="button" className="workspace-settings-row" onClick={() => setInspectorPath("sort")}>
|
|
3401
|
-
<span>Sort</span><code>{summarizeSort(selectedWidget)}</code>
|
|
3497
|
+
<span>Sort</span><code>{summarizeSort(selectedResolvedWidget || selectedWidget)}</code>
|
|
3402
3498
|
</button>
|
|
3403
3499
|
</div>
|
|
3404
3500
|
</section> : null}
|
|
@@ -93,6 +93,7 @@ function applyPatch(currentConfig, patch) {
|
|
|
93
93
|
const next = { ...currentConfig };
|
|
94
94
|
if (patch.dashboards !== undefined) next.dashboards = patch.dashboards;
|
|
95
95
|
if (patch.widgetTypes !== undefined) next.widgetTypes = patch.widgetTypes;
|
|
96
|
+
if (patch.dataModel !== undefined) next.dataModel = patch.dataModel;
|
|
96
97
|
if (patch.canvas !== undefined && patch.canvas !== null) {
|
|
97
98
|
const patchCanvas = { ...patch.canvas };
|
|
98
99
|
if (Array.isArray(patchCanvas.tabs)) {
|
|
@@ -140,7 +141,8 @@ async function writeWorkspaceConfig(patch) {
|
|
|
140
141
|
validateWorkspaceConfig({
|
|
141
142
|
dashboards: next.dashboards,
|
|
142
143
|
widgetTypes: next.widgetTypes,
|
|
143
|
-
canvas: next.canvas
|
|
144
|
+
canvas: next.canvas,
|
|
145
|
+
dataModel: next.dataModel
|
|
144
146
|
});
|
|
145
147
|
const configPath = resolveWorkspaceConfigPath();
|
|
146
148
|
const expectedDir = path.resolve(/*turbopackIgnore: true*/ process.cwd());
|