@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,118 @@
1
+ import * as React from 'react';
2
+ import { memo } from 'react';
3
+ import {
4
+ Button,
5
+ Drawer,
6
+ DrawerContent,
7
+ DrawerHeader,
8
+ DrawerTitle,
9
+ DrawerDescription,
10
+ Json,
11
+ } from '@vendure/dashboard';
12
+ import { Link } from '@tanstack/react-router';
13
+ import { LogLevelBadge } from './LogLevelBadge';
14
+ import { ROUTES } from '../../../constants';
15
+ import { formatDateTime } from '../../../utils';
16
+ import type { DataHubLog } from '../../../types';
17
+
18
+ interface LogDetailDrawerProps {
19
+ log: DataHubLog | null;
20
+ onClose: () => void;
21
+ }
22
+
23
+ /**
24
+ * Drawer component that displays detailed information about a selected log entry.
25
+ * Shows message, context, metadata, and links to related pipeline.
26
+ */
27
+ export const LogDetailDrawer = memo(function LogDetailDrawer({ log, onClose }: LogDetailDrawerProps) {
28
+ const handleOpenChange = React.useCallback((open: boolean) => {
29
+ if (!open) onClose();
30
+ }, [onClose]);
31
+
32
+ return (
33
+ <Drawer open={!!log} onOpenChange={handleOpenChange}>
34
+ <DrawerContent data-testid="datahub-log-detail-drawer">
35
+ <DrawerHeader>
36
+ <DrawerTitle>Log Details</DrawerTitle>
37
+ <DrawerDescription>
38
+ {log?.createdAt ? formatDateTime(log.createdAt) : ''}
39
+ </DrawerDescription>
40
+ </DrawerHeader>
41
+ {log && (
42
+ <div className="p-4 space-y-4">
43
+ <div className="flex items-center gap-3">
44
+ <LogLevelBadge level={log.level} />
45
+ {log.pipeline && (
46
+ <Button asChild variant="link" size="sm" className="p-0 h-auto">
47
+ <Link
48
+ to={`${ROUTES.PIPELINES}/$id`}
49
+ params={{ id: log.pipeline.id }}
50
+ >
51
+ {log.pipeline.name}
52
+ </Link>
53
+ </Button>
54
+ )}
55
+ </div>
56
+
57
+ <div>
58
+ <div className="text-sm font-medium mb-1">Message</div>
59
+ <div className="p-3 bg-muted rounded-lg text-sm font-mono whitespace-pre-wrap">
60
+ {log.message}
61
+ </div>
62
+ </div>
63
+
64
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
65
+ <div>
66
+ <div className="text-xs text-muted-foreground">Step</div>
67
+ <div className="font-mono text-sm">{log.stepKey ?? '—'}</div>
68
+ </div>
69
+ <div>
70
+ <div className="text-xs text-muted-foreground">Duration</div>
71
+ <div className="text-sm">
72
+ {log.durationMs != null ? `${log.durationMs}ms` : '—'}
73
+ </div>
74
+ </div>
75
+ <div>
76
+ <div className="text-xs text-muted-foreground">Records Processed</div>
77
+ <div className="text-sm">{log.recordsProcessed ?? '—'}</div>
78
+ </div>
79
+ <div>
80
+ <div className="text-xs text-muted-foreground">Records Failed</div>
81
+ <div className={`text-sm ${log.recordsFailed > 0 ? 'text-red-600' : ''}`}>
82
+ {log.recordsFailed ?? '—'}
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ {log.context && Object.keys(log.context).length > 0 && (
88
+ <div>
89
+ <div className="text-sm font-medium mb-1">Context</div>
90
+ <Json value={log.context} />
91
+ </div>
92
+ )}
93
+
94
+ {log.metadata && Object.keys(log.metadata).length > 0 && (
95
+ <div>
96
+ <div className="text-sm font-medium mb-1">Metadata</div>
97
+ <Json value={log.metadata} />
98
+ </div>
99
+ )}
100
+
101
+ {log.runId && (
102
+ <div className="pt-3 border-t">
103
+ <Button asChild variant="outline" size="sm">
104
+ <Link
105
+ to={`${ROUTES.PIPELINES}/$id`}
106
+ params={{ id: log.pipeline?.id ?? '' }}
107
+ >
108
+ View Pipeline
109
+ </Link>
110
+ </Button>
111
+ </div>
112
+ )}
113
+ </div>
114
+ )}
115
+ </DrawerContent>
116
+ </Drawer>
117
+ );
118
+ });
@@ -0,0 +1,367 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ Input,
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ Card,
11
+ CardContent,
12
+ CardHeader,
13
+ CardTitle,
14
+ } from '@vendure/dashboard';
15
+ import { toast } from 'sonner';
16
+ import {
17
+ Calendar,
18
+ ChevronLeft,
19
+ ChevronRight,
20
+ Download,
21
+ Filter,
22
+ Hash,
23
+ RefreshCw,
24
+ Search,
25
+ X,
26
+ } from 'lucide-react';
27
+ import {
28
+ useLogs,
29
+ usePipelines,
30
+ useOptionValues,
31
+ } from '../../../hooks';
32
+ import { ErrorState, LoadingState } from '../../../components/shared';
33
+ import { QUERY_LIMITS, UI_DEFAULTS, FILTER_VALUES, TOAST_LOG } from '../../../constants';
34
+ import { LogTableRow } from './LogTableRow';
35
+ import { LogDetailDrawer } from './LogDetailDrawer';
36
+ import type { DataHubLogListOptions, DataHubLog } from '../../../types';
37
+
38
+ /**
39
+ * Log explorer tab with filters, table, and export functionality.
40
+ * Allows filtering by pipeline, level, date range, and message search.
41
+ */
42
+ export function LogExplorerTab({ initialRunId }: { initialRunId?: string }) {
43
+ const [runId, setRunId] = React.useState<string>(initialRunId ?? '');
44
+ const [pipelineId, setPipelineId] = React.useState<string>('');
45
+ const [level, setLevel] = React.useState<string>('');
46
+ const [search, setSearch] = React.useState<string>('');
47
+ const [startDate, setStartDate] = React.useState<string>('');
48
+ const [endDate, setEndDate] = React.useState<string>('');
49
+ const [page, setPage] = React.useState(1);
50
+ const [selectedLog, setSelectedLog] = React.useState<DataHubLog | null>(null);
51
+ const { options: logLevelOptions } = useOptionValues('logLevels');
52
+ const pageSize = UI_DEFAULTS.LOG_EXPLORER_PAGE_SIZE;
53
+
54
+ const pipelinesQuery = usePipelines({ take: QUERY_LIMITS.ALL_ITEMS });
55
+
56
+ const filter = React.useMemo((): DataHubLogListOptions['filter'] => {
57
+ const f: DataHubLogListOptions['filter'] = {};
58
+ if (runId) {
59
+ f.runId = { eq: runId };
60
+ }
61
+ if (pipelineId) {
62
+ f.pipelineId = { eq: pipelineId };
63
+ }
64
+ if (level) {
65
+ f.level = { eq: level };
66
+ }
67
+ if (search) {
68
+ f.message = { contains: search };
69
+ }
70
+ if (startDate) {
71
+ f.createdAt = { ...(f.createdAt || {}), after: new Date(startDate).toISOString() };
72
+ }
73
+ if (endDate) {
74
+ f.createdAt = { ...(f.createdAt || {}), before: new Date(endDate).toISOString() };
75
+ }
76
+ return Object.keys(f).length > 0 ? f : undefined;
77
+ }, [runId, pipelineId, level, search, startDate, endDate]);
78
+
79
+ const logsQuery = useLogs({
80
+ filter,
81
+ sort: { createdAt: 'DESC' },
82
+ skip: (page - 1) * pageSize,
83
+ take: pageSize,
84
+ });
85
+
86
+ const logs = logsQuery.data?.items ?? [];
87
+ const totalItems = logsQuery.data?.totalItems ?? 0;
88
+ const totalPages = Math.ceil(totalItems / pageSize);
89
+ const pipelines = pipelinesQuery.data?.items ?? [];
90
+
91
+ const handleRefetch = React.useCallback(() => logsQuery.refetch(), [logsQuery.refetch]);
92
+
93
+ const handleRunIdChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
94
+ setRunId(e.target.value);
95
+ setPage(1);
96
+ }, []);
97
+
98
+ const handleClearRunId = React.useCallback(() => {
99
+ setRunId('');
100
+ setPage(1);
101
+ }, []);
102
+
103
+ const handlePipelineChange = React.useCallback((v: string) => {
104
+ setPipelineId(v === FILTER_VALUES.ALL ? '' : v);
105
+ setPage(1);
106
+ }, []);
107
+
108
+ const handleLevelChange = React.useCallback((v: string) => {
109
+ setLevel(v === FILTER_VALUES.ALL ? '' : v);
110
+ setPage(1);
111
+ }, []);
112
+
113
+ const handleStartDateChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
114
+ setStartDate(e.target.value);
115
+ setPage(1);
116
+ }, []);
117
+
118
+ const handleEndDateChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
119
+ setEndDate(e.target.value);
120
+ setPage(1);
121
+ }, []);
122
+
123
+ const handleSearchChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
124
+ setSearch(e.target.value);
125
+ setPage(1);
126
+ }, []);
127
+
128
+ const handlePrevPage = React.useCallback(() => {
129
+ setPage(p => Math.max(1, p - 1));
130
+ }, []);
131
+
132
+ const handleNextPage = React.useCallback(() => {
133
+ setPage(p => Math.min(totalPages, p + 1));
134
+ }, [totalPages]);
135
+
136
+ const handleSelectLog = React.useCallback((log: DataHubLog) => {
137
+ setSelectedLog(log);
138
+ }, []);
139
+
140
+ const handleCloseDrawer = React.useCallback(() => {
141
+ setSelectedLog(null);
142
+ }, []);
143
+
144
+ const handleExport = React.useCallback(() => {
145
+ try {
146
+ const data = logs.map((log) => ({
147
+ timestamp: log.createdAt,
148
+ level: log.level,
149
+ pipeline: log.pipeline?.code ?? '',
150
+ step: log.stepKey ?? '',
151
+ message: log.message,
152
+ duration: log.durationMs,
153
+ recordsProcessed: log.recordsProcessed,
154
+ recordsFailed: log.recordsFailed,
155
+ }));
156
+ const json = JSON.stringify(data, null, 2);
157
+ const blob = new Blob([json], { type: 'application/json' });
158
+ const url = URL.createObjectURL(blob);
159
+ const downloadLink = document.createElement('a');
160
+ downloadLink.href = url;
161
+ downloadLink.download = `datahub-logs-${new Date().toISOString().split('T')[0]}.json`;
162
+ downloadLink.click();
163
+ // Revoke blob URL after 1 second to ensure download completes
164
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
165
+ toast.success(TOAST_LOG.EXPORT_SUCCESS);
166
+ } catch {
167
+ toast.error(TOAST_LOG.EXPORT_ERROR);
168
+ }
169
+ }, [logs]);
170
+
171
+ if (logsQuery.isError) {
172
+ return (
173
+ <ErrorState
174
+ title="Failed to load logs"
175
+ message={logsQuery.error?.message || 'An unexpected error occurred'}
176
+ onRetry={handleRefetch}
177
+ />
178
+ );
179
+ }
180
+
181
+ return (
182
+ <div className="space-y-4">
183
+ <Card>
184
+ <CardContent className="pt-4">
185
+ <div className="flex items-center gap-2 mb-3">
186
+ <Filter className="w-4 h-4 text-muted-foreground" />
187
+ <span className="text-sm font-medium">Filters</span>
188
+ </div>
189
+ {runId && (
190
+ <div className="flex items-center gap-2 mb-2">
191
+ <span className="text-xs text-muted-foreground">Filtered by Run ID:</span>
192
+ <span className="inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-xs font-mono">
193
+ {runId}
194
+ <button type="button" onClick={handleClearRunId} className="ml-1 hover:text-destructive" aria-label="Clear run ID filter">
195
+ <X className="h-3 w-3" />
196
+ </button>
197
+ </span>
198
+ </div>
199
+ )}
200
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-7 gap-3" data-testid="datahub-logs-filters">
201
+ <div className="relative">
202
+ <Hash className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
203
+ <Input
204
+ value={runId}
205
+ onChange={handleRunIdChange}
206
+ placeholder="Run ID..."
207
+ className="pl-9"
208
+ data-testid="datahub-logs-filter-run-id"
209
+ aria-label="Filter by run ID"
210
+ />
211
+ </div>
212
+ <Select value={pipelineId || FILTER_VALUES.ALL} onValueChange={handlePipelineChange}>
213
+ <SelectTrigger data-testid="datahub-logs-filter-pipeline">
214
+ <SelectValue placeholder="All Pipelines" />
215
+ </SelectTrigger>
216
+ <SelectContent>
217
+ <SelectItem value={FILTER_VALUES.ALL}>All Pipelines</SelectItem>
218
+ {pipelines.map((p) => (
219
+ <SelectItem key={p.id} value={p.id}>
220
+ {p.name}
221
+ </SelectItem>
222
+ ))}
223
+ </SelectContent>
224
+ </Select>
225
+
226
+ <Select value={level || FILTER_VALUES.ALL} onValueChange={handleLevelChange}>
227
+ <SelectTrigger data-testid="datahub-logs-filter-level">
228
+ <SelectValue placeholder="All Levels" />
229
+ </SelectTrigger>
230
+ <SelectContent>
231
+ <SelectItem value={FILTER_VALUES.ALL}>All Levels</SelectItem>
232
+ {logLevelOptions.map((opt) => (
233
+ <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
234
+ ))}
235
+ </SelectContent>
236
+ </Select>
237
+
238
+ <div className="relative">
239
+ <Calendar className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
240
+ <Input
241
+ type="date"
242
+ value={startDate}
243
+ onChange={handleStartDateChange}
244
+ className="pl-9"
245
+ placeholder="Start date"
246
+ data-testid="datahub-logs-filter-start-date"
247
+ aria-label="Filter by start date"
248
+ />
249
+ </div>
250
+
251
+ <div className="relative">
252
+ <Calendar className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
253
+ <Input
254
+ type="date"
255
+ value={endDate}
256
+ onChange={handleEndDateChange}
257
+ className="pl-9"
258
+ placeholder="End date"
259
+ data-testid="datahub-logs-filter-end-date"
260
+ aria-label="Filter by end date"
261
+ />
262
+ </div>
263
+
264
+ <div className="relative">
265
+ <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
266
+ <Input
267
+ value={search}
268
+ onChange={handleSearchChange}
269
+ placeholder="Search logs..."
270
+ className="pl-9"
271
+ data-testid="datahub-logs-search"
272
+ aria-label="Search log messages"
273
+ />
274
+ </div>
275
+
276
+ <div className="flex gap-2">
277
+ <Button variant="outline" size="icon" onClick={handleRefetch} disabled={logsQuery.isLoading} data-testid="datahub-logs-refresh-button" aria-label="Refresh logs">
278
+ <RefreshCw className={`w-4 h-4 ${logsQuery.isLoading ? 'animate-spin' : ''}`} />
279
+ </Button>
280
+ <Button variant="outline" onClick={handleExport} disabled={logs.length === 0} data-testid="datahub-logs-export-button">
281
+ <Download className="w-4 h-4 mr-2" />
282
+ Export
283
+ </Button>
284
+ </div>
285
+ </div>
286
+ </CardContent>
287
+ </Card>
288
+
289
+ <Card>
290
+ <CardHeader className="pb-3">
291
+ <div className="flex items-center justify-between">
292
+ <CardTitle className="text-base">
293
+ Log Entries ({totalItems.toLocaleString()})
294
+ </CardTitle>
295
+ <div className="flex items-center gap-2" data-testid="datahub-logs-pagination">
296
+ <Button
297
+ variant="outline"
298
+ size="sm"
299
+ onClick={handlePrevPage}
300
+ disabled={page === 1}
301
+ data-testid="datahub-logs-prev-page"
302
+ aria-label="Previous page"
303
+ >
304
+ <ChevronLeft className="w-4 h-4" />
305
+ </Button>
306
+ <span className="text-sm text-muted-foreground">
307
+ Page {page} of {totalPages || 1}
308
+ </span>
309
+ <Button
310
+ variant="outline"
311
+ size="sm"
312
+ onClick={handleNextPage}
313
+ disabled={page >= totalPages}
314
+ data-testid="datahub-logs-next-page"
315
+ aria-label="Next page"
316
+ >
317
+ <ChevronRight className="w-4 h-4" />
318
+ </Button>
319
+ </div>
320
+ </div>
321
+ </CardHeader>
322
+ <CardContent>
323
+ <div className="border rounded-lg overflow-hidden">
324
+ <table className="w-full text-sm" data-testid="datahub-logs-table">
325
+ <thead>
326
+ <tr className="bg-muted">
327
+ <th className="text-left px-3 py-2 w-36">Time</th>
328
+ <th className="text-left px-3 py-2 w-20">Level</th>
329
+ <th className="text-left px-3 py-2 w-32">Pipeline</th>
330
+ <th className="text-left px-3 py-2 w-24">Step</th>
331
+ <th className="text-left px-3 py-2">Message</th>
332
+ <th className="text-right px-3 py-2 w-20">Duration</th>
333
+ <th className="text-right px-3 py-2 w-24">Records</th>
334
+ </tr>
335
+ </thead>
336
+ <tbody>
337
+ {logsQuery.isLoading && logs.length === 0 ? (
338
+ <tr>
339
+ <td colSpan={7} className="p-4">
340
+ <LoadingState type="table" rows={10} message="Loading log entries..." />
341
+ </td>
342
+ </tr>
343
+ ) : logs.length === 0 ? (
344
+ <tr>
345
+ <td colSpan={7} className="px-3 py-8 text-center text-muted-foreground">
346
+ No log entries found
347
+ </td>
348
+ </tr>
349
+ ) : (
350
+ logs.map((log) => (
351
+ <LogTableRow
352
+ key={log.id}
353
+ log={log}
354
+ onSelect={handleSelectLog}
355
+ />
356
+ ))
357
+ )}
358
+ </tbody>
359
+ </table>
360
+ </div>
361
+ </CardContent>
362
+ </Card>
363
+
364
+ <LogDetailDrawer log={selectedLog} onClose={handleCloseDrawer} />
365
+ </div>
366
+ );
367
+ }
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import { memo } from 'react';
3
+ import { AlertCircle, AlertTriangle, Bug, Info } from 'lucide-react';
4
+ import { getLogLevelColor } from '../../../constants';
5
+
6
+ /**
7
+ * Compact level badge showing abbreviated level with count
8
+ */
9
+ export const LevelBadge = memo(function LevelBadge({ level, count }: Readonly<{ level: string; count: number }>) {
10
+ return (
11
+ <span className={`text-xs px-1.5 py-0.5 rounded ${getLogLevelColor(level)}`}>
12
+ {level.charAt(0)}: {count}
13
+ </span>
14
+ );
15
+ });
16
+
17
+ /**
18
+ * Log level badge with icon and full level text
19
+ */
20
+ export const LogLevelBadge = memo(function LogLevelBadge({ level }: Readonly<{ level: string }>) {
21
+ const icons: Record<string, React.ReactNode> = {
22
+ DEBUG: <Bug className="w-3 h-3" />,
23
+ INFO: <Info className="w-3 h-3" />,
24
+ WARN: <AlertTriangle className="w-3 h-3" />,
25
+ ERROR: <AlertCircle className="w-3 h-3" />,
26
+ };
27
+ const icon = icons[level] ?? icons.INFO;
28
+ return (
29
+ <span className={`inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded ${getLogLevelColor(level)}`}>
30
+ {icon}
31
+ {level}
32
+ </span>
33
+ );
34
+ });
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+ import { formatSmartDateTime } from '../../../utils';
3
+ import { COMPONENT_WIDTHS } from '../../../constants';
4
+ import { LogLevelBadge } from './LogLevelBadge';
5
+ import type { DataHubLog } from '../../../types';
6
+
7
+ interface LogTableRowProps {
8
+ log: DataHubLog;
9
+ onSelect: (log: DataHubLog) => void;
10
+ }
11
+
12
+ /**
13
+ * Memoized log row component to avoid re-creating onClick handlers in the parent loop.
14
+ * Displays a single log entry in the log explorer table.
15
+ */
16
+ export const LogTableRow = React.memo(function LogTableRow({
17
+ log,
18
+ onSelect,
19
+ }: LogTableRowProps) {
20
+ const handleClick = React.useCallback(() => onSelect(log), [onSelect, log]);
21
+ const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {
22
+ if (e.key === 'Enter') {
23
+ onSelect(log);
24
+ }
25
+ }, [onSelect, log]);
26
+
27
+ return (
28
+ <tr
29
+ role="row"
30
+ className="border-t hover:bg-muted/30 cursor-pointer"
31
+ onClick={handleClick}
32
+ onKeyDown={handleKeyDown}
33
+ tabIndex={0}
34
+ data-testid={`datahub-log-row-${log.id}`}
35
+ >
36
+ <td className="px-3 py-2 text-muted-foreground whitespace-nowrap">
37
+ {formatSmartDateTime(log.createdAt)}
38
+ </td>
39
+ <td className="px-3 py-2">
40
+ <LogLevelBadge level={log.level} />
41
+ </td>
42
+ <td className="px-3 py-2 font-mono text-xs text-muted-foreground">
43
+ {log.pipeline?.code ?? '—'}
44
+ </td>
45
+ <td className="px-3 py-2 font-mono text-xs text-muted-foreground">
46
+ {log.stepKey ?? '—'}
47
+ </td>
48
+ <td className={`px-3 py-2 ${COMPONENT_WIDTHS.TABLE_CELL_MAX_SM} truncate`} title={log.message}>
49
+ {log.message}
50
+ </td>
51
+ <td className="px-3 py-2 text-right text-muted-foreground">
52
+ {log.durationMs != null ? `${log.durationMs}ms` : '—'}
53
+ </td>
54
+ <td className="px-3 py-2 text-right">
55
+ {log.recordsProcessed != null ? (
56
+ <span>
57
+ {log.recordsProcessed}
58
+ {log.recordsFailed > 0 && (
59
+ <span className="text-red-600 ml-1">
60
+ ({log.recordsFailed} failed)
61
+ </span>
62
+ )}
63
+ </span>
64
+ ) : (
65
+ '—'
66
+ )}
67
+ </td>
68
+ </tr>
69
+ );
70
+ });