@growthub/cli 0.13.6 → 0.13.8
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/helper/query/route.js +98 -34
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +106 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +189 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceContributionGraph.jsx +119 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +357 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +488 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensWalkthrough.jsx +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +437 -26
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +44 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +592 -41
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-lens/page.jsx +76 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +148 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +1559 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +3 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +24 -8
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +82 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +4 -4
- package/dist/index.js +5224 -5225
- package/package.json +1 -1
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
GitBranch,
|
|
30
30
|
Hash,
|
|
31
31
|
Home,
|
|
32
|
+
Hourglass,
|
|
32
33
|
Import,
|
|
33
34
|
Italic,
|
|
34
35
|
Layers,
|
|
@@ -58,6 +59,8 @@ import {
|
|
|
58
59
|
Trash2,
|
|
59
60
|
Type,
|
|
60
61
|
Users,
|
|
62
|
+
Eye,
|
|
63
|
+
Wrench,
|
|
61
64
|
X,
|
|
62
65
|
Zap,
|
|
63
66
|
} from "lucide-react";
|
|
@@ -85,8 +88,10 @@ import {
|
|
|
85
88
|
deriveWidgetDependencyContract
|
|
86
89
|
} from "@/lib/workspace-chart-values";
|
|
87
90
|
import { selectObjectFilterableFields, selectObjectSortableFields } from "@/lib/workspace-metadata-selectors";
|
|
91
|
+
import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
|
|
88
92
|
import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
|
|
89
93
|
import { WorkspaceRail } from "./workspace-rail.jsx";
|
|
94
|
+
import { WorkspaceActivationPanel } from "./components/WorkspaceActivationPanel.jsx";
|
|
90
95
|
|
|
91
96
|
// Workspace Metadata Graph V1 — typed dependency contracts.
|
|
92
97
|
// Used by sidecar dependency summaries; the existing chart hydration path
|
|
@@ -110,6 +115,7 @@ const DATA_MODEL_SOURCE_TYPE = "workspace-data-model";
|
|
|
110
115
|
const LIVE_SOURCE_TYPE = "workspace-source-records";
|
|
111
116
|
const TESTED_SOURCE_STATUSES = new Set(["connected", "approved", "ok", "success"]);
|
|
112
117
|
const HIDDEN_SANDBOX_OBJECT_IDS = new Set(["workspace-helper-sandbox"]);
|
|
118
|
+
const WORKSPACE_UI_CACHE_OBJECT_ID = "workspace-ui-cache";
|
|
113
119
|
|
|
114
120
|
const SOURCE_TYPE_OBJECTS = [
|
|
115
121
|
{
|
|
@@ -173,6 +179,26 @@ const CHART_TYPE_ICONS = {
|
|
|
173
179
|
|
|
174
180
|
const VISIBLE_CHART_TYPES = KNOWN_CHART_TYPES.filter((type) => type !== "line");
|
|
175
181
|
|
|
182
|
+
const TABLE_VIEW_TYPES = ["gantt", "board", "calendar", "timeline"];
|
|
183
|
+
const TABLE_VIEW_LABELS = {
|
|
184
|
+
gantt: "Gantt",
|
|
185
|
+
board: "Board",
|
|
186
|
+
calendar: "Calendar",
|
|
187
|
+
timeline: "Timeline"
|
|
188
|
+
};
|
|
189
|
+
const TABLE_VIEW_HELP = {
|
|
190
|
+
gantt: "Track dependencies and baselines",
|
|
191
|
+
board: "Track work in a Kanban view",
|
|
192
|
+
calendar: "Plan weekly or monthly work",
|
|
193
|
+
timeline: "Schedule work over time"
|
|
194
|
+
};
|
|
195
|
+
const TABLE_VIEW_ICONS = {
|
|
196
|
+
gantt: GitBranch,
|
|
197
|
+
board: Columns3,
|
|
198
|
+
calendar: Calendar,
|
|
199
|
+
timeline: Rows3
|
|
200
|
+
};
|
|
201
|
+
|
|
176
202
|
// User-facing labels for the Twenty-style Y-axis operation dropdown.
|
|
177
203
|
// Keys must stay in sync with `lib/workspace-chart-values.js#KNOWN_AGGREGATIONS`
|
|
178
204
|
// and the validator in `lib/workspace-schema.js`.
|
|
@@ -407,6 +433,44 @@ function getWorkflowSandboxObject(config) {
|
|
|
407
433
|
}) || null;
|
|
408
434
|
}
|
|
409
435
|
|
|
436
|
+
function getWorkspaceUiCache(config) {
|
|
437
|
+
const object = getDataModelObject(config, WORKSPACE_UI_CACHE_OBJECT_ID);
|
|
438
|
+
const row = Array.isArray(object?.rows) ? object.rows.find((entry) => entry?.id === "activation") : null;
|
|
439
|
+
return row && typeof row === "object" ? row : {};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function setWorkspaceUiCacheFlag(config, key, value) {
|
|
443
|
+
const dataModel = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
|
|
444
|
+
const objects = Array.isArray(dataModel.objects) ? dataModel.objects : [];
|
|
445
|
+
const cacheObject = objects.find((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID) || {
|
|
446
|
+
id: WORKSPACE_UI_CACHE_OBJECT_ID,
|
|
447
|
+
label: "Workspace UI Cache",
|
|
448
|
+
source: "Workspace UI Cache",
|
|
449
|
+
objectType: "custom",
|
|
450
|
+
icon: "Settings",
|
|
451
|
+
columns: ["id", key],
|
|
452
|
+
rows: [],
|
|
453
|
+
binding: { mode: "manual", source: "Workspace UI Cache" }
|
|
454
|
+
};
|
|
455
|
+
const columns = Array.from(new Set([...(Array.isArray(cacheObject.columns) ? cacheObject.columns : ["id"]), key]));
|
|
456
|
+
const rows = Array.isArray(cacheObject.rows) ? cacheObject.rows : [];
|
|
457
|
+
const hasActivationRow = rows.some((row) => row?.id === "activation");
|
|
458
|
+
const nextRows = hasActivationRow
|
|
459
|
+
? rows.map((row) => row?.id === "activation" ? { ...row, [key]: value } : row)
|
|
460
|
+
: [...rows, { id: "activation", [key]: value }];
|
|
461
|
+
const nextCacheObject = { ...cacheObject, columns, rows: nextRows };
|
|
462
|
+
const nextObjects = objects.some((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID)
|
|
463
|
+
? objects.map((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID ? nextCacheObject : object)
|
|
464
|
+
: [...objects, nextCacheObject];
|
|
465
|
+
return {
|
|
466
|
+
...config,
|
|
467
|
+
dataModel: {
|
|
468
|
+
...dataModel,
|
|
469
|
+
objects: nextObjects
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
410
474
|
function listBuilderWorkflowItems(config) {
|
|
411
475
|
const navFolders = getDataModelObject(config, "nav-folders");
|
|
412
476
|
const rows = Array.isArray(navFolders?.rows) ? navFolders.rows : [];
|
|
@@ -476,11 +540,76 @@ function updateWorkflowFolderItemInConfig(config, workflow, updater) {
|
|
|
476
540
|
}
|
|
477
541
|
|
|
478
542
|
function createBlankWorkflowSandboxRow(rowId, nowIso) {
|
|
543
|
+
const registryId = "growthub-workspace-smoke-api";
|
|
479
544
|
const draftGraph = JSON.stringify({
|
|
480
|
-
version: "
|
|
545
|
+
version: "1",
|
|
481
546
|
provider: "growthub-native",
|
|
482
|
-
nodes: [
|
|
483
|
-
|
|
547
|
+
nodes: [
|
|
548
|
+
{
|
|
549
|
+
id: "input",
|
|
550
|
+
type: "input",
|
|
551
|
+
label: "Input",
|
|
552
|
+
subtitle: "Manual or source payload",
|
|
553
|
+
config: { inputMode: "manual", samplePayload: {}, sourceType: "", sourceId: "", entityId: "", filterMode: "and", filters: [] }
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
id: "api-request",
|
|
557
|
+
type: "api-registry-call",
|
|
558
|
+
label: "API Registry",
|
|
559
|
+
subtitle: `${registryId} · GET /api/workspace`,
|
|
560
|
+
config: {
|
|
561
|
+
registryId,
|
|
562
|
+
integrationId: registryId,
|
|
563
|
+
baseUrl: "http://localhost:3000",
|
|
564
|
+
endpoint: "/api/workspace",
|
|
565
|
+
method: "GET",
|
|
566
|
+
authRef: "",
|
|
567
|
+
queryParams: {},
|
|
568
|
+
bodyTemplate: "",
|
|
569
|
+
requestHeadersMetadata: { authHeaderName: "x-api-key", authPrefix: "", contentType: "" },
|
|
570
|
+
timeoutMs: 30000
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "transform",
|
|
575
|
+
type: "transform-filter",
|
|
576
|
+
label: "Transform",
|
|
577
|
+
subtitle: "Map fields and filter rows",
|
|
578
|
+
config: {
|
|
579
|
+
rootPath: "",
|
|
580
|
+
mode: "json",
|
|
581
|
+
responseMode: "json",
|
|
582
|
+
fieldMap: {},
|
|
583
|
+
includeFields: [],
|
|
584
|
+
excludeFields: [],
|
|
585
|
+
computedFields: {},
|
|
586
|
+
filters: [],
|
|
587
|
+
filterMode: "and",
|
|
588
|
+
maxRows: 0
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: "result",
|
|
593
|
+
type: "tool-result",
|
|
594
|
+
label: "Result",
|
|
595
|
+
subtitle: "Save status and response",
|
|
596
|
+
config: {
|
|
597
|
+
successStatusCodes: [200],
|
|
598
|
+
writeLastResponse: true,
|
|
599
|
+
writeSourceRecord: true,
|
|
600
|
+
sourceRecordId: "",
|
|
601
|
+
outputMode: "normalized-json",
|
|
602
|
+
previewFields: [],
|
|
603
|
+
statusField: "status",
|
|
604
|
+
lastTestedField: "lastTested"
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
],
|
|
608
|
+
edges: [
|
|
609
|
+
{ from: "input", to: "api-request", passes: "payload, filters, variables" },
|
|
610
|
+
{ from: "api-request", to: "transform", passes: "provider-response" },
|
|
611
|
+
{ from: "transform", to: "result", passes: "normalized-output" }
|
|
612
|
+
]
|
|
484
613
|
}, null, 2);
|
|
485
614
|
return {
|
|
486
615
|
Name: rowId,
|
|
@@ -520,11 +649,77 @@ function createBlankWorkflowSandboxRow(rowId, nowIso) {
|
|
|
520
649
|
};
|
|
521
650
|
}
|
|
522
651
|
|
|
652
|
+
function createWorkflowApiRegistryObject() {
|
|
653
|
+
const preset = OBJECT_TYPE_PRESETS["api-registry"] || {};
|
|
654
|
+
const columns = Array.isArray(preset.columns) ? [...preset.columns] : ["integrationId"];
|
|
655
|
+
return {
|
|
656
|
+
id: "workflow-api-registry",
|
|
657
|
+
label: preset.label || "API Registry",
|
|
658
|
+
source: preset.label || "API Registry",
|
|
659
|
+
objectType: "api-registry",
|
|
660
|
+
icon: preset.icon || "Code2",
|
|
661
|
+
columns,
|
|
662
|
+
rows: [
|
|
663
|
+
{
|
|
664
|
+
integrationId: "growthub-workspace-smoke-api",
|
|
665
|
+
authRef: "",
|
|
666
|
+
baseUrl: "http://localhost:3000",
|
|
667
|
+
endpoint: "/api/workspace",
|
|
668
|
+
method: "GET",
|
|
669
|
+
status: "draft",
|
|
670
|
+
lastTested: "",
|
|
671
|
+
lastResponse: "",
|
|
672
|
+
entityTypes: "workspace",
|
|
673
|
+
description: "Local workspace smoke endpoint for first workflow setup.",
|
|
674
|
+
connectorKind: "custom-http",
|
|
675
|
+
resolverTemplateId: "",
|
|
676
|
+
schemaVersion: "1",
|
|
677
|
+
capabilities: "read",
|
|
678
|
+
executionLane: "sandbox-local"
|
|
679
|
+
}
|
|
680
|
+
],
|
|
681
|
+
binding: { mode: "manual", source: "Data Model" },
|
|
682
|
+
relations: Array.isArray(preset.relations) ? preset.relations.map((relation) => ({ ...relation })) : [],
|
|
683
|
+
fieldSettings: { hidden: [], order: columns }
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function createWorkflowSandboxObject() {
|
|
688
|
+
const preset = OBJECT_TYPE_PRESETS["sandbox-environment"] || {};
|
|
689
|
+
const columns = Array.isArray(preset.columns) ? [...preset.columns] : ["Name"];
|
|
690
|
+
return {
|
|
691
|
+
id: "sandbox-environments",
|
|
692
|
+
label: preset.label || "Sandbox Environments",
|
|
693
|
+
source: preset.label || "Sandbox Environments",
|
|
694
|
+
objectType: "sandbox-environment",
|
|
695
|
+
icon: preset.icon || "Terminal",
|
|
696
|
+
columns,
|
|
697
|
+
rows: [],
|
|
698
|
+
binding: { mode: "manual", source: "Data Model" },
|
|
699
|
+
relations: Array.isArray(preset.relations) ? preset.relations.map((relation) => ({ ...relation })) : [],
|
|
700
|
+
fieldSettings: { hidden: [], order: columns }
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
523
704
|
function addWorkflowFolderShortcut(dataModel, workflow) {
|
|
524
705
|
const objects = Array.isArray(dataModel?.objects) ? dataModel.objects : [];
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
706
|
+
const seededObjects = objects.some((object) => object?.id === "nav-folders")
|
|
707
|
+
? objects
|
|
708
|
+
: [
|
|
709
|
+
...objects,
|
|
710
|
+
{
|
|
711
|
+
id: "nav-folders",
|
|
712
|
+
label: "Custom Folders",
|
|
713
|
+
source: "Custom Folders",
|
|
714
|
+
objectType: "custom",
|
|
715
|
+
icon: "Folder",
|
|
716
|
+
columns: ["name", "order", "collapsed", "items"],
|
|
717
|
+
rows: [],
|
|
718
|
+
binding: { mode: "manual", source: "Custom Folders" }
|
|
719
|
+
}
|
|
720
|
+
];
|
|
721
|
+
const navIndex = seededObjects.findIndex((object) => object?.id === "nav-folders");
|
|
722
|
+
const navObject = seededObjects[navIndex];
|
|
528
723
|
const rows = Array.isArray(navObject.rows) ? navObject.rows : [];
|
|
529
724
|
const folderName = "Builder";
|
|
530
725
|
const existingFolder = rows.find((row) => String(row?.name || "").trim().toLowerCase() === folderName.toLowerCase());
|
|
@@ -562,7 +757,7 @@ function addWorkflowFolderShortcut(dataModel, workflow) {
|
|
|
562
757
|
];
|
|
563
758
|
return {
|
|
564
759
|
...dataModel,
|
|
565
|
-
objects:
|
|
760
|
+
objects: seededObjects.map((object, index) => index === navIndex ? { ...navObject, rows: nextRows } : object)
|
|
566
761
|
};
|
|
567
762
|
}
|
|
568
763
|
|
|
@@ -939,6 +1134,43 @@ function getVisibleColumns(widget) {
|
|
|
939
1134
|
return ordered.filter((name) => !hidden.has(name));
|
|
940
1135
|
}
|
|
941
1136
|
|
|
1137
|
+
function getTableViewSettings(widget) {
|
|
1138
|
+
const settings = widget?.config?.fieldSettings?.tableView;
|
|
1139
|
+
return isPlainConfigObject(settings) ? settings : {};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function getTableViewType(widget) {
|
|
1143
|
+
const type = getTableViewSettings(widget).type;
|
|
1144
|
+
return TABLE_VIEW_TYPES.includes(type) ? type : "";
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function getFieldValue(row, field, fallback = "") {
|
|
1148
|
+
if (!row || !field) return fallback;
|
|
1149
|
+
const value = row[field];
|
|
1150
|
+
if (value === null || value === undefined || value === "") return fallback;
|
|
1151
|
+
return String(value);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function firstMatchingField(columns, candidates) {
|
|
1155
|
+
const lower = columns.map((column) => [column, String(column).toLowerCase()]);
|
|
1156
|
+
for (const candidate of candidates) {
|
|
1157
|
+
const found = lower.find(([, name]) => name.includes(candidate));
|
|
1158
|
+
if (found) return found[0];
|
|
1159
|
+
}
|
|
1160
|
+
return columns[0] || "";
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function resolveTableViewFields(widget) {
|
|
1164
|
+
const columns = getVisibleColumns(widget);
|
|
1165
|
+
const settings = getTableViewSettings(widget);
|
|
1166
|
+
return {
|
|
1167
|
+
titleField: settings.titleField || firstMatchingField(columns, ["title", "name", "task", "project"]),
|
|
1168
|
+
statusField: settings.statusField || firstMatchingField(columns, ["status", "stage", "state", "lane"]),
|
|
1169
|
+
startDateField: settings.startDateField || firstMatchingField(columns, ["start", "created", "date"]),
|
|
1170
|
+
endDateField: settings.endDateField || firstMatchingField(columns, ["end", "due", "deadline", "target"]),
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
942
1174
|
function withFieldSettings(config, patch) {
|
|
943
1175
|
const current = isPlainConfigObject(config?.fieldSettings) ? config.fieldSettings : { hidden: [], order: [] };
|
|
944
1176
|
return {
|
|
@@ -951,6 +1183,21 @@ function withFieldSettings(config, patch) {
|
|
|
951
1183
|
};
|
|
952
1184
|
}
|
|
953
1185
|
|
|
1186
|
+
function withTableViewSettings(config, patch) {
|
|
1187
|
+
const current = isPlainConfigObject(config?.fieldSettings) ? config.fieldSettings : {};
|
|
1188
|
+
const currentTableView = isPlainConfigObject(current.tableView) ? current.tableView : {};
|
|
1189
|
+
return {
|
|
1190
|
+
...config,
|
|
1191
|
+
fieldSettings: {
|
|
1192
|
+
...current,
|
|
1193
|
+
tableView: {
|
|
1194
|
+
...currentTableView,
|
|
1195
|
+
...patch
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
954
1201
|
function isPlainConfigObject(value) {
|
|
955
1202
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
956
1203
|
}
|
|
@@ -1115,6 +1362,10 @@ function summarizeFields(widget) {
|
|
|
1115
1362
|
return hidden ? `${total - hidden} of ${total} shown` : `${total} shown`;
|
|
1116
1363
|
}
|
|
1117
1364
|
|
|
1365
|
+
function summarizeTableView(widget) {
|
|
1366
|
+
return TABLE_VIEW_LABELS[getTableViewType(widget)] || "Table";
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1118
1369
|
function summarizeSort(widget) {
|
|
1119
1370
|
const sort = getSortClauses(widget);
|
|
1120
1371
|
if (!sort.length) return "›";
|
|
@@ -2633,6 +2884,105 @@ function SortSubPanel({ widget, dataModelTable, onChange, onBack }) {
|
|
|
2633
2884
|
</section>;
|
|
2634
2885
|
}
|
|
2635
2886
|
|
|
2887
|
+
function TableViewConfig({ widget, dataModelTable, onChange, onSubPage }) {
|
|
2888
|
+
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
2889
|
+
const columns = getVisibleColumns(viewWidget);
|
|
2890
|
+
const tableView = getTableViewSettings(widget);
|
|
2891
|
+
const activeType = getTableViewType(widget);
|
|
2892
|
+
const setTableView = (patch) => onChange(withTableViewSettings(widget.config, patch));
|
|
2893
|
+
const fieldOptions = columns.length ? columns : getColumnList(viewWidget);
|
|
2894
|
+
return (
|
|
2895
|
+
<div className="workspace-twenty-config workspace-table-view-config" role="group" aria-label="Table view widget settings">
|
|
2896
|
+
<p className="workspace-panel-label">Settings</p>
|
|
2897
|
+
<WidgetSettingsRow icon={Table2} label="Layout" value={summarizeTableView(widget)} disabled />
|
|
2898
|
+
<div className="workspace-chart-type-tabs workspace-table-view-tabs" role="tablist" aria-label="Table view type">
|
|
2899
|
+
{TABLE_VIEW_TYPES.map((type) => {
|
|
2900
|
+
const TypeIcon = TABLE_VIEW_ICONS[type];
|
|
2901
|
+
return (
|
|
2902
|
+
<button
|
|
2903
|
+
key={type}
|
|
2904
|
+
type="button"
|
|
2905
|
+
role="tab"
|
|
2906
|
+
aria-selected={activeType === type}
|
|
2907
|
+
className={activeType === type ? "active" : ""}
|
|
2908
|
+
onClick={() => setTableView({ type: activeType === type ? "" : type })}
|
|
2909
|
+
title={TABLE_VIEW_HELP[type]}
|
|
2910
|
+
>
|
|
2911
|
+
<IconGlyph icon={TypeIcon} size={17} />
|
|
2912
|
+
<em>{TABLE_VIEW_LABELS[type]}</em>
|
|
2913
|
+
</button>
|
|
2914
|
+
);
|
|
2915
|
+
})}
|
|
2916
|
+
</div>
|
|
2917
|
+
{activeType ? <p className="workspace-panel-hint">{TABLE_VIEW_HELP[activeType]}</p> : null}
|
|
2918
|
+
|
|
2919
|
+
<WidgetSettingsRow icon={Box} label="Source" value={summarizeSource(widget)} onClick={() => onSubPage("source")} />
|
|
2920
|
+
<WidgetSettingsRow icon={List} label="Fields" value={summarizeFields(viewWidget)} onClick={() => onSubPage("fields")} />
|
|
2921
|
+
<WidgetSettingsRow icon={Filter} label="Filter" value={summarizeFilter(viewWidget)} onClick={() => onSubPage("filter")} />
|
|
2922
|
+
<WidgetSettingsRow icon={SlidersHorizontal} label="Sort" value={summarizeSort(viewWidget)} onClick={() => onSubPage("sort")} />
|
|
2923
|
+
|
|
2924
|
+
{activeType ? (
|
|
2925
|
+
<>
|
|
2926
|
+
<p className="workspace-panel-label">View fields</p>
|
|
2927
|
+
<WidgetSelectRow icon={Type} label="Title">
|
|
2928
|
+
<FieldDropdown
|
|
2929
|
+
fields={fieldOptions}
|
|
2930
|
+
value={tableView.titleField || ""}
|
|
2931
|
+
onChange={(field) => setTableView({ titleField: field })}
|
|
2932
|
+
placeholder="Auto"
|
|
2933
|
+
disabled={!fieldOptions.length}
|
|
2934
|
+
/>
|
|
2935
|
+
</WidgetSelectRow>
|
|
2936
|
+
{activeType === "board" ? (
|
|
2937
|
+
<WidgetSelectRow icon={Columns3} label="Status">
|
|
2938
|
+
<FieldDropdown
|
|
2939
|
+
fields={fieldOptions}
|
|
2940
|
+
value={tableView.statusField || ""}
|
|
2941
|
+
onChange={(field) => setTableView({ statusField: field })}
|
|
2942
|
+
placeholder="Auto"
|
|
2943
|
+
disabled={!fieldOptions.length}
|
|
2944
|
+
/>
|
|
2945
|
+
</WidgetSelectRow>
|
|
2946
|
+
) : null}
|
|
2947
|
+
{activeType === "gantt" || activeType === "timeline" ? (
|
|
2948
|
+
<>
|
|
2949
|
+
<WidgetSelectRow icon={Calendar} label="Start">
|
|
2950
|
+
<FieldDropdown
|
|
2951
|
+
fields={fieldOptions}
|
|
2952
|
+
value={tableView.startDateField || ""}
|
|
2953
|
+
onChange={(field) => setTableView({ startDateField: field })}
|
|
2954
|
+
placeholder="Auto"
|
|
2955
|
+
disabled={!fieldOptions.length}
|
|
2956
|
+
/>
|
|
2957
|
+
</WidgetSelectRow>
|
|
2958
|
+
<WidgetSelectRow icon={Calendar} label="End">
|
|
2959
|
+
<FieldDropdown
|
|
2960
|
+
fields={fieldOptions}
|
|
2961
|
+
value={tableView.endDateField || ""}
|
|
2962
|
+
onChange={(field) => setTableView({ endDateField: field })}
|
|
2963
|
+
placeholder="Auto"
|
|
2964
|
+
disabled={!fieldOptions.length}
|
|
2965
|
+
/>
|
|
2966
|
+
</WidgetSelectRow>
|
|
2967
|
+
</>
|
|
2968
|
+
) : null}
|
|
2969
|
+
{activeType === "calendar" ? (
|
|
2970
|
+
<WidgetSelectRow icon={Calendar} label="Date">
|
|
2971
|
+
<FieldDropdown
|
|
2972
|
+
fields={fieldOptions}
|
|
2973
|
+
value={tableView.startDateField || ""}
|
|
2974
|
+
onChange={(field) => setTableView({ startDateField: field })}
|
|
2975
|
+
placeholder="Auto"
|
|
2976
|
+
disabled={!fieldOptions.length}
|
|
2977
|
+
/>
|
|
2978
|
+
</WidgetSelectRow>
|
|
2979
|
+
) : null}
|
|
2980
|
+
</>
|
|
2981
|
+
) : null}
|
|
2982
|
+
</div>
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2636
2986
|
function FilterSubPanel({ widget, integrations, dataModelTable, adapterConfig, onRefreshAndSave, onChange, onBack }) {
|
|
2637
2987
|
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
2638
2988
|
const binding = widget.config?.binding || {};
|
|
@@ -3374,6 +3724,55 @@ function IframePreviewModal({ widget, onClose }) {
|
|
|
3374
3724
|
</div>;
|
|
3375
3725
|
}
|
|
3376
3726
|
|
|
3727
|
+
function TableTransformPreview({ widget, columns, rows }) {
|
|
3728
|
+
const type = getTableViewType(widget);
|
|
3729
|
+
const safeRows = Array.isArray(rows) ? rows : [];
|
|
3730
|
+
if (!type) {
|
|
3731
|
+
return <div
|
|
3732
|
+
className="workspace-view-table"
|
|
3733
|
+
aria-label={`${widget.title} preview`}
|
|
3734
|
+
style={{ "--workspace-view-columns": columns.length }}
|
|
3735
|
+
>
|
|
3736
|
+
<div>{columns.map((column) => <span key={column}>{column}</span>)}</div>
|
|
3737
|
+
{safeRows.slice(0, 6).map((row, rowIndex) => <div key={rowIndex}>
|
|
3738
|
+
{columns.map((column) => <span key={column}>{row?.[column] || ""}</span>)}
|
|
3739
|
+
</div>)}
|
|
3740
|
+
{!columns.length && !safeRows.length ? <div className="workspace-view-empty">Select a source</div> : null}
|
|
3741
|
+
<footer>Calculate</footer>
|
|
3742
|
+
</div>;
|
|
3743
|
+
}
|
|
3744
|
+
const fields = resolveTableViewFields(widget);
|
|
3745
|
+
if (type === "board") {
|
|
3746
|
+
const groups = safeRows.slice(0, 8).reduce((acc, row) => {
|
|
3747
|
+
const status = getFieldValue(row, fields.statusField, "Open");
|
|
3748
|
+
if (!acc[status]) acc[status] = [];
|
|
3749
|
+
acc[status].push(row);
|
|
3750
|
+
return acc;
|
|
3751
|
+
}, {});
|
|
3752
|
+
return <div className="workspace-table-transform-preview is-board">
|
|
3753
|
+
{Object.entries(groups).slice(0, 3).map(([status, groupRows]) => <section key={status}>
|
|
3754
|
+
<strong>{status}</strong>
|
|
3755
|
+
{groupRows.slice(0, 3).map((row, index) => <span key={index}>{getFieldValue(row, fields.titleField, `Row ${index + 1}`)}</span>)}
|
|
3756
|
+
</section>)}
|
|
3757
|
+
</div>;
|
|
3758
|
+
}
|
|
3759
|
+
if (type === "calendar") {
|
|
3760
|
+
return <div className="workspace-table-transform-preview is-calendar">
|
|
3761
|
+
{["Mon", "Tue", "Wed", "Thu", "Fri"].map((day, index) => <section key={day}>
|
|
3762
|
+
<strong>{day}</strong>
|
|
3763
|
+
{safeRows[index] ? <span>{getFieldValue(safeRows[index], fields.titleField, `Row ${index + 1}`)}</span> : null}
|
|
3764
|
+
</section>)}
|
|
3765
|
+
</div>;
|
|
3766
|
+
}
|
|
3767
|
+
return <div className={`workspace-table-transform-preview is-${type}`}>
|
|
3768
|
+
{safeRows.slice(0, 5).map((row, index) => <div key={index}>
|
|
3769
|
+
<span>{getFieldValue(row, fields.titleField, `Row ${index + 1}`)}</span>
|
|
3770
|
+
<i style={{ "--offset": `${(index % 4) * 14}%`, "--width": `${34 + (index % 3) * 12}%` }} />
|
|
3771
|
+
<em>{getFieldValue(row, fields.startDateField, getFieldValue(row, fields.endDateField, ""))}</em>
|
|
3772
|
+
</div>)}
|
|
3773
|
+
</div>;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3377
3776
|
function WidgetPreview({ widget, branding, selected, onSelect, onMoveStart, onRemove, onResizeStart, onExpandIframe }) {
|
|
3378
3777
|
const fallbackColumns = widget.config?.columns?.length ? widget.config.columns : [];
|
|
3379
3778
|
const visibleColumns = widget.kind === "view" ? getVisibleColumns(widget) : fallbackColumns;
|
|
@@ -3416,18 +3815,7 @@ function WidgetPreview({ widget, branding, selected, onSelect, onMoveStart, onRe
|
|
|
3416
3815
|
type="button"
|
|
3417
3816
|
><X size={13} /></button>
|
|
3418
3817
|
</div>
|
|
3419
|
-
{widget.kind === "view" ? <
|
|
3420
|
-
className="workspace-view-table"
|
|
3421
|
-
aria-label={`${widget.title} preview`}
|
|
3422
|
-
style={{ "--workspace-view-columns": viewColumns.length }}
|
|
3423
|
-
>
|
|
3424
|
-
<div>{viewColumns.map((column) => <span key={column}>{column}</span>)}</div>
|
|
3425
|
-
{viewRows.slice(0, 6).map((row, rowIndex) => <div key={rowIndex}>
|
|
3426
|
-
{viewColumns.map((column) => <span key={column}>{row?.[column] || ""}</span>)}
|
|
3427
|
-
</div>)}
|
|
3428
|
-
{!viewColumns.length && !viewRows.length ? <div className="workspace-view-empty">Select a source</div> : null}
|
|
3429
|
-
<footer>Calculate</footer>
|
|
3430
|
-
</div> : null}
|
|
3818
|
+
{widget.kind === "view" ? <TableTransformPreview widget={widget} columns={viewColumns} rows={viewRows} /> : null}
|
|
3431
3819
|
{widget.kind === "iframe" ? <div className="workspace-iframe-preview">
|
|
3432
3820
|
{isLikelyHttpUrl(widget.config?.url) ? <iframe title={`${widget.title} preview`} src={widget.config.url} /> : <span>Enter a valid http(s) URL</span>}
|
|
3433
3821
|
<button type="button" onClick={(event) => {
|
|
@@ -3842,6 +4230,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3842
4230
|
const [editingTabId, setEditingTabId] = useState(null);
|
|
3843
4231
|
const [editingTabDraft, setEditingTabDraft] = useState("");
|
|
3844
4232
|
const [workspaceView, setWorkspaceView] = useState("dashboards");
|
|
4233
|
+
const [activationPanelOpen, setActivationPanelOpen] = useState(false);
|
|
3845
4234
|
const [builderListFilter, setBuilderListFilter] = useState({ type: "all", query: "" });
|
|
3846
4235
|
const [builderActionMenuId, setBuilderActionMenuId] = useState(null);
|
|
3847
4236
|
const [builderActionMenuPlacement, setBuilderActionMenuPlacement] = useState(null);
|
|
@@ -3925,6 +4314,31 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3925
4314
|
? initialSourceRecords
|
|
3926
4315
|
: {})
|
|
3927
4316
|
);
|
|
4317
|
+
const activationState = useMemo(() => deriveWorkspaceActivationState({
|
|
4318
|
+
workspaceConfig: config,
|
|
4319
|
+
workspaceSourceRecords,
|
|
4320
|
+
}), [config, workspaceSourceRecords]);
|
|
4321
|
+
// Safe runtime descriptor for the secondary readiness lenses — assembled from
|
|
4322
|
+
// the persistence/adapter props the builder already receives (no fetch, no
|
|
4323
|
+
// secrets; booleans only).
|
|
4324
|
+
const lensMetadataGraph = useMemo(() => ({
|
|
4325
|
+
runtime: {
|
|
4326
|
+
persistenceMode: persistence?.mode || "",
|
|
4327
|
+
persistenceAdapter: persistence?.mode === "database" ? (adapterConfig?.dataAdapter || null) : null,
|
|
4328
|
+
allowFsWrite: persistence?.mode === "filesystem" && persistence?.canSave === true,
|
|
4329
|
+
nangoConfigured: Boolean(adapterConfig?.nango?.hasSecretKey),
|
|
4330
|
+
deploy: { target: adapterConfig?.deployTarget || "" },
|
|
4331
|
+
},
|
|
4332
|
+
}), [persistence, adapterConfig]);
|
|
4333
|
+
const activationStarted = activationState.completedCount > 0;
|
|
4334
|
+
const activationComplete = Boolean(activationState.complete);
|
|
4335
|
+
const activationUiCache = useMemo(() => getWorkspaceUiCache(config), [config]);
|
|
4336
|
+
const activationButtonHidden = activationUiCache.finishSetupButtonHidden === true
|
|
4337
|
+
|| String(activationUiCache.finishSetupButtonHidden || "") === "true";
|
|
4338
|
+
const showFinishSetupButton = workspaceView === "dashboards" && activationStarted && !activationButtonHidden;
|
|
4339
|
+
const showActivationPanel = workspaceView === "dashboards" && (
|
|
4340
|
+
!activationStarted || activationPanelOpen
|
|
4341
|
+
);
|
|
3928
4342
|
const resizeDragRef = useRef(null);
|
|
3929
4343
|
const moveDragRef = useRef(null);
|
|
3930
4344
|
const importInputRef = useRef(null);
|
|
@@ -3937,6 +4351,24 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3937
4351
|
() => listWorkspaceDataModelTables(config, { sourceRecords: workspaceSourceRecords }),
|
|
3938
4352
|
[config, workspaceSourceRecords]
|
|
3939
4353
|
);
|
|
4354
|
+
|
|
4355
|
+
const dismissFinishSetupButton = useCallback(async () => {
|
|
4356
|
+
const nextConfig = setWorkspaceUiCacheFlag(config, "finishSetupButtonHidden", true);
|
|
4357
|
+
setActivationPanelOpen(false);
|
|
4358
|
+
setConfig(nextConfig);
|
|
4359
|
+
try {
|
|
4360
|
+
const response = await fetch("/api/workspace", {
|
|
4361
|
+
method: "PATCH",
|
|
4362
|
+
headers: { "content-type": "application/json" },
|
|
4363
|
+
body: JSON.stringify({ dataModel: nextConfig.dataModel })
|
|
4364
|
+
});
|
|
4365
|
+
const payload = await response.json();
|
|
4366
|
+
if (!response.ok || !payload.workspaceConfig) throw new Error(payload.error || "Failed to save workspace preference");
|
|
4367
|
+
setConfig((prev) => ({ ...prev, dataModel: payload.workspaceConfig.dataModel }));
|
|
4368
|
+
} catch (error) {
|
|
4369
|
+
setConfigMessage(error.message || "Failed to save workspace preference");
|
|
4370
|
+
}
|
|
4371
|
+
}, [config]);
|
|
3940
4372
|
const selectedResolvedWidget = selectedWidget ? resolveViewWidget(selectedWidget, dataModelTables) : null;
|
|
3941
4373
|
const branding = config.branding || {};
|
|
3942
4374
|
const occupiedCells = useMemo(() => {
|
|
@@ -4222,11 +4654,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4222
4654
|
const createWorkflow = useCallback(async () => {
|
|
4223
4655
|
if (saving) return;
|
|
4224
4656
|
const nowIso = new Date().toISOString();
|
|
4225
|
-
const existing = getWorkflowSandboxObject(config);
|
|
4226
|
-
if (!existing) {
|
|
4227
|
-
setConfigMessage("Workflow sandbox object is missing.");
|
|
4228
|
-
return;
|
|
4229
|
-
}
|
|
4657
|
+
const existing = getWorkflowSandboxObject(config) || createWorkflowSandboxObject();
|
|
4230
4658
|
const sandboxObjectId = String(existing.id || "").trim();
|
|
4231
4659
|
const rows = Array.isArray(existing.rows) ? existing.rows : [];
|
|
4232
4660
|
const base = slugifyWorkflowName(`workflow-${rows.length + 1}`);
|
|
@@ -4240,11 +4668,20 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4240
4668
|
const sandboxRow = createBlankWorkflowSandboxRow(rowId, nowIso);
|
|
4241
4669
|
const nextDataModel = {
|
|
4242
4670
|
...(config.dataModel || {}),
|
|
4243
|
-
objects: (
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4671
|
+
objects: (() => {
|
|
4672
|
+
const objects = Array.isArray(config.dataModel?.objects) ? config.dataModel.objects : [];
|
|
4673
|
+
const hasSandboxObject = objects.some((object) => object?.id === sandboxObjectId);
|
|
4674
|
+
const hasSmokeRegistry = objects.some((object) => object?.id === "workflow-api-registry")
|
|
4675
|
+
|| objects.some((object) =>
|
|
4676
|
+
object?.objectType === "api-registry"
|
|
4677
|
+
&& (Array.isArray(object.rows) ? object.rows : []).some((row) => row?.integrationId === "growthub-workspace-smoke-api")
|
|
4678
|
+
);
|
|
4679
|
+
const nextSandboxObject = { ...existing, rows: [...rows, sandboxRow] };
|
|
4680
|
+
const nextObjects = hasSandboxObject
|
|
4681
|
+
? objects.map((object) => object?.id === sandboxObjectId ? nextSandboxObject : object)
|
|
4682
|
+
: [...objects, nextSandboxObject];
|
|
4683
|
+
return hasSmokeRegistry ? nextObjects : [...nextObjects, createWorkflowApiRegistryObject()];
|
|
4684
|
+
})()
|
|
4248
4685
|
};
|
|
4249
4686
|
const finalDataModel = addWorkflowFolderShortcut(nextDataModel, {
|
|
4250
4687
|
objectId: sandboxObjectId,
|
|
@@ -4307,6 +4744,36 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4307
4744
|
selectDashboard(targetIndex);
|
|
4308
4745
|
}, [dashboards, resolvedActiveDashboardId, searchParams, selectDashboard, workspaceView]);
|
|
4309
4746
|
|
|
4747
|
+
const openAddWidgetBuilder = useCallback(() => {
|
|
4748
|
+
const targetDashboard = activeDashboard || dashboards[0];
|
|
4749
|
+
if (!targetDashboard) return false;
|
|
4750
|
+
setConfig((prev) => {
|
|
4751
|
+
const synced = syncActiveDashboard(prev, activeDashboardId);
|
|
4752
|
+
const prevDashboards = synced.dashboards || [];
|
|
4753
|
+
const dashboard = prevDashboards.find((item) => item.id === targetDashboard.id) || prevDashboards[0];
|
|
4754
|
+
if (!dashboard) return prev;
|
|
4755
|
+
const normalized = normalizeDashboard(dashboard, dashboard.id === prevDashboards[0]?.id ? synced.canvas : undefined);
|
|
4756
|
+
const nextCanvas = dashboardCanvasFrom(normalized, synced.canvas);
|
|
4757
|
+
const nextActiveWidgets = getTabs(nextCanvas)[0]?.widgets || [];
|
|
4758
|
+
setSelectedWidgetId(null);
|
|
4759
|
+
setPendingSelectedWidgetId(null);
|
|
4760
|
+
setSelectedPosition(findFreePosition(nextActiveWidgets));
|
|
4761
|
+
setDragPreview(null);
|
|
4762
|
+
setActiveDashboardId(normalized.id);
|
|
4763
|
+
setWorkspaceView("builder");
|
|
4764
|
+
setDashboardLiveSnapshot(cloneConfig(normalized));
|
|
4765
|
+
setDashboardDraftMode(true);
|
|
4766
|
+
setPanelOpen(true);
|
|
4767
|
+
setConfigMessage(`Editing draft for ${normalized.name}`);
|
|
4768
|
+
return {
|
|
4769
|
+
...synced,
|
|
4770
|
+
dashboards: prevDashboards.map((item) => item.id === normalized.id ? normalized : item),
|
|
4771
|
+
canvas: nextCanvas
|
|
4772
|
+
};
|
|
4773
|
+
});
|
|
4774
|
+
return true;
|
|
4775
|
+
}, [activeDashboard, activeDashboardId, dashboards]);
|
|
4776
|
+
|
|
4310
4777
|
const enterDashboardTitleEdit = useCallback((dashboard) => {
|
|
4311
4778
|
if (!dashboard) return;
|
|
4312
4779
|
setEditingDashboardId(dashboard.id);
|
|
@@ -5299,6 +5766,40 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5299
5766
|
id: "workspace.builder", group: "Navigation", icon: Home, label: "Go to Builder",
|
|
5300
5767
|
run: () => showDashboardHome()
|
|
5301
5768
|
});
|
|
5769
|
+
list.push({
|
|
5770
|
+
id: "nav.data-model", group: "Navigation", icon: Database, label: "Go to Management (Data Model)",
|
|
5771
|
+
run: () => { window.location.href = "/data-model"; }
|
|
5772
|
+
});
|
|
5773
|
+
|
|
5774
|
+
// Workspace Lens — fast navigation into the post-activation operating
|
|
5775
|
+
// surface and its filtered views. Unlocks once activation completes.
|
|
5776
|
+
const lensReady = Boolean(activationState?.complete);
|
|
5777
|
+
list.push({
|
|
5778
|
+
id: "lens.open", group: "Workspace Lens", icon: Eye,
|
|
5779
|
+
label: lensReady ? "Open Workspace Lens" : "Workspace Lens (finish setup to unlock)",
|
|
5780
|
+
disabled: !lensReady,
|
|
5781
|
+
run: () => { window.location.href = "/workspace-lens"; }
|
|
5782
|
+
});
|
|
5783
|
+
list.push({
|
|
5784
|
+
id: "lens.blocked", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Blocked",
|
|
5785
|
+
disabled: !lensReady,
|
|
5786
|
+
run: () => { window.location.href = "/workspace-lens?filter=blocked"; }
|
|
5787
|
+
});
|
|
5788
|
+
list.push({
|
|
5789
|
+
id: "lens.ready", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Ready",
|
|
5790
|
+
disabled: !lensReady,
|
|
5791
|
+
run: () => { window.location.href = "/workspace-lens?filter=ready"; }
|
|
5792
|
+
});
|
|
5793
|
+
list.push({
|
|
5794
|
+
id: "lens.assignable", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Agent-assignable",
|
|
5795
|
+
disabled: !lensReady,
|
|
5796
|
+
run: () => { window.location.href = "/workspace-lens?filter=assignable"; }
|
|
5797
|
+
});
|
|
5798
|
+
list.push({
|
|
5799
|
+
id: "lens.runs", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Runs",
|
|
5800
|
+
disabled: !lensReady,
|
|
5801
|
+
run: () => { window.location.href = "/workspace-lens?filter=runs"; }
|
|
5802
|
+
});
|
|
5302
5803
|
|
|
5303
5804
|
return list;
|
|
5304
5805
|
}, [
|
|
@@ -5317,7 +5818,8 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5317
5818
|
saving,
|
|
5318
5819
|
selectedWidget,
|
|
5319
5820
|
showDashboardHome,
|
|
5320
|
-
workspaceView
|
|
5821
|
+
workspaceView,
|
|
5822
|
+
activationState
|
|
5321
5823
|
]);
|
|
5322
5824
|
|
|
5323
5825
|
return <main className="workspace-builder" onPointerDownCapture={resetWidgetSelectionOnOutsidePointer} style={builderStyle}>
|
|
@@ -5345,10 +5847,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5345
5847
|
dashboardsSlot={(
|
|
5346
5848
|
<button
|
|
5347
5849
|
type="button"
|
|
5850
|
+
title="Builder"
|
|
5348
5851
|
className={workspaceView === "dashboards" ? "active workspace-nav-button" : "workspace-nav-button"}
|
|
5349
5852
|
onClick={showDashboardHome}
|
|
5350
5853
|
>
|
|
5351
|
-
|
|
5854
|
+
<Wrench size={15} aria-hidden="true" />
|
|
5855
|
+
<span className="workspace-nav-label">Builder</span>
|
|
5352
5856
|
</button>
|
|
5353
5857
|
)}
|
|
5354
5858
|
managementSlot={(
|
|
@@ -5384,6 +5888,33 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5384
5888
|
<button type="button" className="dm-workflow-chip-btn" onClick={exportConfig}><Download size={13} />Export</button>
|
|
5385
5889
|
<button type="button" className="dm-workflow-icon-btn" onClick={() => importInputRef.current?.click()} aria-label="Import"><Import size={14} /></button>
|
|
5386
5890
|
</div> : <div className="workspace-toolbar-actions">
|
|
5891
|
+
{showFinishSetupButton ? (
|
|
5892
|
+
<span className={`workspace-finish-setup-control${activationComplete ? " is-complete" : ""}`}>
|
|
5893
|
+
<button
|
|
5894
|
+
type="button"
|
|
5895
|
+
className="workspace-finish-setup-trigger"
|
|
5896
|
+
onClick={() => {
|
|
5897
|
+
setWorkspaceView("dashboards");
|
|
5898
|
+
setActivationPanelOpen((open) => !open);
|
|
5899
|
+
}}
|
|
5900
|
+
>
|
|
5901
|
+
{activationComplete ? null : <Hourglass size={15} />}
|
|
5902
|
+
<span>{activationComplete ? "Setup Complete" : "Finish Workspace Setup"}</span>
|
|
5903
|
+
</button>
|
|
5904
|
+
{activationComplete ? (
|
|
5905
|
+
<button
|
|
5906
|
+
type="button"
|
|
5907
|
+
className="workspace-finish-setup-dismiss"
|
|
5908
|
+
aria-label="Hide completed workspace setup"
|
|
5909
|
+
title="Hide completed workspace setup"
|
|
5910
|
+
onClick={dismissFinishSetupButton}
|
|
5911
|
+
>
|
|
5912
|
+
<Check className="workspace-finish-setup-dismiss-check" size={15} aria-hidden="true" />
|
|
5913
|
+
<X className="workspace-finish-setup-dismiss-x" size={15} aria-hidden="true" />
|
|
5914
|
+
</button>
|
|
5915
|
+
) : null}
|
|
5916
|
+
</span>
|
|
5917
|
+
) : null}
|
|
5387
5918
|
<button type="button" onClick={addDashboard}><Plus size={15} />New Dashboard</button>
|
|
5388
5919
|
<button type="button" onClick={createWorkflow} disabled={saving}><GitBranch size={15} />New Workflow</button>
|
|
5389
5920
|
<button type="button" onClick={() => importInputRef.current?.click()}><Import size={15} />Import</button>
|
|
@@ -5397,7 +5928,28 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5397
5928
|
/>
|
|
5398
5929
|
</header>
|
|
5399
5930
|
|
|
5400
|
-
{workspaceView === "dashboards" ?
|
|
5931
|
+
{workspaceView === "dashboards" ? <>
|
|
5932
|
+
{showActivationPanel ? <WorkspaceActivationPanel
|
|
5933
|
+
workspaceConfig={config}
|
|
5934
|
+
workspaceSourceRecords={workspaceSourceRecords}
|
|
5935
|
+
metadataGraph={lensMetadataGraph}
|
|
5936
|
+
showLenses={true}
|
|
5937
|
+
onStepAction={(step) => {
|
|
5938
|
+
if (step?.id === "add-widget") return openAddWidgetBuilder();
|
|
5939
|
+
if (step?.id === "create-workflow") {
|
|
5940
|
+
createWorkflow();
|
|
5941
|
+
return true;
|
|
5942
|
+
}
|
|
5943
|
+
return false;
|
|
5944
|
+
}}
|
|
5945
|
+
onOpenHelper={() => {
|
|
5946
|
+
setHelperIntent("explain");
|
|
5947
|
+
setHelperInitialPrompt("Help me finish setting up this workspace.");
|
|
5948
|
+
setHelperInitialThread(null);
|
|
5949
|
+
setHelperOpen(true);
|
|
5950
|
+
}}
|
|
5951
|
+
/> : null}
|
|
5952
|
+
<section className="workspace-table" id="dashboards" aria-label="Builder">
|
|
5401
5953
|
<div className="workspace-table-heading">
|
|
5402
5954
|
<strong>Builder</strong>
|
|
5403
5955
|
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
@@ -5564,7 +6116,8 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5564
6116
|
)}
|
|
5565
6117
|
</span>
|
|
5566
6118
|
</div>)}
|
|
5567
|
-
</section>
|
|
6119
|
+
</section>
|
|
6120
|
+
</> : null}
|
|
5568
6121
|
|
|
5569
6122
|
{workspaceView === "builder" ? <section className="workspace-canvas" id="canvas" aria-label="Composable dashboard canvas">
|
|
5570
6123
|
<div className="workspace-tabs">
|
|
@@ -5836,14 +6389,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5836
6389
|
</small>
|
|
5837
6390
|
</label> : null}
|
|
5838
6391
|
{selectedWidget.kind === "view" ? <section className="workspace-field-stack">
|
|
5839
|
-
<
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
<WidgetSettingsRow icon={SlidersHorizontal} label="Sort" value={summarizeSort(selectedResolvedWidget || selectedWidget)} onClick={() => setInspectorPath("sort")} />
|
|
5846
|
-
</div>
|
|
6392
|
+
<TableViewConfig
|
|
6393
|
+
widget={selectedWidget}
|
|
6394
|
+
dataModelTable={resolveDataModelTable(dataModelTables, selectedWidget.config?.binding)}
|
|
6395
|
+
onChange={replaceSelectedWidgetConfig}
|
|
6396
|
+
onSubPage={(name) => setInspectorPath(name)}
|
|
6397
|
+
/>
|
|
5847
6398
|
</section> : null}
|
|
5848
6399
|
</section> : null}
|
|
5849
6400
|
{!selectedWidget ? <section>
|