@object-ui/app-shell 7.0.0 → 7.2.0
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/CHANGELOG.md +560 -0
- package/dist/console/AppContent.js +23 -17
- package/dist/console/ConsoleShell.d.ts +16 -0
- package/dist/console/ConsoleShell.js +43 -2
- package/dist/console/ai/AiChatPage.js +47 -16
- package/dist/console/ai/LiveCanvas.d.ts +8 -2
- package/dist/console/ai/LiveCanvas.js +6 -4
- package/dist/console/home/HomeLayout.js +5 -7
- package/dist/console/home/HomePage.js +1 -9
- package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
- package/dist/console/organizations/OrganizationsPage.js +22 -3
- package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
- package/dist/console/organizations/provisionEnvironment.js +64 -0
- package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
- package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
- package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
- package/dist/environment/EnvironmentListToolbar.js +59 -0
- package/dist/environment/entitlements.d.ts +90 -0
- package/dist/environment/entitlements.js +91 -0
- package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
- package/dist/environment/useEnvironmentEntitlements.js +108 -0
- package/dist/hooks/useActionModal.js +15 -1
- package/dist/hooks/useAiSurface.d.ts +59 -0
- package/dist/hooks/useAiSurface.js +78 -0
- package/dist/hooks/useChatConversation.d.ts +30 -0
- package/dist/hooks/useChatConversation.js +63 -0
- package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
- package/dist/hooks/useConsoleActionRuntime.js +42 -10
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10 -2
- package/dist/layout/AppHeader.js +28 -4
- package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
- package/dist/layout/ConsoleFloatingChatbot.js +41 -10
- package/dist/layout/ConsoleLayout.js +5 -6
- package/dist/layout/ContextSelectors.js +59 -35
- package/dist/layout/agentPicker.d.ts +56 -0
- package/dist/layout/agentPicker.js +40 -0
- package/dist/preview/CommitTimeline.d.ts +15 -0
- package/dist/preview/CommitTimeline.js +82 -0
- package/dist/preview/DraftPreviewBar.js +20 -7
- package/dist/preview/UnpublishedAppBar.js +11 -7
- package/dist/preview/commitHistory.d.ts +28 -0
- package/dist/preview/commitHistory.js +48 -0
- package/dist/providers/ExpressionProvider.js +9 -3
- package/dist/providers/MetadataProvider.js +9 -0
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/recordFormNavigation.d.ts +60 -0
- package/dist/utils/recordFormNavigation.js +35 -0
- package/dist/utils/resolvePageVarTokens.d.ts +31 -0
- package/dist/utils/resolvePageVarTokens.js +72 -0
- package/dist/views/CreateViewDialog.js +14 -1
- package/dist/views/FlowRunner.d.ts +2 -30
- package/dist/views/FlowRunner.js +18 -50
- package/dist/views/ObjectView.js +26 -12
- package/dist/views/ScreenView.d.ts +70 -0
- package/dist/views/ScreenView.js +73 -0
- package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
- package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
- package/dist/views/metadata-admin/DirectoryPage.js +2 -14
- package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
- package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
- package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
- package/dist/views/metadata-admin/PackagesPage.js +58 -5
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
- package/dist/views/metadata-admin/ResourceListPage.js +28 -19
- package/dist/views/metadata-admin/StudioHomePage.js +6 -14
- package/dist/views/metadata-admin/anchors.js +20 -2
- package/dist/views/metadata-admin/createBody.d.ts +26 -0
- package/dist/views/metadata-admin/createBody.js +30 -0
- package/dist/views/metadata-admin/i18n.js +108 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
- package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
- package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
- package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
- package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
- package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
- package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
- package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
- package/dist/views/metadata-admin/issuePath.d.ts +22 -0
- package/dist/views/metadata-admin/issuePath.js +65 -0
- package/dist/views/metadata-admin/package-scope.d.ts +41 -0
- package/dist/views/metadata-admin/package-scope.js +59 -0
- package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
- package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
- package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
- package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
- package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
- package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
- package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
- package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
- package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
- package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
- package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
- package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
- package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
- package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
- package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
- package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
- package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
- package/package.json +38 -38
|
@@ -181,6 +181,8 @@ const ENGINE_STRINGS_EN = {
|
|
|
181
181
|
'engine.list.warnCount': '{count} warning(s):',
|
|
182
182
|
'engine.list.allSources': 'All sources',
|
|
183
183
|
'engine.list.allPackages': 'All packages',
|
|
184
|
+
'engine.package.local': 'Local / Custom (this env)',
|
|
185
|
+
'engine.package.writableRequired': 'Pick or create a writable base (package) first — this item cannot be authored into a read-only code package.',
|
|
184
186
|
'engine.list.packageFilter': 'Package',
|
|
185
187
|
'engine.list.source.artifact': 'Artifact',
|
|
186
188
|
'engine.list.source.runtime': 'Runtime',
|
|
@@ -313,6 +315,7 @@ const ENGINE_STRINGS_EN = {
|
|
|
313
315
|
'engine.inspector.flowEdge.source': 'From',
|
|
314
316
|
'engine.inspector.flowEdge.target': 'To',
|
|
315
317
|
'engine.inspector.flowEdge.routing': 'Routing',
|
|
318
|
+
'engine.inspector.flowEdge.branch': 'Branch',
|
|
316
319
|
'engine.inspector.flowEdge.label': 'Branch label',
|
|
317
320
|
'engine.inspector.flowEdge.labelHint': 'e.g. approve / reject',
|
|
318
321
|
'engine.inspector.flowEdge.condition': 'Condition',
|
|
@@ -320,6 +323,18 @@ const ENGINE_STRINGS_EN = {
|
|
|
320
323
|
'engine.inspector.flowEdge.isDefault': 'Default branch (else)',
|
|
321
324
|
'engine.inspector.flowEdge.hint': 'The engine follows this connection when its branch label is selected or its condition is met. The default branch is taken when no other matches.',
|
|
322
325
|
'engine.inspector.flowEdge.remove': 'Remove connection',
|
|
326
|
+
'engine.inspector.flowEdge.approvalBranch': 'Approval branch',
|
|
327
|
+
'engine.inspector.flowEdge.branchApprove': 'Approve',
|
|
328
|
+
'engine.inspector.flowEdge.branchReject': 'Reject',
|
|
329
|
+
'engine.inspector.flowEdge.branchRevise': 'Revise — send back',
|
|
330
|
+
'engine.inspector.flowEdge.branchCustom': '— Custom —',
|
|
331
|
+
'engine.inspector.flowEdge.connection': 'Connection',
|
|
332
|
+
'engine.inspector.flowEdge.type': 'Type',
|
|
333
|
+
'engine.inspector.flowEdge.typeDefault': 'Normal',
|
|
334
|
+
'engine.inspector.flowEdge.typeConditional': 'Conditional',
|
|
335
|
+
'engine.inspector.flowEdge.typeFault': 'Fault (error path)',
|
|
336
|
+
'engine.inspector.flowEdge.typeBack': 'Back-edge (revise loop)',
|
|
337
|
+
'engine.inspector.flowEdge.backHint': 'A back-edge re-enters an earlier node to close a loop (e.g. an approval revise loop). It is traversed normally at run time but excluded from cycle validation.',
|
|
323
338
|
// Workflow action inspector
|
|
324
339
|
'engine.inspector.workflowAction.kind': 'Action',
|
|
325
340
|
'engine.inspector.workflowAction.close': 'Close action',
|
|
@@ -378,6 +393,9 @@ const ENGINE_STRINGS_EN = {
|
|
|
378
393
|
// Report default ("home") inspector
|
|
379
394
|
'engine.inspector.report.kind': 'Report',
|
|
380
395
|
'engine.inspector.report.close': 'Close report',
|
|
396
|
+
'engine.inspector.report.name': 'Name',
|
|
397
|
+
'engine.inspector.report.nameHint': 'snake_case identifier (cannot change after create)',
|
|
398
|
+
'engine.inspector.report.namePlaceholder': 'e.g. pipeline_by_stage',
|
|
381
399
|
'engine.inspector.report.label': 'Label',
|
|
382
400
|
'engine.inspector.report.labelPlaceholder': 'e.g. Pipeline by Stage',
|
|
383
401
|
'engine.inspector.report.type': 'Report type',
|
|
@@ -393,6 +411,12 @@ const ENGINE_STRINGS_EN = {
|
|
|
393
411
|
'engine.inspector.report.rowsEmpty': 'No dimensions yet. Add one from the dataset below.',
|
|
394
412
|
'engine.inspector.report.columnsAcross': 'Columns (across dimensions)',
|
|
395
413
|
'engine.inspector.report.columnsAcrossEmpty': 'No across dimensions yet — the matrix pivots rows × columns.',
|
|
414
|
+
'engine.inspector.report.chart': 'Chart',
|
|
415
|
+
'engine.inspector.report.chartType': 'Chart type',
|
|
416
|
+
'engine.inspector.report.chartNone': 'None (table only)',
|
|
417
|
+
'engine.inspector.report.chartTitle': 'Chart title',
|
|
418
|
+
'engine.inspector.report.chartX': 'X-Axis (dimension)',
|
|
419
|
+
'engine.inspector.report.chartY': 'Y-Axis (measure)',
|
|
396
420
|
'engine.inspector.report.noSchema': 'Spec schema unavailable — basic properties only.',
|
|
397
421
|
// Trailing section for fields the live server has but the bundled spec lacks.
|
|
398
422
|
'engine.inspector.moreFields': 'More fields',
|
|
@@ -423,7 +447,7 @@ const ENGINE_STRINGS_EN = {
|
|
|
423
447
|
'engine.edit.overlay': 'overlay',
|
|
424
448
|
'engine.edit.readOnlyBanner': 'Viewing in read-only mode. Click {edit} to make changes.',
|
|
425
449
|
'engine.edit.readOnlyTypeBanner': 'This metadata type is read-only for safety: it ships executable code or sensitive configuration and cannot be overlaid at runtime. Edit the source in your package and redeploy. To override this lock (use with caution), set {flag} to include {type}, or flip {override} in the registry.',
|
|
426
|
-
'engine.edit.artifactLockedBanner': 'This
|
|
450
|
+
'engine.edit.artifactLockedBanner': 'This {type} is provided by an installed package, so it is read-only at runtime. To change it, edit it in its source package and republish — or create a new {type} from scratch.',
|
|
427
451
|
'engine.badge.createOnly': 'create-only',
|
|
428
452
|
'engine.repeater.empty': 'No items. Click + to add.',
|
|
429
453
|
'engine.badge.writable': 'writable',
|
|
@@ -576,6 +600,15 @@ const ENGINE_STRINGS_EN = {
|
|
|
576
600
|
'engine.packages.detail.disabled': 'Package disabled.',
|
|
577
601
|
'engine.packages.detail.enabled': 'Package enabled.',
|
|
578
602
|
'engine.packages.detail.exported': 'Package exported.',
|
|
603
|
+
'engine.packages.detail.duplicate': 'Duplicate',
|
|
604
|
+
'engine.packages.detail.duplicating': 'Duplicating…',
|
|
605
|
+
'engine.packages.detail.duplicatePrompt': 'New package id for the duplicate (a fresh writable base):',
|
|
606
|
+
'engine.packages.detail.duplicated': 'Package duplicated into a new base.',
|
|
607
|
+
'engine.packages.detail.adoptOrphans': 'Adopt loose items',
|
|
608
|
+
'engine.packages.detail.adopting': 'Adopting…',
|
|
609
|
+
'engine.packages.detail.adoptConfirm': 'Move all package-less (loose) metadata in this environment INTO "{name}"? This rebinds orphaned items to this base.',
|
|
610
|
+
'engine.packages.detail.adopted': 'Loose items adopted into this base.',
|
|
611
|
+
'engine.packages.detail.deleteKeepData': 'Delete the DATA too?\n\nOK = also drop all records (destructive). Cancel = keep records, delete only the structure.',
|
|
579
612
|
'engine.quickfind.placeholder': "Find metadata types or items… (try 'view', 'account')",
|
|
580
613
|
'engine.quickfind.empty': 'Type to search across all metadata types.',
|
|
581
614
|
'engine.quickfind.title': 'Quick Find',
|
|
@@ -698,6 +731,26 @@ const ENGINE_STRINGS_EN = {
|
|
|
698
731
|
'designer.field.relationshipName': 'Relationship name',
|
|
699
732
|
'designer.field.relationshipNameHint': 'Inverse collection key on the parent',
|
|
700
733
|
'designer.field.objectNamePlaceholder': 'object_name',
|
|
734
|
+
// Lookup "Picker config" advanced sub-panel
|
|
735
|
+
'designer.field.lookup.pickerConfig': 'Picker config',
|
|
736
|
+
'designer.field.lookup.displayField': 'Display field',
|
|
737
|
+
'designer.field.lookup.descriptionField': 'Description field',
|
|
738
|
+
'designer.field.lookup.selectField': 'Select a field…',
|
|
739
|
+
'designer.field.lookup.setTargetFirst': 'Set the target object first',
|
|
740
|
+
'designer.field.lookup.searchFields': 'Search fields…',
|
|
741
|
+
'designer.field.lookup.selectableRecords': 'Selectable records',
|
|
742
|
+
'designer.field.lookup.addFilter': 'Add filter',
|
|
743
|
+
'designer.field.lookup.noFilter': 'No filter — every {ref} record is selectable.',
|
|
744
|
+
'designer.field.lookup.filterN': 'Filter {n}',
|
|
745
|
+
'designer.field.lookup.removeFilter': 'Remove filter',
|
|
746
|
+
'designer.field.lookup.filterField': 'Field',
|
|
747
|
+
'designer.field.lookup.filterOperator': 'Operator',
|
|
748
|
+
'designer.field.lookup.filterValue': 'Value',
|
|
749
|
+
'designer.field.lookup.dependsOn': 'Depends on (same-record fields)',
|
|
750
|
+
'designer.field.lookup.addDependsOn': 'Add a field this lookup depends on…',
|
|
751
|
+
'designer.field.lookup.searchHostFields': "Search this object's fields…",
|
|
752
|
+
'designer.field.lookup.pageSize': 'Picker page size',
|
|
753
|
+
'designer.field.lookup.allowCreate': 'Allow quick-create',
|
|
701
754
|
'designer.field.formula': 'Formula (CEL)',
|
|
702
755
|
'designer.field.precision': 'Precision',
|
|
703
756
|
'designer.field.scale': 'Scale',
|
|
@@ -755,6 +808,7 @@ const ENGINE_STRINGS_EN = {
|
|
|
755
808
|
'designer.canvas.askAiGenerate': 'Generate fields with AI',
|
|
756
809
|
};
|
|
757
810
|
const ENGINE_STRINGS_ZH = {
|
|
811
|
+
'engine.package.writableRequired': '请先选择或新建一个可写的基座(package)——只读的代码包中无法新建该项。',
|
|
758
812
|
'engine.directory.title': '元数据',
|
|
759
813
|
'engine.directory.description': '平台协议共暴露 {count} 个元数据类型(其中 {writable} 个支持运行时覆盖)。点击任意卡片即可浏览、覆盖或创建实例。',
|
|
760
814
|
'engine.directory.search': '搜索元数据类型…',
|
|
@@ -824,6 +878,7 @@ const ENGINE_STRINGS_ZH = {
|
|
|
824
878
|
'engine.list.warnCount': '{count} 个警告:',
|
|
825
879
|
'engine.list.allSources': '全部来源',
|
|
826
880
|
'engine.list.allPackages': '全部软件包',
|
|
881
|
+
'engine.package.local': '本地 / 自定义(本环境)',
|
|
827
882
|
'engine.list.packageFilter': '软件包',
|
|
828
883
|
'engine.list.source.artifact': '代码包',
|
|
829
884
|
'engine.list.source.runtime': '运行时',
|
|
@@ -956,6 +1011,7 @@ const ENGINE_STRINGS_ZH = {
|
|
|
956
1011
|
'engine.inspector.flowEdge.source': '起点',
|
|
957
1012
|
'engine.inspector.flowEdge.target': '终点',
|
|
958
1013
|
'engine.inspector.flowEdge.routing': '路由',
|
|
1014
|
+
'engine.inspector.flowEdge.branch': '分支',
|
|
959
1015
|
'engine.inspector.flowEdge.label': '分支标签',
|
|
960
1016
|
'engine.inspector.flowEdge.labelHint': '例如 approve / reject',
|
|
961
1017
|
'engine.inspector.flowEdge.condition': '条件',
|
|
@@ -963,6 +1019,18 @@ const ENGINE_STRINGS_ZH = {
|
|
|
963
1019
|
'engine.inspector.flowEdge.isDefault': '默认分支(else)',
|
|
964
1020
|
'engine.inspector.flowEdge.hint': '当此连线的分支标签被选中、或其条件成立时,引擎会走这条连线。其余都不匹配时走默认分支。',
|
|
965
1021
|
'engine.inspector.flowEdge.remove': '删除连线',
|
|
1022
|
+
'engine.inspector.flowEdge.approvalBranch': '审批分支',
|
|
1023
|
+
'engine.inspector.flowEdge.branchApprove': '通过',
|
|
1024
|
+
'engine.inspector.flowEdge.branchReject': '驳回',
|
|
1025
|
+
'engine.inspector.flowEdge.branchRevise': '退回修改',
|
|
1026
|
+
'engine.inspector.flowEdge.branchCustom': '—— 自定义 ——',
|
|
1027
|
+
'engine.inspector.flowEdge.connection': '连线',
|
|
1028
|
+
'engine.inspector.flowEdge.type': '类型',
|
|
1029
|
+
'engine.inspector.flowEdge.typeDefault': '普通',
|
|
1030
|
+
'engine.inspector.flowEdge.typeConditional': '条件',
|
|
1031
|
+
'engine.inspector.flowEdge.typeFault': '故障(错误路径)',
|
|
1032
|
+
'engine.inspector.flowEdge.typeBack': '回边(退回修改环)',
|
|
1033
|
+
'engine.inspector.flowEdge.backHint': '回边会重新进入更早的节点以闭合一个环(例如审批的退回修改环)。它在运行时正常通行,但不参与环路校验。',
|
|
966
1034
|
// Workflow action inspector
|
|
967
1035
|
'engine.inspector.workflowAction.kind': '动作',
|
|
968
1036
|
'engine.inspector.workflowAction.close': '关闭动作',
|
|
@@ -1020,6 +1088,9 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1020
1088
|
// Report default ("home") inspector
|
|
1021
1089
|
'engine.inspector.report.kind': '报表',
|
|
1022
1090
|
'engine.inspector.report.close': '关闭报表',
|
|
1091
|
+
'engine.inspector.report.name': '名称',
|
|
1092
|
+
'engine.inspector.report.nameHint': 'snake_case 标识符(创建后不可修改)',
|
|
1093
|
+
'engine.inspector.report.namePlaceholder': '例如:pipeline_by_stage',
|
|
1023
1094
|
'engine.inspector.report.label': '显示名',
|
|
1024
1095
|
'engine.inspector.report.labelPlaceholder': '例如:按阶段统计的销售管道',
|
|
1025
1096
|
'engine.inspector.report.type': '报表类型',
|
|
@@ -1035,6 +1106,12 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1035
1106
|
'engine.inspector.report.rowsEmpty': '还没有维度。从下方数据集中添加。',
|
|
1036
1107
|
'engine.inspector.report.columnsAcross': '列(横向维度)',
|
|
1037
1108
|
'engine.inspector.report.columnsAcrossEmpty': '还没有横向维度——矩阵按 行 × 列 透视。',
|
|
1109
|
+
'engine.inspector.report.chart': '图表',
|
|
1110
|
+
'engine.inspector.report.chartType': '图表类型',
|
|
1111
|
+
'engine.inspector.report.chartNone': '无(仅表格)',
|
|
1112
|
+
'engine.inspector.report.chartTitle': '图表标题',
|
|
1113
|
+
'engine.inspector.report.chartX': 'X 轴(维度)',
|
|
1114
|
+
'engine.inspector.report.chartY': 'Y 轴(度量)',
|
|
1038
1115
|
'engine.inspector.report.noSchema': '规格 schema 不可用 —— 仅显示基础属性。',
|
|
1039
1116
|
// Trailing section for fields the live server has but the bundled spec lacks.
|
|
1040
1117
|
'engine.inspector.moreFields': '更多字段',
|
|
@@ -1065,7 +1142,7 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1065
1142
|
'engine.edit.overlay': '覆盖',
|
|
1066
1143
|
'engine.edit.readOnlyBanner': '当前为只读模式。点击 {edit} 进行修改。',
|
|
1067
1144
|
'engine.edit.readOnlyTypeBanner': '出于安全考虑,此元数据类型为只读:它包含可执行代码或敏感配置,不允许在运行时通过覆盖层修改。请编辑包内源代码后重新部署。如需临时解除限制(请谨慎使用),可将 {flag} 设置为包含 {type},或在注册表中开启 {override}。',
|
|
1068
|
-
'engine.edit.artifactLockedBanner': '
|
|
1145
|
+
'engine.edit.artifactLockedBanner': '此 {type} 来自已安装的包,因此在运行时为只读。如需修改,请在其所属包的源中编辑并重新发布——或从零新建一个 {type}。',
|
|
1069
1146
|
'engine.badge.createOnly': '仅可新建',
|
|
1070
1147
|
'engine.repeater.empty': '暂无条目。点击 + 添加。',
|
|
1071
1148
|
'engine.badge.writable': '可写',
|
|
@@ -1218,6 +1295,15 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1218
1295
|
'engine.packages.detail.disabled': '软件包已禁用。',
|
|
1219
1296
|
'engine.packages.detail.enabled': '软件包已启用。',
|
|
1220
1297
|
'engine.packages.detail.exported': '软件包已导出。',
|
|
1298
|
+
'engine.packages.detail.duplicate': '复制',
|
|
1299
|
+
'engine.packages.detail.duplicating': '复制中…',
|
|
1300
|
+
'engine.packages.detail.duplicatePrompt': '副本的新软件包 id(一个全新的可写基座):',
|
|
1301
|
+
'engine.packages.detail.duplicated': '软件包已复制为新基座。',
|
|
1302
|
+
'engine.packages.detail.adoptOrphans': '收编散落项',
|
|
1303
|
+
'engine.packages.detail.adopting': '收编中…',
|
|
1304
|
+
'engine.packages.detail.adoptConfirm': '把本环境中所有无软件包(散落)的元数据移动到 "{name}" 吗?这会把孤儿项重新绑定到此基座。',
|
|
1305
|
+
'engine.packages.detail.adopted': '散落项已收编进此基座。',
|
|
1306
|
+
'engine.packages.detail.deleteKeepData': '同时删除数据吗?\n\n确定 = 同时删除所有记录(破坏性)。取消 = 保留记录,仅删除结构。',
|
|
1221
1307
|
'engine.quickfind.placeholder': '搜索元数据类型或条目…(如:view、account)',
|
|
1222
1308
|
'engine.quickfind.empty': '输入关键字以搜索所有元数据类型。',
|
|
1223
1309
|
'engine.quickfind.title': '快速查找',
|
|
@@ -1340,6 +1426,26 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1340
1426
|
'designer.field.relationshipName': '关系名称',
|
|
1341
1427
|
'designer.field.relationshipNameHint': '父对象上的反向集合键',
|
|
1342
1428
|
'designer.field.objectNamePlaceholder': 'object_name',
|
|
1429
|
+
// Lookup "Picker config" advanced sub-panel
|
|
1430
|
+
'designer.field.lookup.pickerConfig': '选择器配置',
|
|
1431
|
+
'designer.field.lookup.displayField': '显示字段',
|
|
1432
|
+
'designer.field.lookup.descriptionField': '描述字段',
|
|
1433
|
+
'designer.field.lookup.selectField': '选择字段…',
|
|
1434
|
+
'designer.field.lookup.setTargetFirst': '请先设置目标对象',
|
|
1435
|
+
'designer.field.lookup.searchFields': '搜索字段…',
|
|
1436
|
+
'designer.field.lookup.selectableRecords': '可选记录范围',
|
|
1437
|
+
'designer.field.lookup.addFilter': '添加筛选条件',
|
|
1438
|
+
'designer.field.lookup.noFilter': '未设筛选 — 所有 {ref} 记录均可选。',
|
|
1439
|
+
'designer.field.lookup.filterN': '筛选条件 {n}',
|
|
1440
|
+
'designer.field.lookup.removeFilter': '移除筛选条件',
|
|
1441
|
+
'designer.field.lookup.filterField': '字段',
|
|
1442
|
+
'designer.field.lookup.filterOperator': '运算符',
|
|
1443
|
+
'designer.field.lookup.filterValue': '值',
|
|
1444
|
+
'designer.field.lookup.dependsOn': '依赖字段(同记录字段)',
|
|
1445
|
+
'designer.field.lookup.addDependsOn': '添加该查找依赖的字段…',
|
|
1446
|
+
'designer.field.lookup.searchHostFields': '搜索本对象的字段…',
|
|
1447
|
+
'designer.field.lookup.pageSize': '选择器分页大小',
|
|
1448
|
+
'designer.field.lookup.allowCreate': '允许快速创建',
|
|
1343
1449
|
'designer.field.formula': '公式 (CEL)',
|
|
1344
1450
|
'designer.field.precision': '精度',
|
|
1345
1451
|
'designer.field.scale': '小数位',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - base `object`,
|
|
7
7
|
* - `include` relationships (the join allowlist — D-C),
|
|
8
8
|
* - `dimensions` (name + field/`relationship.field` + type + granularity), and
|
|
9
|
-
* - `measures` (name + aggregate + field +
|
|
9
|
+
* - `measures` (name + aggregate + field + format/currency/derived).
|
|
10
10
|
*
|
|
11
11
|
* The base object, the included relationships, and every `field` are picked
|
|
12
12
|
* from the live object graph (a searchable combo over {@link useDatasetFieldCatalog})
|
|
@@ -18,4 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import * as React from 'react';
|
|
20
20
|
import type { MetadataDefaultInspectorProps } from '../default-inspector-registry';
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Patch for a base-object change. A dataset's joins (`include`), `dimensions`,
|
|
23
|
+
* `measures`, and `filter` all reference the OLD object's fields, so a real
|
|
24
|
+
* object change re-bases the dataset and clears them — preventing stale field
|
|
25
|
+
* refs from silently producing broken/ambiguous queries. Selecting the SAME
|
|
26
|
+
* object is a no-op (only sets `object`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function objectChangePatch(next: string, current: string): Record<string, unknown>;
|
|
29
|
+
export declare function DatasetDefaultInspector({ draft, onPatch, readOnly, name }: MetadataDefaultInspectorProps): React.JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* - base `object`,
|
|
9
9
|
* - `include` relationships (the join allowlist — D-C),
|
|
10
10
|
* - `dimensions` (name + field/`relationship.field` + type + granularity), and
|
|
11
|
-
* - `measures` (name + aggregate + field +
|
|
11
|
+
* - `measures` (name + aggregate + field + format/currency/derived).
|
|
12
12
|
*
|
|
13
13
|
* The base object, the included relationships, and every `field` are picked
|
|
14
14
|
* from the live object graph (a searchable combo over {@link useDatasetFieldCatalog})
|
|
@@ -19,10 +19,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
* `onPatch`; the DatasetPreview on the canvas re-runs live as the draft changes.
|
|
20
20
|
*/
|
|
21
21
|
import * as React from 'react';
|
|
22
|
-
import { ArrowRight, Plus, Trash2, X } from 'lucide-react';
|
|
23
|
-
import { Badge, Button, Label } from '@object-ui/components';
|
|
22
|
+
import { AlertTriangle, ArrowRight, ChevronDown, Plus, Trash2, X } from 'lucide-react';
|
|
23
|
+
import { Badge, Button, FilterBuilder, Label, Popover, PopoverContent, PopoverTrigger } from '@object-ui/components';
|
|
24
24
|
import { InspectorShell, InspectorTextField, InspectorSelectField, InspectorCheckboxField, appendArray, spliceArray, } from './_shared';
|
|
25
25
|
import { InspectorComboField } from './InspectorComboField';
|
|
26
|
+
import { toFieldName } from '../previews/object-fields-io';
|
|
27
|
+
import { formatMeasure } from '@object-ui/core';
|
|
28
|
+
import { conditionToGroup, groupToCondition } from './datasetFilterCondition';
|
|
26
29
|
import { useObjectOptions, useDatasetFieldCatalog, useDatasetUsage, fieldTypeToDimensionType, } from './useDatasetFields';
|
|
27
30
|
// Closed to what the dataset compiler supports (no array_agg/string_agg in v1).
|
|
28
31
|
const AGGREGATE_OPTIONS = [
|
|
@@ -54,6 +57,30 @@ const DERIVED_OP_OPTIONS = [
|
|
|
54
57
|
{ value: 'difference', label: 'difference (a − b)' },
|
|
55
58
|
{ value: 'product', label: 'product (a × b)' },
|
|
56
59
|
];
|
|
60
|
+
// Display-format picker options — a business user shouldn't have to know numeral
|
|
61
|
+
// syntax (`$0,0.00`), so the inspector offers kind + decimals + currency and
|
|
62
|
+
// generates the `format`/`currency` strings.
|
|
63
|
+
const FORMAT_KIND_OPTIONS = [
|
|
64
|
+
{ value: 'raw', label: 'Raw number' },
|
|
65
|
+
{ value: 'number', label: 'Number — 1,234.5' },
|
|
66
|
+
{ value: 'currency', label: 'Currency — $1,234.50' },
|
|
67
|
+
{ value: 'percent', label: 'Percent — 12.3%' },
|
|
68
|
+
];
|
|
69
|
+
const DECIMALS_OPTIONS = [
|
|
70
|
+
{ value: '0', label: '0' },
|
|
71
|
+
{ value: '1', label: '1' },
|
|
72
|
+
{ value: '2', label: '2' },
|
|
73
|
+
];
|
|
74
|
+
const CURRENCY_OPTIONS = [
|
|
75
|
+
{ value: 'USD', label: 'USD ($)' },
|
|
76
|
+
{ value: 'EUR', label: 'EUR (€)' },
|
|
77
|
+
{ value: 'GBP', label: 'GBP (£)' },
|
|
78
|
+
{ value: 'CNY', label: 'CNY (¥)' },
|
|
79
|
+
{ value: 'JPY', label: 'JPY (¥)' },
|
|
80
|
+
{ value: 'INR', label: 'INR (₹)' },
|
|
81
|
+
{ value: 'CAD', label: 'CAD ($)' },
|
|
82
|
+
{ value: 'AUD', label: 'AUD ($)' },
|
|
83
|
+
];
|
|
57
84
|
function SectionHeader({ title, count, onAdd, addLabel }) {
|
|
58
85
|
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: title }), _jsx(Badge, { variant: "outline", className: "text-[10px]", children: count })] }), onAdd && (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "h-6 gap-1 px-1.5 text-[11px]", onClick: onAdd, children: [_jsx(Plus, { className: "h-3 w-3" }), " ", addLabel] }))] }));
|
|
59
86
|
}
|
|
@@ -61,7 +88,79 @@ function SectionHeader({ title, count, onAdd, addLabel }) {
|
|
|
61
88
|
function Advanced({ children }) {
|
|
62
89
|
return (_jsxs("details", { className: "group", children: [_jsx("summary", { className: "cursor-pointer select-none list-none text-[11px] text-muted-foreground hover:text-foreground", children: _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx(ArrowRight, { className: "h-3 w-3 transition-transform group-open:rotate-90" }), "Advanced"] }) }), _jsx("div", { className: "mt-1.5 space-y-1.5 border-l pl-2.5", children: children })] }));
|
|
63
90
|
}
|
|
64
|
-
|
|
91
|
+
/** Best-effort parse of a stored measure format into the picker's {kind, decimals}. */
|
|
92
|
+
function parseMeasureFormat(format, currency) {
|
|
93
|
+
const f = (format ?? '').trim();
|
|
94
|
+
const m = f.match(/\.(0+)/);
|
|
95
|
+
const decimals = m ? Math.min(m[1].length, 2) : 0;
|
|
96
|
+
if (currency || /[$£€¥₹]/.test(f))
|
|
97
|
+
return { kind: 'currency', decimals };
|
|
98
|
+
if (f.includes('%'))
|
|
99
|
+
return { kind: 'percent', decimals };
|
|
100
|
+
if (f)
|
|
101
|
+
return { kind: 'number', decimals };
|
|
102
|
+
return { kind: 'raw', decimals: 0 };
|
|
103
|
+
}
|
|
104
|
+
/** Generate {format, currency} from the picker selection. */
|
|
105
|
+
function buildMeasureFormat(kind, decimals, currency) {
|
|
106
|
+
const dp = decimals > 0 ? '.' + '0'.repeat(decimals) : '';
|
|
107
|
+
switch (kind) {
|
|
108
|
+
case 'number': return { format: `0,0${dp}`, currency: undefined };
|
|
109
|
+
case 'currency': return { format: `0,0${dp}`, currency: currency || 'USD' };
|
|
110
|
+
case 'percent': return { format: `0${dp}%`, currency: undefined };
|
|
111
|
+
default: return { format: undefined, currency: undefined };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Structured display-format picker for a measure. Maps {kind, decimals, currency}
|
|
116
|
+
* ⇄ the spec's `format`/`currency` strings and shows a live sample so a business
|
|
117
|
+
* user never has to hand-write a numeral pattern.
|
|
118
|
+
*/
|
|
119
|
+
function MeasureFormatField({ measure, onPatch, disabled }) {
|
|
120
|
+
const { kind, decimals } = parseMeasureFormat(measure.format, measure.currency);
|
|
121
|
+
const currency = measure.currency || 'USD';
|
|
122
|
+
const apply = (k, d, c) => onPatch(buildMeasureFormat(k, d, c));
|
|
123
|
+
const sample = formatMeasure(kind === 'percent' ? 0.1234 : 1234.5, measure.format, measure.currency);
|
|
124
|
+
return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [_jsx(InspectorSelectField, { label: "Display format", value: kind, options: FORMAT_KIND_OPTIONS, onCommit: (v) => apply(v, decimals, currency), disabled: disabled }), kind !== 'raw' && (_jsx(InspectorSelectField, { label: "Decimals", value: String(decimals), options: DECIMALS_OPTIONS, onCommit: (v) => apply(kind, parseInt(v, 10) || 0, currency), disabled: disabled }))] }), kind === 'currency' && (_jsx(InspectorSelectField, { label: "Currency", value: currency, options: CURRENCY_OPTIONS, onCommit: (v) => apply(kind, decimals, v), disabled: disabled })), kind !== 'raw' && (_jsxs("p", { className: "text-[10px] text-muted-foreground", children: ["Sample: ", _jsx("span", { className: "font-mono tabular-nums", children: sample })] }))] }));
|
|
125
|
+
}
|
|
126
|
+
/** The relationship PATH of a `relationship[.relationship].field` reference (all
|
|
127
|
+
* segments but the final column) that isn't yet in `include`, else null. ADR-0071
|
|
128
|
+
* multi-hop: `account.owner.region` → `account.owner`. */
|
|
129
|
+
function missingRelationship(field, include) {
|
|
130
|
+
if (!field || !field.includes('.'))
|
|
131
|
+
return null;
|
|
132
|
+
const rel = field.slice(0, field.lastIndexOf('.'));
|
|
133
|
+
return rel && !include.includes(rel) ? rel : null;
|
|
134
|
+
}
|
|
135
|
+
/** Inline author-time warning: a `relationship.field` whose join isn't declared in `include`. */
|
|
136
|
+
function RelWarning({ rel, onAdd, disabled }) {
|
|
137
|
+
return (_jsxs("p", { className: "flex items-center gap-1 text-[10px] text-amber-600 dark:text-amber-400", children: [_jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }), _jsxs("span", { children: ["Relationship ", _jsx("code", { className: "font-mono", children: rel }), " isn't in Included relationships."] }), !disabled && onAdd && (_jsx("button", { type: "button", className: "underline hover:no-underline", onClick: onAdd, children: "Add it" }))] }));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Visual filter editor for a dataset/measure `FilterCondition`. Wraps the shared
|
|
141
|
+
* {@link FilterBuilder} (a flat AND of `field op value` rows) and converts to/from
|
|
142
|
+
* the spec's Mongo-style `FilterCondition`. Filters it can't faithfully edit
|
|
143
|
+
* (nested / `$or` / multi-op) degrade to a "edit in Source" note rather than being
|
|
144
|
+
* silently rewritten. See {@link conditionToGroup} / {@link groupToCondition}.
|
|
145
|
+
*/
|
|
146
|
+
function DatasetFilterField({ label, help, value, onCommit, fields, disabled }) {
|
|
147
|
+
const { group, representable } = conditionToGroup(value);
|
|
148
|
+
const count = group.conditions.length;
|
|
149
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: label }), !representable ? (_jsxs("p", { className: "rounded-md border border-dashed bg-muted/30 px-2.5 py-1.5 text-[11px] text-muted-foreground", children: ["Advanced filter (nested / OR) \u2014 edit it in the ", _jsx("span", { className: "font-medium", children: "Source" }), " tab."] })) : (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: disabled, className: "h-8 w-full justify-between text-xs font-normal", children: [_jsx("span", { className: "truncate text-left", children: count ? `${count} condition${count === 1 ? '' : 's'}` : _jsx("span", { className: "text-muted-foreground", children: "+ Add filter\u2026" }) }), _jsx(ChevronDown, { className: "h-3.5 w-3.5 opacity-60 shrink-0" })] }) }), _jsx(PopoverContent, { align: "start", className: "w-[440px] max-w-[90vw] p-3", children: fields.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "Pick a base object to add filter conditions." })) : (_jsx(FilterBuilder, { fields: fields, value: group, onChange: (g) => onCommit(groupToCondition(g)) })) })] })), help && _jsx("p", { className: "text-[10px] text-muted-foreground", children: help })] }));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Patch for a base-object change. A dataset's joins (`include`), `dimensions`,
|
|
153
|
+
* `measures`, and `filter` all reference the OLD object's fields, so a real
|
|
154
|
+
* object change re-bases the dataset and clears them — preventing stale field
|
|
155
|
+
* refs from silently producing broken/ambiguous queries. Selecting the SAME
|
|
156
|
+
* object is a no-op (only sets `object`).
|
|
157
|
+
*/
|
|
158
|
+
export function objectChangePatch(next, current) {
|
|
159
|
+
if (next === current)
|
|
160
|
+
return { object: next };
|
|
161
|
+
return { object: next, include: [], dimensions: [], measures: [], filter: undefined };
|
|
162
|
+
}
|
|
163
|
+
export function DatasetDefaultInspector({ draft, onPatch, readOnly, name }) {
|
|
65
164
|
const label = typeof draft.label === 'string' ? draft.label : '';
|
|
66
165
|
const description = typeof draft.description === 'string' ? draft.description : '';
|
|
67
166
|
const object = typeof draft.object === 'string' ? draft.object : '';
|
|
@@ -69,33 +168,62 @@ export function DatasetDefaultInspector({ draft, onPatch, readOnly }) {
|
|
|
69
168
|
const dimensions = Array.isArray(draft.dimensions) ? draft.dimensions : [];
|
|
70
169
|
const measures = Array.isArray(draft.measures) ? draft.measures : [];
|
|
71
170
|
const datasetName = typeof draft.name === 'string' ? draft.name : undefined;
|
|
171
|
+
// In create mode the host passes an empty `name` (the PK is assigned on first
|
|
172
|
+
// save). Mirror ReportDefaultInspector: expose an editable Name that auto-
|
|
173
|
+
// derives a snake_case slug from the label until the author edits it directly,
|
|
174
|
+
// so a dataset created through the canvas saves with a valid identifier instead
|
|
175
|
+
// of dead-ending on the empty-name identity rule.
|
|
176
|
+
const createMode = !name;
|
|
177
|
+
const nameTouched = React.useRef(false);
|
|
178
|
+
const nameValue = typeof draft.name === 'string' ? draft.name : '';
|
|
72
179
|
const { options: objectOptions, loading: objectsLoading } = useObjectOptions();
|
|
73
180
|
const { relationships, fieldOptions, loading: catalogLoading } = useDatasetFieldCatalog(object, include);
|
|
74
181
|
const usage = useDatasetUsage(datasetName);
|
|
75
182
|
const objectComboOptions = React.useMemo(() => objectOptions.map((o) => ({ value: o.name, label: o.label })), [objectOptions]);
|
|
76
183
|
const relationshipComboOptions = React.useMemo(() => relationships.map((r) => ({ value: r.name, label: r.label, hint: r.referenceTo ? `→ ${r.referenceTo}` : undefined })), [relationships]);
|
|
77
184
|
const fieldComboOptions = React.useMemo(() => fieldOptions.map((f) => ({ value: f.value, label: f.label, hint: f.type, group: f.group })), [fieldOptions]);
|
|
185
|
+
// Base-object fields for the filter builders (scope + measure filters operate on
|
|
186
|
+
// the base table; relationship-path filters are out of scope for v1).
|
|
187
|
+
const filterFields = React.useMemo(() => fieldOptions.filter((f) => !f.value.includes('.')).map((f) => ({ value: f.value, label: f.label, type: f.type })), [fieldOptions]);
|
|
188
|
+
const datasetFilter = draft.filter && typeof draft.filter === 'object' ? draft.filter : undefined;
|
|
78
189
|
const baseLabel = objectComboOptions.find((o) => o.value === object)?.label ?? object;
|
|
79
190
|
const patchDimension = (i, patch) => onPatch({ dimensions: dimensions.map((d, idx) => (idx === i ? { ...d, ...patch } : d)) });
|
|
80
191
|
const patchMeasure = (i, patch) => onPatch({ measures: measures.map((m, idx) => (idx === i ? { ...m, ...patch } : m)) });
|
|
81
192
|
// Picking a field auto-infers the dimension type from the field's framework
|
|
82
193
|
// type (region:string, close_date:date, …) — the BI "pick field, type follows"
|
|
83
194
|
// convention — while leaving the Type select free to override.
|
|
195
|
+
const leafName = (path) => (path.includes('.') ? path.split('.').pop() ?? path : path);
|
|
84
196
|
const pickDimensionField = (i, v) => {
|
|
85
197
|
const opt = fieldOptions.find((o) => o.value === v);
|
|
86
|
-
|
|
198
|
+
const patch = opt?.type ? { field: v, type: fieldTypeToDimensionType(opt.type) } : { field: v };
|
|
199
|
+
if (!dimensions[i]?.name)
|
|
200
|
+
patch.name = leafName(v); // auto-name from field when unnamed
|
|
201
|
+
patchDimension(i, patch);
|
|
202
|
+
};
|
|
203
|
+
const pickMeasureField = (i, v) => {
|
|
204
|
+
const patch = { field: v };
|
|
205
|
+
if (!measures[i]?.name)
|
|
206
|
+
patch.name = leafName(v); // auto-name from field when unnamed
|
|
207
|
+
patchMeasure(i, patch);
|
|
87
208
|
};
|
|
88
209
|
return (_jsxs(InspectorShell, { kindLabel: "Dataset", title: String(label || draft.name || 'Dataset'), onClose: () => { }, hideClose: true, children: [datasetName && !usage.loading && (_jsx("p", { className: usage.reports + usage.dashboards > 0
|
|
89
210
|
? 'rounded-md border border-amber-500/30 bg-amber-500/5 px-2.5 py-1.5 text-[11px] text-amber-700 dark:text-amber-300'
|
|
90
211
|
: 'text-[11px] text-muted-foreground', children: usage.reports + usage.dashboards > 0
|
|
91
212
|
? `Bound by ${usage.reports} report${usage.reports === 1 ? '' : 's'} · ${usage.dashboards} dashboard${usage.dashboards === 1 ? '' : 's'} — changes affect them.`
|
|
92
|
-
: 'Not yet bound by any report or dashboard.' })),
|
|
213
|
+
: 'Not yet bound by any report or dashboard.' })), createMode && (_jsx(InspectorTextField, { label: "Name", value: nameValue, onCommit: (v) => { nameTouched.current = true; onPatch({ name: toFieldName(v) }); }, placeholder: "snake_case identifier", disabled: readOnly, mono: true })), _jsx(InspectorTextField, { label: "Label", value: label, onCommit: (v) => {
|
|
214
|
+
// Live-derive the snake_case name from the label until the author edits
|
|
215
|
+
// the Name field directly (create mode only).
|
|
216
|
+
const patch = { label: v };
|
|
217
|
+
if (createMode && !nameTouched.current)
|
|
218
|
+
patch.name = toFieldName(v);
|
|
219
|
+
onPatch(patch);
|
|
220
|
+
}, disabled: readOnly }), _jsx(InspectorTextField, { label: "Description", value: description, onCommit: (v) => onPatch({ description: v }), disabled: readOnly }), _jsx(InspectorComboField, { label: "Base object", value: object, onCommit: (v) => onPatch(objectChangePatch(v, object)), options: objectComboOptions, loading: objectsLoading, placeholder: "Select an object\u2026", searchPlaceholder: "Search objects\u2026", disabled: readOnly, mono: true }), object && (dimensions.length > 0 || measures.length > 0 || include.length > 0 || !!datasetFilter) && (_jsx("p", { className: "text-[10px] text-muted-foreground", children: "Changing the base object clears its dimensions, measures, joins & filters." })), _jsxs("div", { className: "border-t pt-3 space-y-1.5", children: [_jsx(SectionHeader, { title: "Included relationships", count: include.length, addLabel: "Add", onAdd: readOnly ? undefined : () => onPatch({ include: appendArray(include, '') }) }), include.length === 0 ? (_jsxs("p", { className: "rounded-md border border-dashed bg-muted/30 px-3 py-2 text-center text-[11px] text-muted-foreground", children: ["No joins. Add a relationship (a lookup field on ", _jsx("code", { children: baseLabel || 'the base object' }), ") to use ", _jsx("code", { children: "relationship.field" }), " dimensions/measures."] })) : (include.map((rel, i) => (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(InspectorComboField, { value: rel, onCommit: (v) => onPatch({ include: include.map((r, idx) => (idx === i ? v : r)) }), options: relationshipComboOptions, loading: catalogLoading, placeholder: "Select a relationship\u2026", searchPlaceholder: "Search relationships\u2026", disabled: readOnly, mono: true }), !readOnly && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 shrink-0 p-0", onClick: () => onPatch({ include: spliceArray(include, i, null) }), "aria-label": "Remove relationship", children: _jsx(X, { className: "h-3.5 w-3.5" }) }))] }, i)))), object && include.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 pt-0.5 text-[10px] text-muted-foreground", children: [_jsx("span", { className: "font-mono font-medium", children: baseLabel }), include.map((rel, i) => {
|
|
93
221
|
const r = relationships.find((x) => x.name === rel);
|
|
94
222
|
return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx(ArrowRight, { className: "h-3 w-3 opacity-60" }), _jsxs("span", { className: "font-mono", children: [rel, r?.referenceTo ? ` (${r.referenceTo})` : ''] })] }, i));
|
|
95
|
-
})] }))] }), _jsxs("div", { className: "border-t pt-3 space-y-2", children: [_jsx(SectionHeader, { title: "Dimensions", count: dimensions.length, addLabel: "Add dimension", onAdd: readOnly ? undefined : () => onPatch({ dimensions: appendArray(dimensions, { name: '', field: '', type: 'string' }) }) }), dimensions.map((d, i) => (_jsxs("div", { className: "rounded-md border p-2 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-[11px] font-medium text-muted-foreground", children: ["Dimension ", i + 1] }), !readOnly && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Remove dimension", title: "Remove dimension", className: "h-6 w-6 shrink-0 p-0 text-muted-foreground hover:text-destructive", onClick: () => onPatch({ dimensions: spliceArray(dimensions, i, null) }), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(InspectorTextField, { label: "Name", value: d.name ?? '', onCommit: (v) => patchDimension(i, { name: v }), placeholder: "e.g. region", disabled: readOnly, mono: true }), _jsx(InspectorComboField, { label: "Field", value: d.field ?? '', onCommit: (v) => pickDimensionField(i, v), options: fieldComboOptions, loading: catalogLoading, placeholder: "field or relationship.field", searchPlaceholder: "Search fields\u2026", disabled: readOnly, mono: true }), _jsx(InspectorSelectField, { label: "Type", value: d.type, options: DIMENSION_TYPE_OPTIONS, onCommit: (v) => patchDimension(i, { type: v }), disabled: readOnly }), _jsxs(Advanced, { children: [_jsx(InspectorTextField, { label: "Label (optional)", value: d.label ?? '', onCommit: (v) => patchDimension(i, { label: v || undefined }), placeholder: d.name || 'Display label', disabled: readOnly }), d.type === 'date' && (_jsx(InspectorSelectField, { label: "Date bucket", value: d.dateGranularity ?? '', options: DATE_GRANULARITY_OPTIONS, onCommit: (v) => patchDimension(i, { dateGranularity: v || undefined }), disabled: readOnly }))] })] }, i)))] }), _jsxs("div", { className: "border-t pt-3 space-y-2", children: [_jsx(SectionHeader, { title: "Measures", count: measures.length, addLabel: "Add measure", onAdd: readOnly ? undefined : () => onPatch({ measures: appendArray(measures, { name: '', aggregate: 'sum', field: ''
|
|
223
|
+
})] }))] }), _jsx("div", { className: "border-t pt-3", children: _jsx(DatasetFilterField, { label: "Scope filter", help: "Intrinsic scope, ANDed into every query (e.g. exclude soft-deleted records).", value: datasetFilter, onCommit: (fc) => onPatch({ filter: fc }), fields: filterFields, disabled: readOnly }) }), _jsxs("div", { className: "border-t pt-3 space-y-2", children: [_jsx(SectionHeader, { title: "Dimensions", count: dimensions.length, addLabel: "Add dimension", onAdd: readOnly ? undefined : () => onPatch({ dimensions: appendArray(dimensions, { name: '', field: '', type: 'string' }) }) }), dimensions.map((d, i) => (_jsxs("div", { className: "rounded-md border p-2 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-[11px] font-medium text-muted-foreground", children: ["Dimension ", i + 1] }), !readOnly && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Remove dimension", title: "Remove dimension", className: "h-6 w-6 shrink-0 p-0 text-muted-foreground hover:text-destructive", onClick: () => onPatch({ dimensions: spliceArray(dimensions, i, null) }), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(InspectorTextField, { label: "Name", value: d.name ?? '', onCommit: (v) => patchDimension(i, { name: v }), placeholder: "e.g. region", disabled: readOnly, mono: true }), _jsx(InspectorComboField, { label: "Field", value: d.field ?? '', onCommit: (v) => pickDimensionField(i, v), options: fieldComboOptions, loading: catalogLoading, placeholder: "field or relationship.field", searchPlaceholder: "Search fields\u2026", disabled: readOnly, mono: true }), (() => { const rel = missingRelationship(d.field, include); return rel ? _jsx(RelWarning, { rel: rel, disabled: readOnly, onAdd: () => onPatch({ include: appendArray(include, rel) }) }) : null; })(), _jsx(InspectorSelectField, { label: "Type", value: d.type, options: DIMENSION_TYPE_OPTIONS, onCommit: (v) => patchDimension(i, { type: v }), disabled: readOnly }), _jsxs(Advanced, { children: [_jsx(InspectorTextField, { label: "Label (optional)", value: d.label ?? '', onCommit: (v) => patchDimension(i, { label: v || undefined }), placeholder: d.name || 'Display label', disabled: readOnly }), d.type === 'date' && (_jsx(InspectorSelectField, { label: "Date bucket", value: d.dateGranularity ?? '', options: DATE_GRANULARITY_OPTIONS, onCommit: (v) => patchDimension(i, { dateGranularity: v || undefined }), disabled: readOnly }))] })] }, i)))] }), _jsxs("div", { className: "border-t pt-3 space-y-2", children: [_jsx(SectionHeader, { title: "Measures", count: measures.length, addLabel: "Add measure", onAdd: readOnly ? undefined : () => onPatch({ measures: appendArray(measures, { name: '', aggregate: 'sum', field: '' }) }) }), measures.map((m, i) => {
|
|
96
224
|
const otherMeasures = measures.filter((_, idx) => idx !== i).map((x) => x.name).filter((n) => !!n);
|
|
97
225
|
const derived = m.derived;
|
|
98
|
-
return (_jsxs("div", { className: "rounded-md border p-2 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-[11px] font-medium text-muted-foreground", children: ["Measure ", i + 1] }), !readOnly && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Remove measure", title: "Remove measure", className: "h-6 w-6 shrink-0 p-0 text-muted-foreground hover:text-destructive", onClick: () => onPatch({ measures: spliceArray(measures, i, null) }), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(InspectorTextField, { label: "Name", value: m.name ?? '', onCommit: (v) => patchMeasure(i, { name: v }), placeholder: "e.g. revenue", disabled: readOnly, mono: true }), _jsx(InspectorSelectField, { label: "Aggregate", value: m.aggregate, options: AGGREGATE_OPTIONS, onCommit: (v) => patchMeasure(i, { aggregate: v }), disabled: readOnly }), _jsx(InspectorComboField, { label: "Field", value: m.field ?? '', onCommit: (v) =>
|
|
226
|
+
return (_jsxs("div", { className: "rounded-md border p-2 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-[11px] font-medium text-muted-foreground", children: ["Measure ", i + 1] }), !readOnly && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Remove measure", title: "Remove measure", className: "h-6 w-6 shrink-0 p-0 text-muted-foreground hover:text-destructive", onClick: () => onPatch({ measures: spliceArray(measures, i, null) }), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(InspectorTextField, { label: "Name", value: m.name ?? '', onCommit: (v) => patchMeasure(i, { name: v }), placeholder: "e.g. revenue", disabled: readOnly, mono: true }), _jsx(InspectorSelectField, { label: "Aggregate", value: m.aggregate, options: AGGREGATE_OPTIONS, onCommit: (v) => patchMeasure(i, { aggregate: v }), disabled: readOnly }), _jsx(InspectorComboField, { label: "Field", value: m.field ?? '', onCommit: (v) => pickMeasureField(i, v), options: fieldComboOptions, loading: catalogLoading, placeholder: "field (optional for count)", searchPlaceholder: "Search fields\u2026", disabled: readOnly, mono: true }), (() => { const rel = missingRelationship(m.field, include); return rel ? _jsx(RelWarning, { rel: rel, disabled: readOnly, onAdd: () => onPatch({ include: appendArray(include, rel) }) }) : null; })(), _jsxs(Advanced, { children: [_jsx(InspectorTextField, { label: "Label (optional)", value: m.label ?? '', onCommit: (v) => patchMeasure(i, { label: v || undefined }), placeholder: m.name || 'Display label', disabled: readOnly }), _jsx(MeasureFormatField, { measure: m, onPatch: (pp) => patchMeasure(i, pp), disabled: readOnly }), _jsx(DatasetFilterField, { label: "Filter (measure-scoped)", help: "Only rows matching this filter feed this measure (e.g. won_amount = sum(amount) where stage = won).", value: m.filter, onCommit: (fc) => patchMeasure(i, { filter: fc }), fields: filterFields, disabled: readOnly }), _jsx(InspectorCheckboxField, { label: "Derived \u2014 computed from other measures", value: !!derived, onCommit: (v) => patchMeasure(i, { derived: v ? { op: 'ratio', of: [] } : undefined }), disabled: readOnly }), derived && (_jsxs("div", { className: "space-y-1.5 rounded-md border border-dashed p-2", children: [_jsx(InspectorSelectField, { label: "Operation", value: derived.op, options: DERIVED_OP_OPTIONS, onCommit: (v) => patchMeasure(i, { derived: { ...derived, op: v } }), disabled: readOnly }), _jsx(Label, { className: "text-xs text-muted-foreground", children: "Operands (other measures)" }), (() => { const need = derived.op === 'ratio' || derived.op === 'difference' ? 2 : 1; const have = Array.isArray(derived.of) ? derived.of.length : 0; return have < need ? _jsxs("p", { className: "text-[10px] text-amber-600 dark:text-amber-400", children: ["Select ", need === 2 ? 'exactly 2 measures' : 'at least 1 measure', " for ", derived.op, "."] }) : null; })(), otherMeasures.length === 0 ? (_jsx("p", { className: "text-[11px] italic text-muted-foreground", children: "Add other measures first." })) : (_jsx("div", { className: "space-y-1", children: otherMeasures.map((nm) => {
|
|
99
227
|
const checked = Array.isArray(derived.of) && derived.of.includes(nm);
|
|
100
228
|
return (_jsx(InspectorCheckboxField, { label: nm, value: checked, disabled: readOnly, onCommit: (v) => {
|
|
101
229
|
const current = Array.isArray(derived.of) ? derived.of : [];
|