@oronts/vendure-data-hub-plugin 0.1.1 → 0.1.2

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.
Files changed (235) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/dashboard/components/common/ConnectionConfigEditor.tsx +589 -0
  3. package/dist/dashboard/components/common/HeadersEditor.tsx +90 -0
  4. package/dist/dashboard/components/common/ValidationFeedback.tsx +17 -0
  5. package/dist/dashboard/components/common/index.ts +10 -0
  6. package/dist/dashboard/components/pipelines/PipelineEditor.tsx +504 -0
  7. package/dist/dashboard/components/pipelines/PipelineExport.tsx +63 -0
  8. package/dist/dashboard/components/pipelines/PipelineImport.tsx +87 -0
  9. package/dist/dashboard/components/pipelines/ReactFlowPipelineEditor.tsx +539 -0
  10. package/dist/dashboard/components/pipelines/shared/NodePropertiesPanel.tsx +146 -0
  11. package/dist/dashboard/components/pipelines/shared/PipelineNode.tsx +155 -0
  12. package/dist/dashboard/components/pipelines/shared/PipelineSettingsPanel.tsx +392 -0
  13. package/dist/dashboard/components/pipelines/shared/StepListItem.tsx +144 -0
  14. package/dist/dashboard/components/pipelines/shared/index.ts +33 -0
  15. package/dist/dashboard/components/pipelines/shared/visual-node-config.ts +169 -0
  16. package/dist/dashboard/components/shared/LoadMoreButton.tsx +18 -0
  17. package/dist/dashboard/components/shared/entity-selector/EntitySelector.tsx +59 -0
  18. package/dist/dashboard/components/shared/entity-selector/index.ts +1 -0
  19. package/dist/dashboard/components/shared/error-boundary/ErrorBoundary.tsx +90 -0
  20. package/dist/dashboard/components/shared/error-boundary/index.ts +1 -0
  21. package/dist/dashboard/components/shared/feedback/EmptyState.tsx +36 -0
  22. package/dist/dashboard/components/shared/feedback/ErrorState.tsx +69 -0
  23. package/dist/dashboard/components/shared/feedback/LoadingState.tsx +104 -0
  24. package/dist/dashboard/components/shared/feedback/ValidationErrorDisplay.tsx +29 -0
  25. package/dist/dashboard/components/shared/feedback/index.ts +4 -0
  26. package/dist/dashboard/components/shared/file-dropzone/FileDropzone.tsx +167 -0
  27. package/dist/dashboard/components/shared/file-dropzone/index.ts +1 -0
  28. package/dist/dashboard/components/shared/filter-conditions-editor/FilterConditionsEditor.tsx +226 -0
  29. package/dist/dashboard/components/shared/filter-conditions-editor/index.ts +1 -0
  30. package/dist/dashboard/components/shared/index.ts +45 -0
  31. package/dist/dashboard/components/shared/schema-form/SchemaFormRenderer.tsx +248 -0
  32. package/dist/dashboard/components/shared/schema-form/fields/BooleanField.tsx +26 -0
  33. package/dist/dashboard/components/shared/schema-form/fields/FieldWrapper.tsx +28 -0
  34. package/dist/dashboard/components/shared/schema-form/fields/FileUploadField.tsx +171 -0
  35. package/dist/dashboard/components/shared/schema-form/fields/JsonField.tsx +132 -0
  36. package/dist/dashboard/components/shared/schema-form/fields/NumberField.tsx +33 -0
  37. package/dist/dashboard/components/shared/schema-form/fields/SelectField.tsx +70 -0
  38. package/dist/dashboard/components/shared/schema-form/fields/StringField.tsx +36 -0
  39. package/dist/dashboard/components/shared/schema-form/fields/TextareaField.tsx +31 -0
  40. package/dist/dashboard/components/shared/schema-form/fields/index.ts +23 -0
  41. package/dist/dashboard/components/shared/schema-form/index.ts +2 -0
  42. package/dist/dashboard/components/shared/schema-form/utils.ts +3 -0
  43. package/dist/dashboard/components/shared/selectable-card/SelectableCard.tsx +65 -0
  44. package/dist/dashboard/components/shared/selectable-card/index.ts +1 -0
  45. package/dist/dashboard/components/shared/stat-card/StatCard.tsx +121 -0
  46. package/dist/dashboard/components/shared/stat-card/index.ts +1 -0
  47. package/dist/dashboard/components/shared/step-config/AdapterRequiredWarning.tsx +25 -0
  48. package/dist/dashboard/components/shared/step-config/AdapterSelector.tsx +109 -0
  49. package/dist/dashboard/components/shared/step-config/AdvancedEditors.tsx +634 -0
  50. package/dist/dashboard/components/shared/step-config/EnrichConfigComponent.tsx +295 -0
  51. package/dist/dashboard/components/shared/step-config/ExtractTestResults.tsx +143 -0
  52. package/dist/dashboard/components/shared/step-config/GateConfigComponent.tsx +127 -0
  53. package/dist/dashboard/components/shared/step-config/LoadTestResults.tsx +104 -0
  54. package/dist/dashboard/components/shared/step-config/OperatorCard.tsx +266 -0
  55. package/dist/dashboard/components/shared/step-config/OperatorCheatSheetButton.tsx +54 -0
  56. package/dist/dashboard/components/shared/step-config/OperatorFieldInput.tsx +209 -0
  57. package/dist/dashboard/components/shared/step-config/RetrySettingsComponent.tsx +111 -0
  58. package/dist/dashboard/components/shared/step-config/RouteConfigComponent.tsx +125 -0
  59. package/dist/dashboard/components/shared/step-config/StepConfigPanel.tsx +564 -0
  60. package/dist/dashboard/components/shared/step-config/StepTester.tsx +165 -0
  61. package/dist/dashboard/components/shared/step-config/TestResultContainer.tsx +57 -0
  62. package/dist/dashboard/components/shared/step-config/TransformTestResults.tsx +130 -0
  63. package/dist/dashboard/components/shared/step-config/ValidateConfigComponent.tsx +334 -0
  64. package/dist/dashboard/components/shared/step-config/index.ts +29 -0
  65. package/dist/dashboard/components/shared/step-config/step-test-handlers.ts +297 -0
  66. package/dist/dashboard/components/shared/trigger-config/TriggerForm.tsx +478 -0
  67. package/dist/dashboard/components/shared/trigger-config/index.ts +1 -0
  68. package/dist/dashboard/components/shared/triggers-panel/TriggersPanel.tsx +281 -0
  69. package/dist/dashboard/components/shared/triggers-panel/index.ts +1 -0
  70. package/dist/dashboard/components/shared/wizard/ConfigurationNameCard.tsx +59 -0
  71. package/dist/dashboard/components/shared/wizard/SummaryCard.tsx +53 -0
  72. package/dist/dashboard/components/shared/wizard/WizardFooter.tsx +47 -0
  73. package/dist/dashboard/components/shared/wizard/WizardProgressBar.tsx +78 -0
  74. package/dist/dashboard/components/shared/wizard/index.ts +4 -0
  75. package/dist/dashboard/components/shared/wizard-trigger/TriggerSchemaFields.tsx +128 -0
  76. package/dist/dashboard/components/shared/wizard-trigger/TriggerSelector.tsx +33 -0
  77. package/dist/dashboard/components/shared/wizard-trigger/index.ts +2 -0
  78. package/dist/dashboard/components/templates/TemplateGallery.tsx +210 -0
  79. package/dist/dashboard/components/templates/TemplatePreview.tsx +214 -0
  80. package/dist/dashboard/components/templates/index.ts +4 -0
  81. package/dist/dashboard/components/wizards/export-wizard/DestinationStep.tsx +207 -0
  82. package/dist/dashboard/components/wizards/export-wizard/ExportWizard.tsx +221 -0
  83. package/dist/dashboard/components/wizards/export-wizard/FieldsStep.tsx +159 -0
  84. package/dist/dashboard/components/wizards/export-wizard/FormatStep.tsx +246 -0
  85. package/dist/dashboard/components/wizards/export-wizard/ReviewStep.tsx +231 -0
  86. package/dist/dashboard/components/wizards/export-wizard/SourceStep.tsx +154 -0
  87. package/dist/dashboard/components/wizards/export-wizard/TriggerStep.tsx +234 -0
  88. package/dist/dashboard/components/wizards/export-wizard/constants.ts +73 -0
  89. package/dist/dashboard/components/wizards/export-wizard/index.ts +2 -0
  90. package/dist/dashboard/components/wizards/export-wizard/types.ts +39 -0
  91. package/dist/dashboard/components/wizards/import-wizard/ImportWizard.tsx +350 -0
  92. package/dist/dashboard/components/wizards/import-wizard/MappingStep.tsx +286 -0
  93. package/dist/dashboard/components/wizards/import-wizard/PreviewStep.tsx +79 -0
  94. package/dist/dashboard/components/wizards/import-wizard/ReviewStep.tsx +266 -0
  95. package/dist/dashboard/components/wizards/import-wizard/SourceStep.tsx +537 -0
  96. package/dist/dashboard/components/wizards/import-wizard/StrategyStep.tsx +328 -0
  97. package/dist/dashboard/components/wizards/import-wizard/TargetStep.tsx +76 -0
  98. package/dist/dashboard/components/wizards/import-wizard/TemplateStep.tsx +116 -0
  99. package/dist/dashboard/components/wizards/import-wizard/TransformStep.tsx +666 -0
  100. package/dist/dashboard/components/wizards/import-wizard/TriggerStep.tsx +51 -0
  101. package/dist/dashboard/components/wizards/import-wizard/constants.ts +104 -0
  102. package/dist/dashboard/components/wizards/import-wizard/index.ts +3 -0
  103. package/dist/dashboard/components/wizards/import-wizard/types.ts +35 -0
  104. package/dist/dashboard/components/wizards/index.ts +7 -0
  105. package/dist/dashboard/components/wizards/shared/WizardStepContainer.tsx +27 -0
  106. package/dist/dashboard/components/wizards/shared/constants.ts +16 -0
  107. package/dist/dashboard/components/wizards/shared/index.ts +10 -0
  108. package/dist/dashboard/constants/colors.ts +25 -0
  109. package/dist/dashboard/constants/connection-defaults.ts +7 -0
  110. package/dist/dashboard/constants/connection-types.ts +1 -0
  111. package/dist/dashboard/constants/defaults.ts +18 -0
  112. package/dist/dashboard/constants/editor.ts +69 -0
  113. package/dist/dashboard/constants/enum-maps.ts +18 -0
  114. package/dist/dashboard/constants/fallbacks.ts +44 -0
  115. package/dist/dashboard/constants/file-format-registry.ts +206 -0
  116. package/dist/dashboard/constants/index.ts +24 -0
  117. package/dist/dashboard/constants/navigation.ts +29 -0
  118. package/dist/dashboard/constants/permissions.ts +41 -0
  119. package/dist/dashboard/constants/placeholders.ts +77 -0
  120. package/dist/dashboard/constants/routes.ts +12 -0
  121. package/dist/dashboard/constants/run-status.ts +1 -0
  122. package/dist/dashboard/constants/sentinel-values.ts +12 -0
  123. package/dist/dashboard/constants/step-configs.ts +9 -0
  124. package/dist/dashboard/constants/step-mappings.ts +170 -0
  125. package/dist/dashboard/constants/steps.ts +37 -0
  126. package/dist/dashboard/constants/toast-messages.ts +149 -0
  127. package/dist/dashboard/constants/triggers.ts +5 -0
  128. package/dist/dashboard/constants/ui-config.ts +139 -0
  129. package/dist/dashboard/constants/ui-dimensions.ts +145 -0
  130. package/dist/dashboard/constants/ui-states.ts +28 -0
  131. package/dist/dashboard/constants/ui-types.ts +85 -0
  132. package/dist/dashboard/constants/validation-patterns.ts +26 -0
  133. package/dist/dashboard/gql/gql.ts +370 -0
  134. package/dist/dashboard/gql/graphql.ts +10378 -0
  135. package/dist/dashboard/gql/index.ts +1 -0
  136. package/dist/dashboard/hooks/api/index.ts +115 -0
  137. package/dist/dashboard/hooks/api/mutation-helpers.ts +34 -0
  138. package/dist/dashboard/hooks/api/use-adapters.ts +92 -0
  139. package/dist/dashboard/hooks/api/use-config-options.ts +513 -0
  140. package/dist/dashboard/hooks/api/use-connections.ts +84 -0
  141. package/dist/dashboard/hooks/api/use-entity-field-schemas.ts +99 -0
  142. package/dist/dashboard/hooks/api/use-entity-loaders.ts +45 -0
  143. package/dist/dashboard/hooks/api/use-hooks.ts +68 -0
  144. package/dist/dashboard/hooks/api/use-logs.ts +102 -0
  145. package/dist/dashboard/hooks/api/use-pipeline-runs.ts +221 -0
  146. package/dist/dashboard/hooks/api/use-pipelines.ts +279 -0
  147. package/dist/dashboard/hooks/api/use-queues.ts +141 -0
  148. package/dist/dashboard/hooks/api/use-secrets.ts +75 -0
  149. package/dist/dashboard/hooks/api/use-settings.ts +55 -0
  150. package/dist/dashboard/hooks/api/use-step-tester.ts +79 -0
  151. package/dist/dashboard/hooks/index.ts +13 -0
  152. package/dist/dashboard/hooks/use-adapter-catalog.ts +253 -0
  153. package/dist/dashboard/hooks/use-export-templates.ts +80 -0
  154. package/dist/dashboard/hooks/use-import-templates.ts +139 -0
  155. package/dist/dashboard/hooks/use-load-more.ts +29 -0
  156. package/dist/dashboard/hooks/use-stable-keys.ts +54 -0
  157. package/dist/dashboard/hooks/use-trigger-types.ts +100 -0
  158. package/dist/dashboard/hooks/use-wizard-navigation.ts +128 -0
  159. package/dist/dashboard/index.tsx +55 -0
  160. package/dist/dashboard/routes/adapters/AdapterCard.tsx +102 -0
  161. package/dist/dashboard/routes/adapters/AdapterConstants.tsx +20 -0
  162. package/dist/dashboard/routes/adapters/AdapterDetail.tsx +208 -0
  163. package/dist/dashboard/routes/adapters/AdapterTypeSection.tsx +105 -0
  164. package/dist/dashboard/routes/adapters/AdaptersPage.tsx +276 -0
  165. package/dist/dashboard/routes/adapters/AdaptersTable.tsx +107 -0
  166. package/dist/dashboard/routes/adapters/index.ts +1 -0
  167. package/dist/dashboard/routes/connections/ConnectionDetail.tsx +218 -0
  168. package/dist/dashboard/routes/connections/ConnectionsList.tsx +34 -0
  169. package/dist/dashboard/routes/connections/index.ts +2 -0
  170. package/dist/dashboard/routes/hooks/Hooks.tsx +425 -0
  171. package/dist/dashboard/routes/hooks/hook-stages.ts +52 -0
  172. package/dist/dashboard/routes/hooks/index.ts +1 -0
  173. package/dist/dashboard/routes/index.ts +8 -0
  174. package/dist/dashboard/routes/logs/Logs.tsx +93 -0
  175. package/dist/dashboard/routes/logs/components/LogDetailDrawer.tsx +118 -0
  176. package/dist/dashboard/routes/logs/components/LogExplorerTab.tsx +367 -0
  177. package/dist/dashboard/routes/logs/components/LogLevelBadge.tsx +34 -0
  178. package/dist/dashboard/routes/logs/components/LogTableRow.tsx +70 -0
  179. package/dist/dashboard/routes/logs/components/LogsOverviewTab.tsx +178 -0
  180. package/dist/dashboard/routes/logs/components/RealtimeLogTab.tsx +122 -0
  181. package/dist/dashboard/routes/logs/index.ts +1 -0
  182. package/dist/dashboard/routes/pipelines/ErrorAuditList.tsx +39 -0
  183. package/dist/dashboard/routes/pipelines/ExportWizardPage.tsx +96 -0
  184. package/dist/dashboard/routes/pipelines/ImportWizardPage.tsx +104 -0
  185. package/dist/dashboard/routes/pipelines/PipelineDetail.tsx +211 -0
  186. package/dist/dashboard/routes/pipelines/PipelineRunsBlock.tsx +377 -0
  187. package/dist/dashboard/routes/pipelines/PipelinesList.tsx +87 -0
  188. package/dist/dashboard/routes/pipelines/RetryPatchHelper.tsx +51 -0
  189. package/dist/dashboard/routes/pipelines/RunDetailsPanel.tsx +238 -0
  190. package/dist/dashboard/routes/pipelines/RunErrorsList.tsx +116 -0
  191. package/dist/dashboard/routes/pipelines/StepCounters.tsx +24 -0
  192. package/dist/dashboard/routes/pipelines/StepSummaryTable.tsx +36 -0
  193. package/dist/dashboard/routes/pipelines/components/DryRunDialog.tsx +341 -0
  194. package/dist/dashboard/routes/pipelines/components/PipelineActionButtons.tsx +201 -0
  195. package/dist/dashboard/routes/pipelines/components/PipelineEditorToggle.tsx +116 -0
  196. package/dist/dashboard/routes/pipelines/components/PipelineFormFields.tsx +156 -0
  197. package/dist/dashboard/routes/pipelines/components/PipelineWebhookInfo.tsx +111 -0
  198. package/dist/dashboard/routes/pipelines/components/ReviewActionsPanel.tsx +342 -0
  199. package/dist/dashboard/routes/pipelines/components/ValidationPanel.tsx +121 -0
  200. package/dist/dashboard/routes/pipelines/components/VersionHistoryDialog.tsx +131 -0
  201. package/dist/dashboard/routes/pipelines/components/index.ts +25 -0
  202. package/dist/dashboard/routes/pipelines/hooks/index.ts +1 -0
  203. package/dist/dashboard/routes/pipelines/hooks/use-pipeline-validation.ts +114 -0
  204. package/dist/dashboard/routes/pipelines/index.ts +4 -0
  205. package/dist/dashboard/routes/pipelines/utils/index.ts +1 -0
  206. package/dist/dashboard/routes/pipelines/utils/pipeline-conversion.ts +261 -0
  207. package/dist/dashboard/routes/queues/ConsumersTable.tsx +134 -0
  208. package/dist/dashboard/routes/queues/DeadLettersTable.tsx +118 -0
  209. package/dist/dashboard/routes/queues/FailedRunsTable.tsx +74 -0
  210. package/dist/dashboard/routes/queues/QueuesPage.tsx +290 -0
  211. package/dist/dashboard/routes/queues/index.ts +1 -0
  212. package/dist/dashboard/routes/queues/types.ts +22 -0
  213. package/dist/dashboard/routes/secrets/SecretDetail.tsx +278 -0
  214. package/dist/dashboard/routes/secrets/SecretsList.tsx +34 -0
  215. package/dist/dashboard/routes/secrets/index.ts +2 -0
  216. package/dist/dashboard/routes/settings/Settings.tsx +343 -0
  217. package/dist/dashboard/routes/settings/index.ts +1 -0
  218. package/dist/dashboard/types/index.ts +89 -0
  219. package/dist/dashboard/types/pipeline.ts +51 -0
  220. package/dist/dashboard/types/ui-types.ts +400 -0
  221. package/dist/dashboard/types/wizard.ts +235 -0
  222. package/dist/dashboard/utils/adapter-grouping.ts +43 -0
  223. package/dist/dashboard/utils/column-analysis.ts +11 -0
  224. package/dist/dashboard/utils/field-preparation.ts +31 -0
  225. package/dist/dashboard/utils/form-validation.ts +373 -0
  226. package/dist/dashboard/utils/formatters.ts +92 -0
  227. package/dist/dashboard/utils/icon-resolver.ts +35 -0
  228. package/dist/dashboard/utils/index.ts +60 -0
  229. package/dist/dashboard/utils/query-key-factory.ts +54 -0
  230. package/dist/dashboard/utils/step-helpers.ts +32 -0
  231. package/dist/dashboard/utils/string-helpers.ts +4 -0
  232. package/dist/dashboard/utils/template-helpers.ts +26 -0
  233. package/dist/dashboard/utils/trigger-sync.ts +138 -0
  234. package/dist/dashboard/utils/wizard-to-pipeline.ts +569 -0
  235. package/package.json +2 -2
@@ -0,0 +1,121 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ Dialog,
5
+ DialogClose,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@vendure/dashboard';
11
+ import type { ValidationIssue, ValidationState } from '../../../types';
12
+ import { DIALOG_DIMENSIONS, SCROLL_HEIGHTS } from '../../../constants';
13
+
14
+ export interface ValidationPanelProps {
15
+ validation: ValidationState;
16
+ isLoading: boolean;
17
+ open: boolean;
18
+ onOpenChange: (open: boolean) => void;
19
+ }
20
+
21
+ export function ValidationPanel({
22
+ validation,
23
+ isLoading,
24
+ open,
25
+ onOpenChange,
26
+ }: ValidationPanelProps) {
27
+ return (
28
+ <Dialog open={open} onOpenChange={onOpenChange}>
29
+ <DialogContent className={DIALOG_DIMENSIONS.MAX_WIDTH_2XL} data-testid="validation-dialog">
30
+ <DialogHeader>
31
+ <DialogTitle>Validation Issues</DialogTitle>
32
+ <DialogDescription>Fix the following before publishing.</DialogDescription>
33
+ </DialogHeader>
34
+ <div className={`space-y-2 ${SCROLL_HEIGHTS.VALIDATION_PANEL} overflow-auto`}>
35
+ {isLoading ? (
36
+ <div className="flex items-center gap-2 text-muted-foreground py-4 justify-center">
37
+ <div className="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" />
38
+ <span>Validating...</span>
39
+ </div>
40
+ ) : validation.issues.length === 0 ? (
41
+ <div className="text-sm text-muted-foreground">No issues.</div>
42
+ ) : (
43
+ validation.issues.map((issue) => (
44
+ <ValidationIssueItem key={`${issue.stepKey ?? 'global'}-${issue.field ?? 'none'}-${issue.message}`} issue={issue} />
45
+ ))
46
+ )}
47
+ {validation.warnings.length > 0 && (
48
+ <div className="mt-4">
49
+ <div className="text-sm font-medium text-amber-800 mb-2">Warnings</div>
50
+ {validation.warnings.map((warning) => (
51
+ <ValidationIssueItem key={`warning-${warning.stepKey ?? 'global'}-${warning.field ?? 'none'}-${warning.message}`} issue={warning} variant="warning" />
52
+ ))}
53
+ </div>
54
+ )}
55
+ </div>
56
+ <div className="flex justify-end">
57
+ <DialogClose asChild>
58
+ <Button variant="outline" data-testid="validation-dialog-close-button">Close</Button>
59
+ </DialogClose>
60
+ </div>
61
+ </DialogContent>
62
+ </Dialog>
63
+ );
64
+ }
65
+
66
+ interface ValidationIssueItemProps {
67
+ issue: ValidationIssue;
68
+ variant?: 'error' | 'warning';
69
+ }
70
+
71
+ function ValidationIssueItem({ issue, variant = 'error' }: ValidationIssueItemProps) {
72
+ const bgClass = variant === 'warning' ? 'bg-amber-50 dark:bg-amber-950/30 border-amber-200 dark:border-amber-800' : '';
73
+
74
+ return (
75
+ <div className={`border rounded p-2 ${bgClass}`}>
76
+ <div className="text-sm">{issue.message}</div>
77
+ <div className="text-xs text-muted-foreground">
78
+ {issue.stepKey ? `Step: ${issue.stepKey}` : ''}
79
+ {issue.field ? ` - Field: ${issue.field}` : ''}
80
+ {issue.reason ? ` - Code: ${issue.reason}` : ''}
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Validation status badge component for inline display
88
+ */
89
+ export interface ValidationStatusBadgeProps {
90
+ validation: ValidationState;
91
+ isLoading: boolean;
92
+ onShowIssues: () => void;
93
+ }
94
+
95
+ export function ValidationStatusBadge({
96
+ validation,
97
+ isLoading,
98
+ onShowIssues,
99
+ }: ValidationStatusBadgeProps) {
100
+ if (isLoading) {
101
+ return <span className="text-xs text-muted-foreground">Validating...</span>;
102
+ }
103
+
104
+ if (validation.isValid === true) {
105
+ return <span className="text-xs px-2 py-0.5 rounded bg-emerald-100 text-emerald-800">Valid</span>;
106
+ }
107
+
108
+ if (validation.isValid === false) {
109
+ return (
110
+ <button
111
+ type="button"
112
+ className="text-xs px-2 py-0.5 rounded bg-amber-100 text-amber-800 hover:underline"
113
+ onClick={onShowIssues}
114
+ >
115
+ Issues: {validation.count}
116
+ </button>
117
+ );
118
+ }
119
+
120
+ return null;
121
+ }
@@ -0,0 +1,131 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Badge,
4
+ Button,
5
+ Dialog,
6
+ DialogClose,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '@vendure/dashboard';
12
+ import { useQuery } from '@tanstack/react-query';
13
+ import { api } from '@vendure/dashboard';
14
+ import { pipelineTimelineDocument, pipelineKeys } from '../../../hooks';
15
+ import { REVISION_TYPE, UI_LIMITS, DIALOG_DIMENSIONS, RUN_STATUS } from '../../../constants';
16
+ import { formatDateTime } from '../../../utils';
17
+
18
+ export interface TimelineEntry {
19
+ revision: {
20
+ id: string;
21
+ createdAt: string;
22
+ version: number;
23
+ type: string;
24
+ commitMessage?: string | null;
25
+ authorName?: string | null;
26
+ changesSummary?: unknown;
27
+ isLatest: boolean;
28
+ isCurrent: boolean;
29
+ };
30
+ runCount: number;
31
+ lastRunAt?: string | null;
32
+ lastRunStatus?: string | null;
33
+ }
34
+
35
+ export interface VersionHistoryDialogProps {
36
+ open: boolean;
37
+ onOpenChange: (open: boolean) => void;
38
+ pipelineId: string | undefined;
39
+ }
40
+
41
+ export function VersionHistoryDialog({
42
+ open,
43
+ onOpenChange,
44
+ pipelineId,
45
+ }: VersionHistoryDialogProps) {
46
+ const { data: timeline = [], isPending: historyPending, isError } = useQuery({
47
+ queryKey: pipelineKeys.timeline(pipelineId ?? '', UI_LIMITS.TIMELINE_LIMIT),
48
+ queryFn: () =>
49
+ api.query(pipelineTimelineDocument, { pipelineId: pipelineId!, limit: UI_LIMITS.TIMELINE_LIMIT })
50
+ .then(res => (res?.dataHubPipelineTimeline ?? []) as TimelineEntry[]),
51
+ enabled: open && !!pipelineId,
52
+ });
53
+
54
+ return (
55
+ <Dialog open={open} onOpenChange={onOpenChange}>
56
+ <DialogContent className={`${DIALOG_DIMENSIONS.MAX_WIDTH_2XL} ${DIALOG_DIMENSIONS.MAX_HEIGHT_80VH} flex flex-col`}>
57
+ <DialogHeader className="flex-none">
58
+ <DialogTitle>Version history</DialogTitle>
59
+ <DialogDescription>Timeline of pipeline revisions</DialogDescription>
60
+ </DialogHeader>
61
+ <div className="flex-1 min-h-0 overflow-auto">
62
+ {isError ? (
63
+ <p className="text-sm text-destructive text-center py-4">Failed to load version history</p>
64
+ ) : historyPending ? (
65
+ <div className="flex items-center justify-center py-8 text-muted-foreground">Loading...</div>
66
+ ) : timeline.length === 0 ? (
67
+ <div className="flex items-center justify-center py-8 text-muted-foreground">No version history</div>
68
+ ) : (
69
+ <div className="space-y-2">
70
+ {timeline.map((entry) => (
71
+ <div
72
+ key={entry.revision.id}
73
+ className={`border rounded-md p-3 ${entry.revision.isCurrent ? 'border-primary bg-primary/5' : ''}`}
74
+ >
75
+ <div className="flex items-center justify-between mb-1">
76
+ <div className="flex items-center gap-2">
77
+ <span className="font-medium">
78
+ {entry.revision.type === REVISION_TYPE.PUBLISHED ? `v${entry.revision.version}` : 'Draft'}
79
+ </span>
80
+ {entry.revision.isCurrent && (
81
+ <Badge variant="default" className="text-xs">Current</Badge>
82
+ )}
83
+ {entry.revision.isLatest && !entry.revision.isCurrent && (
84
+ <Badge variant="secondary" className="text-xs">Latest</Badge>
85
+ )}
86
+ <Badge variant={entry.revision.type === REVISION_TYPE.PUBLISHED ? 'default' : 'outline'} className="text-xs">
87
+ {entry.revision.type}
88
+ </Badge>
89
+ </div>
90
+ <span className="text-xs text-muted-foreground">
91
+ {formatDateTime(entry.revision.createdAt)}
92
+ </span>
93
+ </div>
94
+ {entry.revision.commitMessage && (
95
+ <div className="text-sm text-foreground mb-1">{entry.revision.commitMessage}</div>
96
+ )}
97
+ <div className="flex items-center gap-4 text-xs text-muted-foreground">
98
+ {entry.revision.authorName && <span>by {entry.revision.authorName}</span>}
99
+ {entry.runCount > 0 && <span>{entry.runCount} run{entry.runCount !== 1 ? 's' : ''}</span>}
100
+ {entry.lastRunStatus && (
101
+ <span className={entry.lastRunStatus === RUN_STATUS.COMPLETED ? 'text-green-600' : entry.lastRunStatus === RUN_STATUS.FAILED ? 'text-red-600' : ''}>
102
+ Last: {entry.lastRunStatus}
103
+ </span>
104
+ )}
105
+ </div>
106
+ {entry.revision.changesSummary && typeof entry.revision.changesSummary === 'object' && (
107
+ <div className="mt-2 text-xs text-muted-foreground">
108
+ {(() => {
109
+ const cs = entry.revision.changesSummary as { stepsAdded?: string[]; stepsRemoved?: string[]; stepsModified?: string[]; totalChanges?: number };
110
+ const parts: string[] = [];
111
+ if (cs.stepsAdded?.length) parts.push(`+${cs.stepsAdded.length} step${cs.stepsAdded.length > 1 ? 's' : ''}`);
112
+ if (cs.stepsRemoved?.length) parts.push(`-${cs.stepsRemoved.length} step${cs.stepsRemoved.length > 1 ? 's' : ''}`);
113
+ if (cs.stepsModified?.length) parts.push(`~${cs.stepsModified.length} modified`);
114
+ return parts.length ? parts.join(', ') : `${cs.totalChanges ?? 0} changes`;
115
+ })()}
116
+ </div>
117
+ )}
118
+ </div>
119
+ ))}
120
+ </div>
121
+ )}
122
+ </div>
123
+ <div className="flex justify-end gap-2 pt-3 flex-none">
124
+ <DialogClose asChild>
125
+ <Button variant="secondary">Close</Button>
126
+ </DialogClose>
127
+ </div>
128
+ </DialogContent>
129
+ </Dialog>
130
+ );
131
+ }
@@ -0,0 +1,25 @@
1
+ export { DryRunDialog } from './DryRunDialog';
2
+ export type { DryRunDialogProps } from './DryRunDialog';
3
+
4
+ export { VersionHistoryDialog } from './VersionHistoryDialog';
5
+ export type { VersionHistoryDialogProps, TimelineEntry } from './VersionHistoryDialog';
6
+
7
+ export { ValidationPanel } from './ValidationPanel';
8
+ export type { ValidationPanelProps } from './ValidationPanel';
9
+ // Re-export ValidationState from central types for convenience
10
+ export type { ValidationState } from '../../../types';
11
+
12
+ export { PipelineActionButtons } from './PipelineActionButtons';
13
+ export type { PipelineActionButtonsProps } from './PipelineActionButtons';
14
+
15
+ export { ReviewActionsPanel } from './ReviewActionsPanel';
16
+ export type { ReviewActionsPanelProps } from './ReviewActionsPanel';
17
+
18
+ export { PipelineWebhookInfo } from './PipelineWebhookInfo';
19
+ export type { PipelineWebhookInfoProps } from './PipelineWebhookInfo';
20
+
21
+ export { PipelineEditorToggle } from './PipelineEditorToggle';
22
+ export type { PipelineEditorToggleProps, EditorMode } from './PipelineEditorToggle';
23
+
24
+ export { PipelineFormFields } from './PipelineFormFields';
25
+ export type { PipelineFormFieldsProps } from './PipelineFormFields';
@@ -0,0 +1 @@
1
+ export { usePipelineValidation } from './use-pipeline-validation';
@@ -0,0 +1,114 @@
1
+ import * as React from 'react';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ import { api } from '@vendure/dashboard';
4
+ import { validatePipelineDefinitionDocument } from '../../../hooks';
5
+ import type { PipelineValidationResult, ValidationIssue } from '../../../types';
6
+ import { getErrorMessage } from '../../../../shared';
7
+ import type { ValidationState } from '../components';
8
+
9
+ /** Debounce delay (ms) before triggering validation after definition changes. */
10
+ const VALIDATION_DEBOUNCE_MS = 300;
11
+
12
+ const EMPTY_VALIDATION: ValidationState = {
13
+ isValid: null,
14
+ count: 0,
15
+ issues: [],
16
+ warnings: [],
17
+ };
18
+
19
+ /**
20
+ * Parse the raw validation API response into a ValidationState.
21
+ */
22
+ function parseValidationResult(out: PipelineValidationResult | undefined): ValidationState {
23
+ const issues: ValidationIssue[] = Array.isArray(out?.issues)
24
+ ? out.issues.map((i) => ({
25
+ message: i.message,
26
+ stepKey: i.stepKey ?? null,
27
+ reason: i.reason ?? null,
28
+ field: i.field ?? null,
29
+ }))
30
+ : [];
31
+
32
+ const warnings: ValidationIssue[] = Array.isArray(out?.warnings)
33
+ ? out.warnings.map((i) => ({
34
+ message: i.message,
35
+ stepKey: i.stepKey ?? null,
36
+ reason: i.reason ?? null,
37
+ field: i.field ?? null,
38
+ }))
39
+ : [];
40
+
41
+ return {
42
+ isValid: Boolean(out?.isValid),
43
+ count: issues.length,
44
+ issues,
45
+ warnings,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Hook for auto-validating a pipeline definition with debouncing.
51
+ *
52
+ * Uses React Query `useMutation` for the API call while preserving
53
+ * debounce (300ms) and race condition protection via a request counter.
54
+ *
55
+ * @param definition - The current pipeline definition to validate
56
+ * @returns Validation state and pending status
57
+ */
58
+ export function usePipelineValidation(definition: unknown): {
59
+ validation: ValidationState;
60
+ validationPending: boolean;
61
+ setValidation: React.Dispatch<React.SetStateAction<ValidationState>>;
62
+ } {
63
+ const [validation, setValidation] = React.useState<ValidationState>(EMPTY_VALIDATION);
64
+ const requestIdRef = React.useRef(0);
65
+
66
+ const validateMutation = useMutation({
67
+ mutationFn: async (def: unknown) => {
68
+ const res = await api.query(validatePipelineDefinitionDocument, {
69
+ definition: def,
70
+ level: 'FULL',
71
+ });
72
+ return res?.validateDataHubPipelineDefinition as PipelineValidationResult | undefined;
73
+ },
74
+ });
75
+
76
+ React.useEffect(() => {
77
+ if (!definition) {
78
+ setValidation(EMPTY_VALIDATION);
79
+ validateMutation.reset();
80
+ return;
81
+ }
82
+
83
+ const requestId = ++requestIdRef.current;
84
+
85
+ const timer = setTimeout(async () => {
86
+ try {
87
+ const out = await validateMutation.mutateAsync(definition);
88
+ if (requestId !== requestIdRef.current) return;
89
+ setValidation(parseValidationResult(out));
90
+ } catch (e) {
91
+ if (requestId !== requestIdRef.current) return;
92
+ setValidation({
93
+ isValid: false,
94
+ count: 1,
95
+ issues: [
96
+ {
97
+ message: getErrorMessage(e),
98
+ },
99
+ ],
100
+ warnings: [],
101
+ });
102
+ }
103
+ }, VALIDATION_DEBOUNCE_MS);
104
+
105
+ return () => {
106
+ clearTimeout(timer);
107
+ };
108
+ // validateMutation is intentionally excluded: including it would cause infinite
109
+ // re-triggers since useMutation returns a new object each render.
110
+ // eslint-disable-next-line react-hooks/exhaustive-deps
111
+ }, [definition]);
112
+
113
+ return { validation, validationPending: validateMutation.isPending, setValidation };
114
+ }
@@ -0,0 +1,4 @@
1
+ export { pipelinesList } from './PipelinesList';
2
+ export { pipelineDetail } from './PipelineDetail';
3
+ export { importWizardPage } from './ImportWizardPage';
4
+ export { exportWizardPage } from './ExportWizardPage';
@@ -0,0 +1 @@
1
+ export { toVisualDefinition, toCanonicalDefinition, layoutDagNodes } from './pipeline-conversion';
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Utility functions for converting between canonical (steps-based) and visual (nodes/edges-based)
3
+ * pipeline definitions.
4
+ */
5
+ import type { PipelineDefinition } from '../../../../shared/types';
6
+ import type { VisualPipelineDefinition, PipelineNode } from '../../../types';
7
+ import { NODE_LAYOUT, mapStepTypeToCategory, mapCategoryToStepType } from '../../../constants';
8
+
9
+ /**
10
+ * Calculate node levels in DAG based on edge connections.
11
+ * Nodes at the same level can run in parallel.
12
+ * Protected against cycles with max iteration limit.
13
+ */
14
+ function calculateNodeLevels(
15
+ nodes: PipelineNode[],
16
+ edges: Array<{ source: string; target: string }>
17
+ ): Map<string, number> {
18
+ const levels = new Map<string, number>();
19
+ const incomingEdges = new Map<string, string[]>();
20
+ const outgoingEdges = new Map<string, string[]>();
21
+
22
+ for (const node of nodes) {
23
+ incomingEdges.set(node.id, []);
24
+ outgoingEdges.set(node.id, []);
25
+ }
26
+
27
+ for (const edge of edges) {
28
+ const incoming = incomingEdges.get(edge.target) ?? [];
29
+ incoming.push(edge.source);
30
+ incomingEdges.set(edge.target, incoming);
31
+
32
+ const outgoing = outgoingEdges.get(edge.source) ?? [];
33
+ outgoing.push(edge.target);
34
+ outgoingEdges.set(edge.source, outgoing);
35
+ }
36
+
37
+ const entryNodes = nodes.filter(n => (incomingEdges.get(n.id) ?? []).length === 0);
38
+ for (const node of entryNodes) {
39
+ levels.set(node.id, 0);
40
+ }
41
+
42
+ const queue = [...entryNodes.map(n => n.id)];
43
+ const maxIterations = nodes.length * edges.length + nodes.length;
44
+ let iterations = 0;
45
+
46
+ while (queue.length > 0 && iterations < maxIterations) {
47
+ iterations++;
48
+ const nodeId = queue.shift()!;
49
+ const currentLevel = levels.get(nodeId) ?? 0;
50
+
51
+ for (const targetId of outgoingEdges.get(nodeId) ?? []) {
52
+ const existingLevel = levels.get(targetId);
53
+ const newLevel = currentLevel + 1;
54
+
55
+ if (existingLevel === undefined || newLevel > existingLevel) {
56
+ levels.set(targetId, newLevel);
57
+ queue.push(targetId);
58
+ }
59
+ }
60
+ }
61
+
62
+ for (const node of nodes) {
63
+ if (!levels.has(node.id)) {
64
+ levels.set(node.id, 0);
65
+ }
66
+ }
67
+
68
+ return levels;
69
+ }
70
+
71
+ /**
72
+ * Auto-layout nodes in DAG format - parallel nodes stacked vertically.
73
+ */
74
+ export function layoutDagNodes(def: VisualPipelineDefinition): VisualPipelineDefinition {
75
+ const nodes = def.nodes ?? [];
76
+ const edges = def.edges ?? [];
77
+
78
+ if (nodes.length === 0) {
79
+ return def;
80
+ }
81
+
82
+ const levels = calculateNodeLevels(nodes, edges);
83
+
84
+ const nodesByLevel = new Map<number, PipelineNode[]>();
85
+ for (const node of nodes) {
86
+ const level = levels.get(node.id) ?? 0;
87
+ const levelNodes = nodesByLevel.get(level) ?? [];
88
+ levelNodes.push(node);
89
+ nodesByLevel.set(level, levelNodes);
90
+ }
91
+
92
+ const levelValues = Array.from(levels.values());
93
+ const maxLevel = levelValues.length > 0 ? Math.max(...levelValues) : 0;
94
+ const repositionedNodes: PipelineNode[] = [];
95
+
96
+ for (let level = 0; level <= maxLevel; level++) {
97
+ const levelNodes = nodesByLevel.get(level) ?? [];
98
+ const count = levelNodes.length;
99
+ const centerY = NODE_LAYOUT.INITIAL_Y;
100
+ const startY = centerY - ((count - 1) * NODE_LAYOUT.SPACING_Y) / 2;
101
+
102
+ for (let i = 0; i < levelNodes.length; i++) {
103
+ const node = levelNodes[i];
104
+ repositionedNodes.push({
105
+ ...node,
106
+ position: {
107
+ x: NODE_LAYOUT.INITIAL_X + level * NODE_LAYOUT.SPACING_X,
108
+ y: startY + i * NODE_LAYOUT.SPACING_Y,
109
+ },
110
+ });
111
+ }
112
+ }
113
+
114
+ return {
115
+ ...def,
116
+ nodes: repositionedNodes,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Convert a canonical pipeline definition to a visual definition for the ReactFlow editor.
122
+ *
123
+ * @param def - The canonical or visual pipeline definition
124
+ * @returns A visual pipeline definition with nodes and edges
125
+ */
126
+ export function toVisualDefinition(
127
+ def: PipelineDefinition | VisualPipelineDefinition | undefined
128
+ ): VisualPipelineDefinition {
129
+ if (!def) {
130
+ return {
131
+ nodes: [],
132
+ edges: [],
133
+ variables: {},
134
+ capabilities: undefined,
135
+ dependsOn: undefined,
136
+ trigger: undefined,
137
+ };
138
+ }
139
+
140
+ // Already in visual format
141
+ if ('nodes' in def && def.nodes) {
142
+ return def as VisualPipelineDefinition;
143
+ }
144
+
145
+ const canonicalDef = def as PipelineDefinition;
146
+ const steps = Array.isArray(canonicalDef.steps) ? canonicalDef.steps : [];
147
+
148
+ const nodes: PipelineNode[] = steps.map((step, i) => {
149
+ const id = String(step.key ?? `step-${i}`);
150
+ const category = mapStepTypeToCategory(step.type);
151
+ const adapterCode = step.config?.adapterCode;
152
+ const label = step.name || step.key || `Step ${i + 1}`;
153
+
154
+ return {
155
+ id,
156
+ type: category,
157
+ position: { x: 0, y: 0 },
158
+ data: {
159
+ label,
160
+ type: category,
161
+ adapterCode,
162
+ config: step.config ?? {},
163
+ },
164
+ };
165
+ });
166
+
167
+ const canonicalEdges = canonicalDef.edges;
168
+ let edges: Array<{ id: string; source: string; target: string }>;
169
+
170
+ if (Array.isArray(canonicalEdges) && canonicalEdges.length) {
171
+ edges = canonicalEdges.map((e, idx) => ({
172
+ id: String(e.id ?? `edge-${idx}`),
173
+ source: String(e.from),
174
+ target: String(e.to),
175
+ }));
176
+ } else {
177
+ edges = nodes.slice(1).map((n, i) => ({
178
+ id: `edge-${i}`,
179
+ source: nodes[i].id,
180
+ target: n.id,
181
+ }));
182
+ }
183
+
184
+ const visualDef: VisualPipelineDefinition = {
185
+ nodes,
186
+ edges,
187
+ variables: canonicalDef.context ?? {},
188
+ capabilities: canonicalDef.capabilities,
189
+ dependsOn: canonicalDef.dependsOn,
190
+ trigger: canonicalDef.trigger,
191
+ };
192
+
193
+ return layoutDagNodes(visualDef);
194
+ }
195
+
196
+ /**
197
+ * Convert a visual pipeline definition back to canonical format for storage.
198
+ *
199
+ * @param def - The visual or canonical pipeline definition
200
+ * @returns A canonical pipeline definition with steps and edges
201
+ */
202
+ export function toCanonicalDefinition(
203
+ def: VisualPipelineDefinition | PipelineDefinition | undefined
204
+ ): PipelineDefinition {
205
+ if (!def) {
206
+ return { version: 1, steps: [] };
207
+ }
208
+
209
+ // Check if it's a visual definition with nodes
210
+ if ('nodes' in def && Array.isArray(def.nodes) && def.nodes.length > 0) {
211
+ const visualDef = def as VisualPipelineDefinition;
212
+ const steps = visualDef.nodes.map((node, idx) => {
213
+ const stepType = mapCategoryToStepType(node.data?.type);
214
+ const adapterCode =
215
+ node.data?.adapterCode || (node.data?.config?.adapterCode as string) || '';
216
+ const existingConfig = node.data?.config ?? {};
217
+ const { adapterCode: _, ...restConfig } = existingConfig;
218
+
219
+ return {
220
+ key: node.id ?? `step-${idx}`,
221
+ type: stepType,
222
+ name: node.data?.label,
223
+ config: {
224
+ ...restConfig,
225
+ adapterCode,
226
+ },
227
+ };
228
+ });
229
+
230
+ const edges = (visualDef.edges ?? []).map((e, i) => ({
231
+ id: e.id ?? `edge-${i}`,
232
+ from: e.source,
233
+ to: e.target,
234
+ }));
235
+
236
+ return {
237
+ version: 1,
238
+ steps,
239
+ edges,
240
+ context: visualDef.variables ?? {},
241
+ ...(visualDef.capabilities ? { capabilities: visualDef.capabilities } : {}),
242
+ ...(visualDef.dependsOn ? { dependsOn: visualDef.dependsOn } : {}),
243
+ ...(visualDef.trigger ? { trigger: visualDef.trigger } : {}),
244
+ };
245
+ }
246
+
247
+ // Check if it's already canonical format
248
+ if ('steps' in def && Array.isArray(def.steps)) {
249
+ const canonicalDef = def as PipelineDefinition;
250
+ return {
251
+ ...canonicalDef,
252
+ version:
253
+ typeof canonicalDef.version === 'number' && canonicalDef.version > 0
254
+ ? canonicalDef.version
255
+ : 1,
256
+ };
257
+ }
258
+
259
+ // Fallback: return default with any extra properties
260
+ return { version: 1, steps: [], ...def };
261
+ }