@oronts/vendure-data-hub-plugin 0.1.0 → 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 +10 -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 +4 -4
@@ -0,0 +1,425 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ DashboardRouteDefinition,
5
+ Input,
6
+ Json,
7
+ Page,
8
+ PageActionBar,
9
+ PageActionBarRight,
10
+ PageBlock,
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ PermissionGuard,
17
+ Card,
18
+ CardContent,
19
+ CardDescription,
20
+ CardHeader,
21
+ CardTitle,
22
+ Badge,
23
+ Label,
24
+ } from '@vendure/dashboard';
25
+ import { toast } from 'sonner';
26
+ import { DATAHUB_NAV_SECTION, UI_DEFAULTS, UI_LIMITS, QUERY_LIMITS, ROUTES, DATAHUB_PERMISSIONS, TOAST_HOOK, FALLBACK_STAGE_CATEGORIES } from '../../constants';
27
+ import { formatDateTime } from '../../utils';
28
+ import {
29
+ Play,
30
+ RefreshCw,
31
+ Zap,
32
+ CheckCircle2,
33
+ XCircle,
34
+ Info,
35
+ Clock,
36
+ Loader2,
37
+ } from 'lucide-react';
38
+ import {
39
+ usePipelines,
40
+ usePipelineHooks,
41
+ useEvents,
42
+ useTestHook,
43
+ useHookStages,
44
+ useHookStageCategories,
45
+ handleMutationError,
46
+ } from '../../hooks';
47
+ import type { HookStageCategoryConfig } from '../../hooks';
48
+ import { ErrorState, LoadingState } from '../../components/shared';
49
+ import { buildHookStages } from './hook-stages';
50
+ import type { HookStage } from './hook-stages';
51
+
52
+ export const hooksPage: DashboardRouteDefinition = {
53
+ navMenuItem: {
54
+ sectionId: DATAHUB_NAV_SECTION,
55
+ id: 'data-hub-hooks',
56
+ url: ROUTES.HOOKS,
57
+ title: 'Hooks & Events',
58
+ },
59
+ path: ROUTES.HOOKS,
60
+ loader: () => ({ breadcrumb: 'Hooks & Events' }),
61
+ component: () => (
62
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.UPDATE_PIPELINE]}>
63
+ <HooksPage />
64
+ </PermissionGuard>
65
+ ),
66
+ };
67
+
68
+ interface HookStageSectionProps {
69
+ categoryInfo: HookStageCategoryConfig;
70
+ stages: HookStage[];
71
+ hooks: Record<string, unknown>;
72
+ selectedStage: HookStage | null;
73
+ isPending: boolean;
74
+ testResult: 'success' | 'error' | null;
75
+ onTest: (stage: HookStage) => void;
76
+ disabled: boolean;
77
+ }
78
+
79
+ function HookStageSection({ categoryInfo, stages: allStages, hooks, selectedStage, isPending, testResult, onTest, disabled }: HookStageSectionProps) {
80
+ const stages = allStages.filter(s => s.category === categoryInfo.key);
81
+
82
+ return (
83
+ <div className="mb-6">
84
+ <div className="flex items-center gap-2 mb-3">
85
+ <Badge className={categoryInfo.color}>{categoryInfo.label}</Badge>
86
+ <span className="text-sm text-muted-foreground">{categoryInfo.description}</span>
87
+ </div>
88
+ <div className={`grid gap-3 ${categoryInfo.gridClass}`}>
89
+ {stages.map(stage => (
90
+ <HookStageCard
91
+ key={stage.key}
92
+ stage={stage}
93
+ isConfigured={!!hooks[stage.key]}
94
+ isSelected={selectedStage?.key === stage.key}
95
+ isLoading={isPending && selectedStage?.key === stage.key}
96
+ testResult={selectedStage?.key === stage.key ? testResult : null}
97
+ onTest={() => onTest(stage)}
98
+ disabled={disabled}
99
+ />
100
+ ))}
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function HooksPage() {
107
+ const [pipelineId, setPipelineId] = React.useState<string>('');
108
+ const [selectedStage, setSelectedStage] = React.useState<HookStage | null>(null);
109
+ const [testResult, setTestResult] = React.useState<'success' | 'error' | null>(null);
110
+ const [eventFilter, setEventFilter] = React.useState('');
111
+
112
+ const pipelinesQuery = usePipelines({ take: QUERY_LIMITS.ALL_ITEMS });
113
+ const hooksQuery = usePipelineHooks(pipelineId || undefined);
114
+ const eventsQuery = useEvents(UI_DEFAULTS.EVENTS_LIMIT);
115
+ const testMutation = useTestHook();
116
+ const { hookStages: backendStages } = useHookStages();
117
+ const { categories: backendCategories } = useHookStageCategories();
118
+
119
+ const stages = React.useMemo(() => buildHookStages(backendStages), [backendStages]);
120
+ const stageCategories = React.useMemo(
121
+ () => backendCategories.length > 0 ? backendCategories : FALLBACK_STAGE_CATEGORIES,
122
+ [backendCategories],
123
+ );
124
+
125
+ const hooks = hooksQuery.data ?? {};
126
+ const pipelines = pipelinesQuery.data?.items ?? [];
127
+ const selectedPipeline = pipelines.find(p => p.id === pipelineId);
128
+
129
+ const isLoading = pipelinesQuery.isLoading;
130
+ const hasError = pipelinesQuery.isError || eventsQuery.isError || hooksQuery.isError;
131
+ const errorMessage = pipelinesQuery.error?.message || eventsQuery.error?.message || hooksQuery.error?.message;
132
+
133
+ const handleRetry = React.useCallback(() => {
134
+ pipelinesQuery.refetch();
135
+ eventsQuery.refetch();
136
+ }, [pipelinesQuery.refetch, eventsQuery.refetch]);
137
+
138
+ const runTest = React.useCallback((stage: HookStage) => {
139
+ if (!pipelineId) {
140
+ toast.error(TOAST_HOOK.SELECT_PIPELINE_FIRST);
141
+ return;
142
+ }
143
+ setSelectedStage(stage);
144
+ setTestResult(null);
145
+ testMutation.mutate(
146
+ {
147
+ pipelineId,
148
+ stage: stage.key,
149
+ payload: stage.examplePayload,
150
+ },
151
+ {
152
+ onSuccess: () => {
153
+ setTestResult('success');
154
+ toast.success(TOAST_HOOK.TEST_SUCCESS);
155
+ eventsQuery.refetch();
156
+ },
157
+ onError: (err: unknown) => {
158
+ setTestResult('error');
159
+ handleMutationError('test hook', err);
160
+ },
161
+ }
162
+ );
163
+ }, [pipelineId, testMutation.mutate, eventsQuery.refetch]);
164
+
165
+ return (
166
+ <Page pageId="data-hub-hooks">
167
+ <PageActionBar>
168
+ <PageActionBarRight>
169
+ <Button
170
+ variant="ghost"
171
+ onClick={() => eventsQuery.refetch()}
172
+ disabled={eventsQuery.isFetching}
173
+ >
174
+ <RefreshCw className={`w-4 h-4 mr-2 ${eventsQuery.isFetching ? 'animate-spin' : ''}`} />
175
+ Refresh Events
176
+ </Button>
177
+ </PageActionBarRight>
178
+ </PageActionBar>
179
+
180
+ {hasError && (
181
+ <PageBlock column="main" blockId="error">
182
+ <ErrorState
183
+ title="Failed to load data"
184
+ message={errorMessage || 'An unexpected error occurred'}
185
+ onRetry={handleRetry}
186
+ />
187
+ </PageBlock>
188
+ )}
189
+
190
+ {isLoading && !hasError && (
191
+ <PageBlock column="main" blockId="loading">
192
+ <LoadingState type="card" rows={3} message="Loading pipelines and hooks..." />
193
+ </PageBlock>
194
+ )}
195
+
196
+ <PageBlock column="main" blockId="intro">
197
+ <Card>
198
+ <CardHeader className="pb-3">
199
+ <div className="flex items-center gap-2">
200
+ <Zap className="w-5 h-5 text-primary" />
201
+ <CardTitle>Pipeline Hooks</CardTitle>
202
+ </div>
203
+ <CardDescription>
204
+ Hooks let you run custom code at specific points during pipeline execution.
205
+ Select a pipeline below to view its configured hooks and test them with sample data.
206
+ </CardDescription>
207
+ </CardHeader>
208
+ <CardContent>
209
+ <div className="flex items-end gap-4">
210
+ <div className="flex-1 max-w-xs">
211
+ <Label className="text-sm font-medium mb-1.5 block">
212
+ Select Pipeline
213
+ </Label>
214
+ <Select value={pipelineId} onValueChange={setPipelineId}>
215
+ <SelectTrigger>
216
+ <SelectValue placeholder="Choose a pipeline to test..." />
217
+ </SelectTrigger>
218
+ <SelectContent>
219
+ {pipelines.map(p => (
220
+ <SelectItem key={p.id} value={p.id}>
221
+ {p.name}
222
+ <span className="text-muted-foreground ml-2 text-xs">
223
+ ({p.code})
224
+ </span>
225
+ </SelectItem>
226
+ ))}
227
+ </SelectContent>
228
+ </Select>
229
+ </div>
230
+ {selectedPipeline && (
231
+ <div className="text-sm text-muted-foreground">
232
+ <Info className="w-4 h-4 inline mr-1" />
233
+ Testing hooks for <strong>{selectedPipeline.name}</strong>
234
+ </div>
235
+ )}
236
+ </div>
237
+ </CardContent>
238
+ </Card>
239
+ </PageBlock>
240
+
241
+ <PageBlock column="main" blockId="stages">
242
+ <div className="mb-4">
243
+ <h3 className="text-lg font-semibold mb-1">Available Hook Stages</h3>
244
+ <p className="text-sm text-muted-foreground">
245
+ Click any hook to test it with sample data. Results will appear in the Events section below.
246
+ </p>
247
+ </div>
248
+
249
+ {stageCategories.map(cat => (
250
+ <HookStageSection
251
+ key={cat.key}
252
+ categoryInfo={cat}
253
+ stages={stages}
254
+ hooks={hooks as Record<string, unknown>}
255
+ selectedStage={selectedStage}
256
+ isPending={testMutation.isPending}
257
+ testResult={testResult}
258
+ onTest={runTest}
259
+ disabled={!pipelineId}
260
+ />
261
+ ))}
262
+ </PageBlock>
263
+
264
+ {pipelineId && Object.keys(hooks).length > 0 && (
265
+ <PageBlock column="main" blockId="configured">
266
+ <details className="group">
267
+ <summary className="cursor-pointer text-sm font-medium mb-2 flex items-center gap-2">
268
+ <span>View Raw Hook Configuration</span>
269
+ <span className="text-muted-foreground">(Advanced)</span>
270
+ </summary>
271
+ <div className="mt-2 p-3 bg-muted rounded-lg">
272
+ <Json value={hooks} />
273
+ </div>
274
+ </details>
275
+ </PageBlock>
276
+ )}
277
+
278
+ <PageBlock column="main" blockId="events">
279
+ <div className="flex items-center justify-between mb-4">
280
+ <div>
281
+ <h3 className="text-lg font-semibold">Recent Events</h3>
282
+ <p className="text-sm text-muted-foreground">
283
+ Live feed of events triggered by hooks (auto-refreshes every 5s)
284
+ </p>
285
+ </div>
286
+ <div className="flex items-center gap-2">
287
+ <Input
288
+ className="w-48"
289
+ placeholder="Filter events..."
290
+ aria-label="Filter hook events"
291
+ value={eventFilter}
292
+ onChange={e => setEventFilter(e.target.value)}
293
+ />
294
+ </div>
295
+ </div>
296
+
297
+ <div className="border rounded-lg overflow-hidden">
298
+ <table className="w-full text-sm">
299
+ <thead>
300
+ <tr className="bg-muted">
301
+ <th className="text-left px-3 py-2 w-32">Time</th>
302
+ <th className="text-left px-3 py-2 w-48">Event</th>
303
+ <th className="text-left px-3 py-2">Payload</th>
304
+ </tr>
305
+ </thead>
306
+ <tbody>
307
+ {/* Events have unique createdAt timestamps, used as stable keys */}
308
+ {(eventsQuery.data ?? [])
309
+ .filter(e => !eventFilter || (e.name ?? '').toLowerCase().includes(eventFilter.toLowerCase()))
310
+ .slice(0, UI_LIMITS.TABLE_PREVIEW_ROWS)
311
+ .map((e) => (
312
+ <tr key={`${e.createdAt}-${e.name}`} className="border-t align-top hover:bg-muted/50">
313
+ <td className="px-3 py-2 text-muted-foreground">
314
+ <Clock className="w-3 h-3 inline mr-1" />
315
+ {formatDateTime(e.createdAt as string)}
316
+ </td>
317
+ <td className="px-3 py-2">
318
+ <code className="text-xs bg-muted px-1.5 py-0.5 rounded">
319
+ {e.name}
320
+ </code>
321
+ </td>
322
+ <td className="px-3 py-2">
323
+ <Json value={e.payload as Record<string, unknown>} />
324
+ </td>
325
+ </tr>
326
+ ))}
327
+ {(eventsQuery.data ?? []).length === 0 && (
328
+ <tr>
329
+ <td colSpan={3} className="px-3 py-8 text-center text-muted-foreground">
330
+ <Info className="w-5 h-5 mx-auto mb-2 opacity-50" />
331
+ No events yet. Test a hook to see events appear here.
332
+ </td>
333
+ </tr>
334
+ )}
335
+ </tbody>
336
+ </table>
337
+ </div>
338
+ </PageBlock>
339
+ </Page>
340
+ );
341
+ }
342
+
343
+ const HookStageCard = React.memo(function HookStageCard({
344
+ stage,
345
+ isConfigured,
346
+ isSelected,
347
+ isLoading,
348
+ testResult,
349
+ onTest,
350
+ disabled,
351
+ }: Readonly<{
352
+ stage: HookStage;
353
+ isConfigured: boolean;
354
+ isSelected: boolean;
355
+ isLoading: boolean;
356
+ testResult: 'success' | 'error' | null;
357
+ onTest: () => void;
358
+ disabled: boolean;
359
+ }>) {
360
+ const handleClick = React.useCallback(() => {
361
+ if (!disabled) {
362
+ onTest();
363
+ }
364
+ }, [disabled, onTest]);
365
+
366
+ const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {
367
+ if (e.key === 'Enter' || e.key === ' ') {
368
+ e.preventDefault();
369
+ if (!disabled) {
370
+ onTest();
371
+ }
372
+ }
373
+ }, [disabled, onTest]);
374
+
375
+ return (
376
+ <div
377
+ className={`
378
+ border rounded-lg p-3 transition-all cursor-pointer
379
+ ${disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-primary hover:shadow-sm'}
380
+ ${isSelected ? 'border-primary ring-1 ring-primary' : ''}
381
+ ${isConfigured ? 'bg-primary/5' : ''}
382
+ `}
383
+ onClick={handleClick}
384
+ onKeyDown={handleKeyDown}
385
+ role="button"
386
+ tabIndex={disabled ? -1 : 0}
387
+ aria-label={`Configure ${stage.label} hook stage`}
388
+ aria-disabled={disabled}
389
+ >
390
+ <div className="flex items-start justify-between mb-2">
391
+ <div className="flex items-center gap-2">
392
+ <div className={`p-1.5 rounded ${isConfigured ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'}`}>
393
+ <stage.icon className="w-4 h-4" />
394
+ </div>
395
+ <div>
396
+ <div className="font-medium text-sm">{stage.label}</div>
397
+ {isConfigured && (
398
+ <Badge variant="outline" className="text-xs mt-0.5">
399
+ Configured
400
+ </Badge>
401
+ )}
402
+ </div>
403
+ </div>
404
+ {isLoading && <Loader2 className="w-4 h-4 animate-spin text-primary" />}
405
+ {!isLoading && testResult === 'success' && (
406
+ <CheckCircle2 className="w-4 h-4 text-green-600" />
407
+ )}
408
+ {!isLoading && testResult === 'error' && (
409
+ <XCircle className="w-4 h-4 text-red-600" />
410
+ )}
411
+ </div>
412
+ <p className="text-xs text-muted-foreground line-clamp-2">
413
+ {stage.description}
414
+ </p>
415
+ {!disabled && (
416
+ <div className="mt-2 pt-2 border-t">
417
+ <div className="text-xs text-muted-foreground flex items-center gap-1">
418
+ <Play className="w-3 h-3" />
419
+ Click to test
420
+ </div>
421
+ </div>
422
+ )}
423
+ </div>
424
+ );
425
+ });
@@ -0,0 +1,52 @@
1
+ import { Circle } from 'lucide-react';
2
+ import type { HookStageConfig } from '../../hooks/api/use-config-options';
3
+ import { resolveIconName } from '../../utils/icon-resolver';
4
+
5
+ export interface HookStage {
6
+ key: string;
7
+ label: string;
8
+ description: string;
9
+ icon: React.ElementType;
10
+ category: 'lifecycle' | 'data' | 'error';
11
+ examplePayload: Record<string, unknown>;
12
+ }
13
+
14
+ /**
15
+ * Example payloads for hook testing (frontend-only, keyed by stage).
16
+ * These are documentation data, not served from the backend.
17
+ */
18
+ const EXAMPLE_PAYLOADS: Record<string, Record<string, unknown>> = {
19
+ PIPELINE_STARTED: { pipelineCode: 'my-pipeline', runId: '123' },
20
+ PIPELINE_COMPLETED: { pipelineCode: 'my-pipeline', runId: '123', recordsProcessed: 100, duration: 5000 },
21
+ PIPELINE_FAILED: { pipelineCode: 'my-pipeline', runId: '123', error: 'Connection timeout' },
22
+ BEFORE_EXTRACT: { stepKey: 'extract', config: {} },
23
+ AFTER_EXTRACT: { stepKey: 'extract', recordCount: 50, records: [{ id: 1 }] },
24
+ BEFORE_TRANSFORM: { stepKey: 'transform', recordCount: 50 },
25
+ AFTER_TRANSFORM: { stepKey: 'transform', recordCount: 48, dropped: 2 },
26
+ BEFORE_VALIDATE: { stepKey: 'validate', schemaCode: 'product-schema' },
27
+ AFTER_VALIDATE: { stepKey: 'validate', valid: 45, invalid: 3 },
28
+ BEFORE_ENRICH: { stepKey: 'enrich' },
29
+ AFTER_ENRICH: { stepKey: 'enrich', enrichedFields: ['category', 'price'] },
30
+ BEFORE_ROUTE: { stepKey: 'route', recordCount: 45 },
31
+ AFTER_ROUTE: { stepKey: 'route', destinations: { products: 30, inventory: 15 } },
32
+ BEFORE_LOAD: { stepKey: 'load', destination: 'vendure', recordCount: 45 },
33
+ AFTER_LOAD: { stepKey: 'load', created: 20, updated: 25, errors: 0 },
34
+ ON_ERROR: { error: 'Validation failed', record: { id: 1 }, stepKey: 'validate' },
35
+ ON_RETRY: { errorId: '456', attempt: 2, maxAttempts: 3 },
36
+ ON_DEAD_LETTER: { errorId: '456', reason: 'Max retries exceeded', record: { id: 1 } },
37
+ };
38
+
39
+ /**
40
+ * Build HookStage objects from backend metadata, merging in frontend-only
41
+ * examplePayloads and resolving icon names to Lucide components.
42
+ */
43
+ export function buildHookStages(backendStages: HookStageConfig[]): HookStage[] {
44
+ return backendStages.map(stage => ({
45
+ key: stage.key,
46
+ label: stage.label,
47
+ description: stage.description,
48
+ icon: resolveIconName(stage.icon) ?? Circle,
49
+ category: stage.category as HookStage['category'],
50
+ examplePayload: EXAMPLE_PAYLOADS[stage.key] ?? {},
51
+ }));
52
+ }
@@ -0,0 +1 @@
1
+ export { hooksPage } from './Hooks';
@@ -0,0 +1,8 @@
1
+ export { pipelinesList, pipelineDetail, importWizardPage, exportWizardPage } from './pipelines';
2
+ export { connectionsList, connectionDetail } from './connections';
3
+ export { secretsList, secretDetail } from './secrets';
4
+ export { adaptersList } from './adapters';
5
+ export { queuesPage } from './queues';
6
+ export { hooksPage } from './hooks';
7
+ export { settingsPage } from './settings';
8
+ export { logsPage } from './logs';
@@ -0,0 +1,93 @@
1
+ import * as React from 'react';
2
+ import {
3
+ DashboardRouteDefinition,
4
+ Page,
5
+ PageActionBar,
6
+ PageActionBarRight,
7
+ PageBlock,
8
+ PermissionGuard,
9
+ Tabs,
10
+ TabsContent,
11
+ TabsList,
12
+ TabsTrigger,
13
+ } from '@vendure/dashboard';
14
+ import {
15
+ BarChart3,
16
+ FileText,
17
+ Zap,
18
+ } from 'lucide-react';
19
+ import { DATAHUB_NAV_SECTION, ROUTES, DATAHUB_PERMISSIONS } from '../../constants';
20
+ import { LogsOverviewTab } from './components/LogsOverviewTab';
21
+ import { LogExplorerTab } from './components/LogExplorerTab';
22
+ import { RealtimeLogTab } from './components/RealtimeLogTab';
23
+
24
+ /**
25
+ * Route definition for the Logs & Analytics page.
26
+ * Navigation and permission guarding.
27
+ */
28
+ export const logsPage: DashboardRouteDefinition = {
29
+ navMenuItem: {
30
+ sectionId: DATAHUB_NAV_SECTION,
31
+ id: 'data-hub-logs',
32
+ url: ROUTES.LOGS,
33
+ title: 'Logs & Analytics',
34
+ },
35
+ path: ROUTES.LOGS,
36
+ loader: () => ({ breadcrumb: 'Logs & Analytics' }),
37
+ component: () => (
38
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.VIEW_RUNS]}>
39
+ <LogsPage />
40
+ </PermissionGuard>
41
+ ),
42
+ };
43
+
44
+ /**
45
+ * Logs page with tabbed layout: Overview, Log Explorer, Real-time Feed.
46
+ */
47
+ function LogsPage() {
48
+ const initialRunId = React.useMemo(() => {
49
+ const params = new URLSearchParams(window.location.search);
50
+ return params.get('runId') ?? undefined;
51
+ }, []);
52
+
53
+ const [activeTab, setActiveTab] = React.useState(initialRunId ? 'logs' : 'overview');
54
+
55
+ return (
56
+ <Page pageId="data-hub-logs">
57
+ <PageActionBar>
58
+ <PageActionBarRight />
59
+ </PageActionBar>
60
+
61
+ <PageBlock column="main" blockId="tabs">
62
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
63
+ <TabsList className="mb-4" data-testid="datahub-logs-tabs">
64
+ <TabsTrigger value="overview" className="gap-2" data-testid="datahub-logs-tab-overview">
65
+ <BarChart3 className="w-4 h-4" />
66
+ Overview
67
+ </TabsTrigger>
68
+ <TabsTrigger value="logs" className="gap-2" data-testid="datahub-logs-tab-explorer">
69
+ <FileText className="w-4 h-4" />
70
+ Log Explorer
71
+ </TabsTrigger>
72
+ <TabsTrigger value="realtime" className="gap-2" data-testid="datahub-logs-tab-realtime">
73
+ <Zap className="w-4 h-4" />
74
+ Real-time Feed
75
+ </TabsTrigger>
76
+ </TabsList>
77
+
78
+ <TabsContent value="overview">
79
+ <LogsOverviewTab />
80
+ </TabsContent>
81
+
82
+ <TabsContent value="logs">
83
+ <LogExplorerTab initialRunId={initialRunId} />
84
+ </TabsContent>
85
+
86
+ <TabsContent value="realtime">
87
+ {activeTab === 'realtime' && <RealtimeLogTab />}
88
+ </TabsContent>
89
+ </Tabs>
90
+ </PageBlock>
91
+ </Page>
92
+ );
93
+ }