@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,211 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ DashboardRouteDefinition,
5
+ Page,
6
+ PageActionBar,
7
+ PageActionBarRight,
8
+ PageBlock,
9
+ PageTitle,
10
+ PermissionGuard,
11
+ detailPageRouteLoader,
12
+ useDetailPage,
13
+ } from '@vendure/dashboard';
14
+ import { AnyRoute, useNavigate } from '@tanstack/react-router';
15
+ import { useQueryClient } from '@tanstack/react-query';
16
+ import { toast } from 'sonner';
17
+ import { getErrorMessage } from '../../../shared';
18
+ import { DATAHUB_PERMISSIONS, ROUTES, TOAST_PIPELINE } from '../../constants';
19
+ import {
20
+ createPipelineDocument,
21
+ pipelineDetailDocument,
22
+ updatePipelineDocument,
23
+ pipelineKeys,
24
+ } from '../../hooks';
25
+ import type { PipelineDefinition, ValidationIssue, PipelineEntity } from '../../types';
26
+ import { PipelineRunsBlock } from './PipelineRunsBlock';
27
+ import {
28
+ DryRunDialog,
29
+ VersionHistoryDialog,
30
+ ValidationPanel,
31
+ PipelineActionButtons,
32
+ PipelineWebhookInfo,
33
+ PipelineEditorToggle,
34
+ PipelineFormFields,
35
+ ReviewActionsPanel,
36
+ } from './components';
37
+ import { usePipelineValidation } from './hooks';
38
+
39
+ export const pipelineDetail: DashboardRouteDefinition = {
40
+ path: `${ROUTES.PIPELINES}/$id`,
41
+ loader: detailPageRouteLoader({
42
+ queryDocument: pipelineDetailDocument,
43
+ breadcrumb: (isNew, entity) => [
44
+ { path: ROUTES.PIPELINES, label: 'Data Hub' },
45
+ isNew ? 'New pipeline' : entity?.name,
46
+ ],
47
+ }),
48
+ component: route => (
49
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.READ_PIPELINE]}>
50
+ <PipelineDetailPage route={route} />
51
+ </PermissionGuard>
52
+ ),
53
+ };
54
+
55
+ function PipelineDetailPage({ route }: { route: AnyRoute }) {
56
+ const params = route.useParams();
57
+ const navigate = useNavigate();
58
+ const queryClient = useQueryClient();
59
+ const creating = params.id === 'new';
60
+
61
+ const { form, submitHandler, entity, isPending, resetForm, refreshEntity } = useDetailPage({
62
+ queryDocument: pipelineDetailDocument,
63
+ createDocument: createPipelineDocument,
64
+ updateDocument: updatePipelineDocument,
65
+ setValuesForCreate: () => ({
66
+ code: '',
67
+ name: '',
68
+ enabled: true,
69
+ definition: { steps: [] },
70
+ }),
71
+ setValuesForUpdate: p => ({
72
+ id: p?.id ?? '',
73
+ code: p?.code ?? '',
74
+ name: p?.name ?? '',
75
+ enabled: p?.enabled ?? true,
76
+ definition: p?.definition ?? {},
77
+ }),
78
+ params: { id: params.id },
79
+ onSuccess: async data => {
80
+ toast.success(TOAST_PIPELINE.SAVE_SUCCESS);
81
+ resetForm();
82
+ if (creating) {
83
+ await navigate({ to: `../$id`, params: { id: data.id } });
84
+ }
85
+ },
86
+ onError: err => {
87
+ toast.error(TOAST_PIPELINE.SAVE_ERROR, {
88
+ description: getErrorMessage(err),
89
+ });
90
+ },
91
+ });
92
+
93
+ // Dialog states
94
+ const [dryRunOpen, setDryRunOpen] = React.useState(false);
95
+ const [historyOpen, setHistoryOpen] = React.useState(false);
96
+ const [issuesOpen, setIssuesOpen] = React.useState(false);
97
+
98
+ // Validation
99
+ const watchedDefinition = form.watch('definition');
100
+ const { validation, validationPending, setValidation } = usePipelineValidation(watchedDefinition);
101
+
102
+ // Callbacks
103
+ const handleImport = React.useCallback((def: PipelineDefinition) => {
104
+ form.setValue('definition', def, { shouldDirty: true });
105
+ }, [form]);
106
+
107
+ const handleValidationFailed = React.useCallback((issues: ValidationIssue[]) => {
108
+ setValidation({ isValid: false, count: issues.length, issues, warnings: [] });
109
+ setIssuesOpen(true);
110
+ }, [setValidation]);
111
+
112
+ const handleStatusChange = React.useCallback(() => {
113
+ queryClient.invalidateQueries({ queryKey: pipelineKeys.lists() });
114
+ refreshEntity();
115
+ }, [queryClient, refreshEntity]);
116
+
117
+ // Scroll to runs section if hash is #runs
118
+ React.useEffect(() => {
119
+ if (typeof window !== 'undefined' && window.location.hash === '#runs') {
120
+ const el = document.getElementById('runs');
121
+ if (el) {
122
+ try { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch { el.scrollIntoView(); }
123
+ }
124
+ }
125
+ }, []);
126
+
127
+ const pipelineEntity = entity as PipelineEntity | undefined;
128
+
129
+ return (
130
+ <>
131
+ <Page pageId="data-hub-pipeline-detail" form={form} submitHandler={submitHandler}>
132
+ <PageTitle>{creating ? 'New pipeline' : entity?.name ?? ''}</PageTitle>
133
+ <PageActionBar>
134
+ <PageActionBarRight>
135
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.UPDATE_PIPELINE]}>
136
+ <Button
137
+ type="submit"
138
+ disabled={
139
+ !form.formState.isDirty ||
140
+ !form.formState.isValid ||
141
+ isPending ||
142
+ validationPending ||
143
+ validation.isValid === false
144
+ }
145
+ title={validation.isValid === false ? `Cannot save: ${validation.count} validation error(s)` : undefined}
146
+ >
147
+ {creating ? 'Create' : 'Update'}
148
+ </Button>
149
+ </PermissionGuard>
150
+ <PipelineActionButtons
151
+ entityId={entity?.id}
152
+ status={pipelineEntity?.status}
153
+ definition={form.getValues('definition') as PipelineDefinition | undefined}
154
+ creating={creating}
155
+ onImport={handleImport}
156
+ onOpenDryRun={() => setDryRunOpen(true)}
157
+ onOpenHistory={() => setHistoryOpen(true)}
158
+ onValidationFailed={handleValidationFailed}
159
+ onStatusChange={handleStatusChange}
160
+ />
161
+ </PageActionBarRight>
162
+ </PageActionBar>
163
+ <div className="w-full space-y-4">
164
+ <PageBlock column="main" blockId="main-form">
165
+ <PipelineFormFields
166
+ form={form}
167
+ creating={creating}
168
+ entity={pipelineEntity}
169
+ validation={validation}
170
+ validationPending={validationPending}
171
+ onShowIssues={() => setIssuesOpen(true)}
172
+ />
173
+ {!creating && (
174
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.UPDATE_PIPELINE]}>
175
+ <div className="mt-6">
176
+ <ReviewActionsPanel
177
+ entityId={entity?.id}
178
+ status={pipelineEntity?.status}
179
+ onStatusChange={handleStatusChange}
180
+ />
181
+ </div>
182
+ </PermissionGuard>
183
+ )}
184
+ <PipelineEditorToggle form={form} issues={validation.issues} />
185
+ <PipelineWebhookInfo definition={() => form.getValues('definition') as PipelineDefinition | undefined} />
186
+ </PageBlock>
187
+ {!creating && <PipelineRunsBlock pipelineId={entity?.id} />}
188
+ </div>
189
+ </Page>
190
+
191
+ <ValidationPanel
192
+ validation={validation}
193
+ isLoading={validationPending}
194
+ open={issuesOpen}
195
+ onOpenChange={setIssuesOpen}
196
+ />
197
+
198
+ <DryRunDialog
199
+ open={dryRunOpen}
200
+ onOpenChange={setDryRunOpen}
201
+ pipelineId={entity?.id}
202
+ />
203
+
204
+ <VersionHistoryDialog
205
+ open={historyOpen}
206
+ onOpenChange={setHistoryOpen}
207
+ pipelineId={entity?.id}
208
+ />
209
+ </>
210
+ );
211
+ }
@@ -0,0 +1,377 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Badge,
4
+ Button,
5
+ DataTable,
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ PageBlock,
13
+ PermissionGuard,
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue,
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ Drawer,
24
+ DrawerContent,
25
+ DrawerHeader,
26
+ DrawerTitle,
27
+ DrawerDescription,
28
+ } from '@vendure/dashboard';
29
+ import { Link } from '@tanstack/react-router';
30
+ import { ColumnDef, SortingState } from '@tanstack/react-table';
31
+ import { toast } from 'sonner';
32
+ import { Eye, ScrollText, Play, XCircle, ShieldCheck } from 'lucide-react';
33
+ import { ErrorState, LoadingState } from '../../components/shared';
34
+ import { formatDateTime } from '../../utils';
35
+ import {
36
+ DATAHUB_PERMISSIONS,
37
+ QUERY_LIMITS,
38
+ ROUTES,
39
+ RUN_STATUS,
40
+ FILTER_VALUES,
41
+ SELECT_WIDTHS,
42
+ TOAST_PIPELINE,
43
+ getRunStatusBadgeVariant,
44
+ } from '../../constants';
45
+ import {
46
+ usePipelineRuns,
47
+ useCancelRun,
48
+ useRunPipeline,
49
+ handleMutationError,
50
+ } from '../../hooks';
51
+ import { useOptionValues } from '../../hooks/api/use-config-options';
52
+ import { RunDetailsPanel } from './RunDetailsPanel';
53
+ import { getErrorMessage } from '../../../shared';
54
+ import type { RunRow } from '../../types';
55
+
56
+ /**
57
+ * Terminal run statuses — intentionally hardcoded rather than derived from backend.
58
+ * These are a fundamental system invariant: a run is either still in progress or it has
59
+ * reached one of these four terminal states. Adding `isFinished` metadata to the GraphQL
60
+ * `DataHubOptionValue` type would require schema + codegen changes for no practical benefit,
61
+ * since the set of terminal statuses is fixed by the execution engine contract.
62
+ */
63
+ const FINISHED_STATUSES = [RUN_STATUS.COMPLETED, RUN_STATUS.FAILED, RUN_STATUS.CANCELLED, RUN_STATUS.TIMEOUT] as string[];
64
+
65
+ export function PipelineRunsBlock({ pipelineId }: { pipelineId?: string }) {
66
+ const { options: statusOptions } = useOptionValues('runStatuses');
67
+ const [page, setPage] = React.useState(1);
68
+ const [itemsPerPage, setItemsPerPage] = React.useState(QUERY_LIMITS.PAGINATION_DEFAULT);
69
+ const [sorting, setSorting] = React.useState<SortingState>([
70
+ { id: 'startedAt', desc: true },
71
+ ]);
72
+ const [status, setStatus] = React.useState<string>('');
73
+ const [selectedRun, setSelectedRun] = React.useState<RunRow | null>(null);
74
+ const [cancelConfirmRunId, setCancelConfirmRunId] = React.useState<string | null>(null);
75
+ const [cancellingRunId, setCancellingRunId] = React.useState<string | null>(null);
76
+
77
+ const sortVar = sorting.length
78
+ ? { [sorting[0].id]: sorting[0].desc ? 'DESC' : 'ASC' }
79
+ : undefined;
80
+
81
+ const { data, isLoading, isError, error, refetch } = usePipelineRuns(pipelineId, {
82
+ take: itemsPerPage,
83
+ skip: (page - 1) * itemsPerPage,
84
+ sort: sortVar as Record<string, 'ASC' | 'DESC'> | undefined,
85
+ filter: status ? { status: { eq: status } } : undefined,
86
+ });
87
+
88
+ const cancelRun = useCancelRun();
89
+ const runPipeline = useRunPipeline();
90
+
91
+ const runs: RunRow[] = data?.items ?? [];
92
+ const totalItems = data?.totalItems ?? 0;
93
+
94
+ const handleSelectRun = React.useCallback((run: RunRow) => {
95
+ setSelectedRun(run);
96
+ }, []);
97
+
98
+ const handleCancelRun = React.useCallback((runId: string) => {
99
+ setCancelConfirmRunId(runId);
100
+ }, []);
101
+
102
+ const handleConfirmCancel = React.useCallback(() => {
103
+ if (!cancelConfirmRunId) return;
104
+ setCancellingRunId(cancelConfirmRunId);
105
+ cancelRun.mutate(cancelConfirmRunId, {
106
+ onSettled: () => {
107
+ setCancellingRunId(null);
108
+ },
109
+ });
110
+ setCancelConfirmRunId(null);
111
+ }, [cancelConfirmRunId, cancelRun.mutate]);
112
+
113
+ const handleStatusChange = React.useCallback((v: string) => {
114
+ setPage(1);
115
+ setStatus(v === FILTER_VALUES.ALL ? '' : v);
116
+ }, []);
117
+
118
+ const handlePageChange = React.useCallback((_table: unknown, newPage: number, newItemsPerPage: number) => {
119
+ setPage(newPage);
120
+ setItemsPerPage(newItemsPerPage);
121
+ }, []);
122
+
123
+ const handleSortChange = React.useCallback((_table: unknown, newSorting: SortingState) => {
124
+ setSorting(newSorting);
125
+ }, []);
126
+
127
+ const handleCloseDrawer = React.useCallback((open: boolean) => {
128
+ if (!open) setSelectedRun(null);
129
+ }, []);
130
+
131
+ const handleOnRerun = React.useCallback((id: string) => {
132
+ runPipeline.mutate(id, {
133
+ onSuccess: () => toast.success(TOAST_PIPELINE.RUN_STARTED),
134
+ onError: (err) => handleMutationError('start pipeline run', err),
135
+ });
136
+ }, [runPipeline.mutate]);
137
+
138
+ const columns: ColumnDef<RunRow, unknown>[] = React.useMemo(() => [
139
+ {
140
+ id: 'id',
141
+ header: 'ID',
142
+ accessorFn: row => row.id,
143
+ cell: ({ row }) => (
144
+ <button
145
+ type="button"
146
+ className="font-mono text-muted-foreground underline-offset-2 hover:underline"
147
+ onClick={() => handleSelectRun(row.original)}
148
+ aria-label={`View run ${row.original.id}`}
149
+ >
150
+ {row.original.id}
151
+ </button>
152
+ ),
153
+ enableSorting: false,
154
+ },
155
+ {
156
+ id: 'status',
157
+ header: 'Status',
158
+ accessorFn: row => row.status,
159
+ cell: ({ row }) => {
160
+ const st = row.original.status;
161
+ const isPaused = st === RUN_STATUS.PAUSED;
162
+ return (
163
+ <Badge
164
+ variant={getRunStatusBadgeVariant(st) as 'default' | 'secondary' | 'destructive' | 'outline'}
165
+ className={isPaused ? 'border-amber-400 dark:border-amber-600 text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/30' : undefined}
166
+ >
167
+ {isPaused ? 'AWAITING APPROVAL' : st}
168
+ </Badge>
169
+ );
170
+ },
171
+ },
172
+ {
173
+ id: 'startedAt',
174
+ header: 'Started',
175
+ accessorFn: row => row.startedAt ?? '',
176
+ cell: ({ row }) => formatDateTime(row.original.startedAt),
177
+ },
178
+ {
179
+ id: 'finishedAt',
180
+ header: 'Finished',
181
+ accessorFn: row => row.finishedAt ?? '',
182
+ cell: ({ row }) => formatDateTime(row.original.finishedAt),
183
+ },
184
+ {
185
+ id: 'processed',
186
+ header: 'Processed',
187
+ accessorFn: row => Number((row.metrics?.processed ?? 0)),
188
+ cell: ({ row }) => Number(row.original.metrics?.processed ?? 0),
189
+ enableSorting: false,
190
+ },
191
+ {
192
+ id: 'actions',
193
+ header: 'Actions',
194
+ cell: ({ row }) => {
195
+ const st = row.original.status;
196
+ const isFinished = FINISHED_STATUSES.includes(st);
197
+ const canCancel = st === RUN_STATUS.RUNNING || st === RUN_STATUS.PENDING;
198
+ const isPaused = st === RUN_STATUS.PAUSED;
199
+
200
+ return (
201
+ <div className="flex items-center gap-0.5">
202
+ <Tooltip>
203
+ <TooltipTrigger asChild>
204
+ <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => handleSelectRun(row.original)} aria-label="View details">
205
+ <Eye className="h-3.5 w-3.5" />
206
+ </Button>
207
+ </TooltipTrigger>
208
+ <TooltipContent>View details</TooltipContent>
209
+ </Tooltip>
210
+
211
+ <Tooltip>
212
+ <TooltipTrigger asChild>
213
+ <Button variant="ghost" size="icon" className="h-7 w-7" asChild>
214
+ <Link to={`${ROUTES.LOGS}?runId=${row.original.id}`} aria-label="View logs">
215
+ <ScrollText className="h-3.5 w-3.5" />
216
+ </Link>
217
+ </Button>
218
+ </TooltipTrigger>
219
+ <TooltipContent>View logs</TooltipContent>
220
+ </Tooltip>
221
+
222
+ {isPaused && (
223
+ <Tooltip>
224
+ <TooltipTrigger asChild>
225
+ <Button variant="ghost" size="icon" className="h-7 w-7 text-amber-600" onClick={() => handleSelectRun(row.original)} aria-label="Approve gate">
226
+ <ShieldCheck className="h-3.5 w-3.5" />
227
+ </Button>
228
+ </TooltipTrigger>
229
+ <TooltipContent>Approve gate</TooltipContent>
230
+ </Tooltip>
231
+ )}
232
+
233
+ {isFinished && pipelineId && (
234
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.RUN_PIPELINE]}>
235
+ <Tooltip>
236
+ <TooltipTrigger asChild>
237
+ <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => handleOnRerun(pipelineId)} aria-label="Re-run pipeline">
238
+ <Play className="h-3.5 w-3.5" />
239
+ </Button>
240
+ </TooltipTrigger>
241
+ <TooltipContent>Re-run pipeline</TooltipContent>
242
+ </Tooltip>
243
+ </PermissionGuard>
244
+ )}
245
+
246
+ {canCancel && (
247
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.RUN_PIPELINE]}>
248
+ <Tooltip>
249
+ <TooltipTrigger asChild>
250
+ <Button variant="ghost" size="icon" className="h-7 w-7 text-destructive" onClick={() => handleCancelRun(row.original.id)} disabled={cancellingRunId === row.original.id} aria-label="Cancel run">
251
+ <XCircle className="h-3.5 w-3.5" />
252
+ </Button>
253
+ </TooltipTrigger>
254
+ <TooltipContent>Cancel run</TooltipContent>
255
+ </Tooltip>
256
+ </PermissionGuard>
257
+ )}
258
+ </div>
259
+ );
260
+ },
261
+ enableSorting: false,
262
+ },
263
+ ], [handleSelectRun, handleCancelRun, handleOnRerun, cancellingRunId, pipelineId]);
264
+
265
+ let content: React.ReactNode;
266
+
267
+ if (isError && !data) {
268
+ content = (
269
+ <PageBlock column="main" blockId="runs-error">
270
+ <ErrorState
271
+ title="Failed to load pipeline runs"
272
+ message={getErrorMessage(error)}
273
+ onRetry={() => refetch()}
274
+ />
275
+ </PageBlock>
276
+ );
277
+ } else if (isLoading && runs.length === 0) {
278
+ content = (
279
+ <PageBlock column="main" blockId="runs-loading">
280
+ <LoadingState type="table" rows={5} message="Loading pipeline runs..." />
281
+ </PageBlock>
282
+ );
283
+ } else {
284
+ content = (
285
+ <>
286
+ <PageBlock column="main" blockId="runs">
287
+ <div className="flex items-center justify-between mb-2">
288
+ <h3 className="text-base font-semibold">Runs</h3>
289
+ <div className="flex items-center gap-2">
290
+ <Select value={status || FILTER_VALUES.ALL} onValueChange={handleStatusChange}>
291
+ <SelectTrigger className={SELECT_WIDTHS.RUN_STATUS} data-testid="datahub-run-status-filter">
292
+ <SelectValue placeholder="All statuses" />
293
+ </SelectTrigger>
294
+ <SelectContent>
295
+ <SelectItem value={FILTER_VALUES.ALL}>All</SelectItem>
296
+ {statusOptions.map(opt => (
297
+ <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
298
+ ))}
299
+ </SelectContent>
300
+ </Select>
301
+ <Button variant="ghost" onClick={() => refetch()} disabled={isLoading} data-testid="datahub-run-history-refresh-button">
302
+ Refresh
303
+ </Button>
304
+ </div>
305
+ </div>
306
+ <TooltipProvider>
307
+ <DataTable
308
+ columns={columns}
309
+ data={runs}
310
+ totalItems={totalItems}
311
+ isLoading={isLoading}
312
+ page={page}
313
+ itemsPerPage={itemsPerPage}
314
+ sorting={sorting}
315
+ onPageChange={handlePageChange}
316
+ onSortChange={handleSortChange}
317
+ onRefresh={refetch}
318
+ disableViewOptions
319
+ data-testid="datahub-run-history-table"
320
+ />
321
+ </TooltipProvider>
322
+ </PageBlock>
323
+ <Drawer open={!!selectedRun} onOpenChange={handleCloseDrawer}>
324
+ <DrawerContent>
325
+ <DrawerHeader>
326
+ <DrawerTitle>Run details</DrawerTitle>
327
+ <DrawerDescription>
328
+ {selectedRun ? `Run ${selectedRun.id}` : 'Details'}
329
+ </DrawerDescription>
330
+ </DrawerHeader>
331
+ {selectedRun && (
332
+ <RunDetailsPanel
333
+ runId={selectedRun.id}
334
+ initialData={selectedRun}
335
+ onCancel={handleCancelRun}
336
+ onRerun={handleOnRerun}
337
+ isCancelling={cancellingRunId === selectedRun.id}
338
+ />
339
+ )}
340
+ </DrawerContent>
341
+ </Drawer>
342
+ <Dialog open={!!cancelConfirmRunId} onOpenChange={(open) => { if (!open) setCancelConfirmRunId(null); }}>
343
+ <DialogContent className="max-w-md">
344
+ <DialogHeader>
345
+ <DialogTitle>Cancel Pipeline Run</DialogTitle>
346
+ <DialogDescription>
347
+ This will request cancellation of the running pipeline. This action cannot be undone.
348
+ </DialogDescription>
349
+ </DialogHeader>
350
+ <DialogFooter>
351
+ <Button
352
+ variant="outline"
353
+ onClick={() => setCancelConfirmRunId(null)}
354
+ >
355
+ Keep Running
356
+ </Button>
357
+ <Button
358
+ variant="destructive"
359
+ onClick={handleConfirmCancel}
360
+ >
361
+ Cancel Run
362
+ </Button>
363
+ </DialogFooter>
364
+ </DialogContent>
365
+ </Dialog>
366
+ </>
367
+ );
368
+ }
369
+
370
+ return (
371
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.VIEW_RUNS]}>
372
+ <div id="runs">
373
+ {content}
374
+ </div>
375
+ </PermissionGuard>
376
+ );
377
+ }
@@ -0,0 +1,87 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ DashboardRouteDefinition,
5
+ DetailPageButton,
6
+ ListPage,
7
+ PageActionBarRight,
8
+ PermissionGuard,
9
+ } from '@vendure/dashboard';
10
+ import { Link } from '@tanstack/react-router';
11
+ import { Download, PlusIcon, Upload } from 'lucide-react';
12
+ import { DATAHUB_NAV_ID, DATAHUB_NAV_SECTION, DATAHUB_PERMISSIONS, ROUTES, getStatusBadgeVariant, PIPELINE_STATUS } from '../../constants';
13
+ import { pipelinesListDocument, deletePipelineDocument } from '../../hooks';
14
+ import type { DataHubPipeline } from '../../types';
15
+
16
+ function StatusBadge({ status }: { status?: string | null }) {
17
+ const pipelineStatus = status || PIPELINE_STATUS.DRAFT;
18
+ return (
19
+ <Badge variant={getStatusBadgeVariant(pipelineStatus)}>
20
+ {pipelineStatus}
21
+ </Badge>
22
+ );
23
+ }
24
+
25
+ export const pipelinesList: DashboardRouteDefinition = {
26
+ navMenuItem: {
27
+ sectionId: DATAHUB_NAV_SECTION,
28
+ id: DATAHUB_NAV_ID,
29
+ url: ROUTES.PIPELINES,
30
+ title: 'Pipelines',
31
+ },
32
+ path: ROUTES.PIPELINES,
33
+ loader: () => ({ breadcrumb: 'Data Hub' }),
34
+ component: route => (
35
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.READ_PIPELINE]}>
36
+ <ListPage
37
+ pageId="data-hub-pipelines-list"
38
+ title="Pipelines"
39
+ listQuery={pipelinesListDocument}
40
+ deleteMutation={deletePipelineDocument}
41
+ route={route}
42
+ defaultColumns={['name', 'code', 'status', 'version', 'enabled', 'createdAt']}
43
+ customizeColumns={{
44
+ name: {
45
+ header: 'Name',
46
+ cell: ({ row }) => (
47
+ <DetailPageButton id={row.original.id} label={row.original.name} />
48
+ ),
49
+ },
50
+ status: {
51
+ header: 'Status',
52
+ cell: ({ row }) => <StatusBadge status={(row.original as DataHubPipeline).status} />,
53
+ },
54
+ version: {
55
+ header: 'Version',
56
+ cell: ({ row }) => (
57
+ <span className="text-muted-foreground">
58
+ v{(row.original as DataHubPipeline).version ?? 0}
59
+ </span>
60
+ ),
61
+ },
62
+ }}
63
+ >
64
+ <PageActionBarRight>
65
+ <Button variant="outline" asChild data-testid="datahub-import-wizard-button">
66
+ <Link to="./import-wizard">
67
+ <Upload className="mr-2 h-4 w-4" />
68
+ Import Wizard
69
+ </Link>
70
+ </Button>
71
+ <Button variant="outline" asChild data-testid="datahub-export-wizard-button">
72
+ <Link to="./export-wizard">
73
+ <Download className="mr-2 h-4 w-4" />
74
+ Export Wizard
75
+ </Link>
76
+ </Button>
77
+ <Button asChild data-testid="datahub-pipeline-create-button">
78
+ <Link to="./new">
79
+ <PlusIcon className="mr-2 h-4 w-4" />
80
+ New pipeline
81
+ </Link>
82
+ </Button>
83
+ </PageActionBarRight>
84
+ </ListPage>
85
+ </PermissionGuard>
86
+ ),
87
+ };