@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.
- package/CHANGELOG.md +5 -0
- package/dist/dashboard/components/common/ConnectionConfigEditor.tsx +589 -0
- package/dist/dashboard/components/common/HeadersEditor.tsx +90 -0
- package/dist/dashboard/components/common/ValidationFeedback.tsx +17 -0
- package/dist/dashboard/components/common/index.ts +10 -0
- package/dist/dashboard/components/pipelines/PipelineEditor.tsx +504 -0
- package/dist/dashboard/components/pipelines/PipelineExport.tsx +63 -0
- package/dist/dashboard/components/pipelines/PipelineImport.tsx +87 -0
- package/dist/dashboard/components/pipelines/ReactFlowPipelineEditor.tsx +539 -0
- package/dist/dashboard/components/pipelines/shared/NodePropertiesPanel.tsx +146 -0
- package/dist/dashboard/components/pipelines/shared/PipelineNode.tsx +155 -0
- package/dist/dashboard/components/pipelines/shared/PipelineSettingsPanel.tsx +392 -0
- package/dist/dashboard/components/pipelines/shared/StepListItem.tsx +144 -0
- package/dist/dashboard/components/pipelines/shared/index.ts +33 -0
- package/dist/dashboard/components/pipelines/shared/visual-node-config.ts +169 -0
- package/dist/dashboard/components/shared/LoadMoreButton.tsx +18 -0
- package/dist/dashboard/components/shared/entity-selector/EntitySelector.tsx +59 -0
- package/dist/dashboard/components/shared/entity-selector/index.ts +1 -0
- package/dist/dashboard/components/shared/error-boundary/ErrorBoundary.tsx +90 -0
- package/dist/dashboard/components/shared/error-boundary/index.ts +1 -0
- package/dist/dashboard/components/shared/feedback/EmptyState.tsx +36 -0
- package/dist/dashboard/components/shared/feedback/ErrorState.tsx +69 -0
- package/dist/dashboard/components/shared/feedback/LoadingState.tsx +104 -0
- package/dist/dashboard/components/shared/feedback/ValidationErrorDisplay.tsx +29 -0
- package/dist/dashboard/components/shared/feedback/index.ts +4 -0
- package/dist/dashboard/components/shared/file-dropzone/FileDropzone.tsx +167 -0
- package/dist/dashboard/components/shared/file-dropzone/index.ts +1 -0
- package/dist/dashboard/components/shared/filter-conditions-editor/FilterConditionsEditor.tsx +226 -0
- package/dist/dashboard/components/shared/filter-conditions-editor/index.ts +1 -0
- package/dist/dashboard/components/shared/index.ts +45 -0
- package/dist/dashboard/components/shared/schema-form/SchemaFormRenderer.tsx +248 -0
- package/dist/dashboard/components/shared/schema-form/fields/BooleanField.tsx +26 -0
- package/dist/dashboard/components/shared/schema-form/fields/FieldWrapper.tsx +28 -0
- package/dist/dashboard/components/shared/schema-form/fields/FileUploadField.tsx +171 -0
- package/dist/dashboard/components/shared/schema-form/fields/JsonField.tsx +132 -0
- package/dist/dashboard/components/shared/schema-form/fields/NumberField.tsx +33 -0
- package/dist/dashboard/components/shared/schema-form/fields/SelectField.tsx +70 -0
- package/dist/dashboard/components/shared/schema-form/fields/StringField.tsx +36 -0
- package/dist/dashboard/components/shared/schema-form/fields/TextareaField.tsx +31 -0
- package/dist/dashboard/components/shared/schema-form/fields/index.ts +23 -0
- package/dist/dashboard/components/shared/schema-form/index.ts +2 -0
- package/dist/dashboard/components/shared/schema-form/utils.ts +3 -0
- package/dist/dashboard/components/shared/selectable-card/SelectableCard.tsx +65 -0
- package/dist/dashboard/components/shared/selectable-card/index.ts +1 -0
- package/dist/dashboard/components/shared/stat-card/StatCard.tsx +121 -0
- package/dist/dashboard/components/shared/stat-card/index.ts +1 -0
- package/dist/dashboard/components/shared/step-config/AdapterRequiredWarning.tsx +25 -0
- package/dist/dashboard/components/shared/step-config/AdapterSelector.tsx +109 -0
- package/dist/dashboard/components/shared/step-config/AdvancedEditors.tsx +634 -0
- package/dist/dashboard/components/shared/step-config/EnrichConfigComponent.tsx +295 -0
- package/dist/dashboard/components/shared/step-config/ExtractTestResults.tsx +143 -0
- package/dist/dashboard/components/shared/step-config/GateConfigComponent.tsx +127 -0
- package/dist/dashboard/components/shared/step-config/LoadTestResults.tsx +104 -0
- package/dist/dashboard/components/shared/step-config/OperatorCard.tsx +266 -0
- package/dist/dashboard/components/shared/step-config/OperatorCheatSheetButton.tsx +54 -0
- package/dist/dashboard/components/shared/step-config/OperatorFieldInput.tsx +209 -0
- package/dist/dashboard/components/shared/step-config/RetrySettingsComponent.tsx +111 -0
- package/dist/dashboard/components/shared/step-config/RouteConfigComponent.tsx +125 -0
- package/dist/dashboard/components/shared/step-config/StepConfigPanel.tsx +564 -0
- package/dist/dashboard/components/shared/step-config/StepTester.tsx +165 -0
- package/dist/dashboard/components/shared/step-config/TestResultContainer.tsx +57 -0
- package/dist/dashboard/components/shared/step-config/TransformTestResults.tsx +130 -0
- package/dist/dashboard/components/shared/step-config/ValidateConfigComponent.tsx +334 -0
- package/dist/dashboard/components/shared/step-config/index.ts +29 -0
- package/dist/dashboard/components/shared/step-config/step-test-handlers.ts +297 -0
- package/dist/dashboard/components/shared/trigger-config/TriggerForm.tsx +478 -0
- package/dist/dashboard/components/shared/trigger-config/index.ts +1 -0
- package/dist/dashboard/components/shared/triggers-panel/TriggersPanel.tsx +281 -0
- package/dist/dashboard/components/shared/triggers-panel/index.ts +1 -0
- package/dist/dashboard/components/shared/wizard/ConfigurationNameCard.tsx +59 -0
- package/dist/dashboard/components/shared/wizard/SummaryCard.tsx +53 -0
- package/dist/dashboard/components/shared/wizard/WizardFooter.tsx +47 -0
- package/dist/dashboard/components/shared/wizard/WizardProgressBar.tsx +78 -0
- package/dist/dashboard/components/shared/wizard/index.ts +4 -0
- package/dist/dashboard/components/shared/wizard-trigger/TriggerSchemaFields.tsx +128 -0
- package/dist/dashboard/components/shared/wizard-trigger/TriggerSelector.tsx +33 -0
- package/dist/dashboard/components/shared/wizard-trigger/index.ts +2 -0
- package/dist/dashboard/components/templates/TemplateGallery.tsx +210 -0
- package/dist/dashboard/components/templates/TemplatePreview.tsx +214 -0
- package/dist/dashboard/components/templates/index.ts +4 -0
- package/dist/dashboard/components/wizards/export-wizard/DestinationStep.tsx +207 -0
- package/dist/dashboard/components/wizards/export-wizard/ExportWizard.tsx +221 -0
- package/dist/dashboard/components/wizards/export-wizard/FieldsStep.tsx +159 -0
- package/dist/dashboard/components/wizards/export-wizard/FormatStep.tsx +246 -0
- package/dist/dashboard/components/wizards/export-wizard/ReviewStep.tsx +231 -0
- package/dist/dashboard/components/wizards/export-wizard/SourceStep.tsx +154 -0
- package/dist/dashboard/components/wizards/export-wizard/TriggerStep.tsx +234 -0
- package/dist/dashboard/components/wizards/export-wizard/constants.ts +73 -0
- package/dist/dashboard/components/wizards/export-wizard/index.ts +2 -0
- package/dist/dashboard/components/wizards/export-wizard/types.ts +39 -0
- package/dist/dashboard/components/wizards/import-wizard/ImportWizard.tsx +350 -0
- package/dist/dashboard/components/wizards/import-wizard/MappingStep.tsx +286 -0
- package/dist/dashboard/components/wizards/import-wizard/PreviewStep.tsx +79 -0
- package/dist/dashboard/components/wizards/import-wizard/ReviewStep.tsx +266 -0
- package/dist/dashboard/components/wizards/import-wizard/SourceStep.tsx +537 -0
- package/dist/dashboard/components/wizards/import-wizard/StrategyStep.tsx +328 -0
- package/dist/dashboard/components/wizards/import-wizard/TargetStep.tsx +76 -0
- package/dist/dashboard/components/wizards/import-wizard/TemplateStep.tsx +116 -0
- package/dist/dashboard/components/wizards/import-wizard/TransformStep.tsx +666 -0
- package/dist/dashboard/components/wizards/import-wizard/TriggerStep.tsx +51 -0
- package/dist/dashboard/components/wizards/import-wizard/constants.ts +104 -0
- package/dist/dashboard/components/wizards/import-wizard/index.ts +3 -0
- package/dist/dashboard/components/wizards/import-wizard/types.ts +35 -0
- package/dist/dashboard/components/wizards/index.ts +7 -0
- package/dist/dashboard/components/wizards/shared/WizardStepContainer.tsx +27 -0
- package/dist/dashboard/components/wizards/shared/constants.ts +16 -0
- package/dist/dashboard/components/wizards/shared/index.ts +10 -0
- package/dist/dashboard/constants/colors.ts +25 -0
- package/dist/dashboard/constants/connection-defaults.ts +7 -0
- package/dist/dashboard/constants/connection-types.ts +1 -0
- package/dist/dashboard/constants/defaults.ts +18 -0
- package/dist/dashboard/constants/editor.ts +69 -0
- package/dist/dashboard/constants/enum-maps.ts +18 -0
- package/dist/dashboard/constants/fallbacks.ts +44 -0
- package/dist/dashboard/constants/file-format-registry.ts +206 -0
- package/dist/dashboard/constants/index.ts +24 -0
- package/dist/dashboard/constants/navigation.ts +29 -0
- package/dist/dashboard/constants/permissions.ts +41 -0
- package/dist/dashboard/constants/placeholders.ts +77 -0
- package/dist/dashboard/constants/routes.ts +12 -0
- package/dist/dashboard/constants/run-status.ts +1 -0
- package/dist/dashboard/constants/sentinel-values.ts +12 -0
- package/dist/dashboard/constants/step-configs.ts +9 -0
- package/dist/dashboard/constants/step-mappings.ts +170 -0
- package/dist/dashboard/constants/steps.ts +37 -0
- package/dist/dashboard/constants/toast-messages.ts +149 -0
- package/dist/dashboard/constants/triggers.ts +5 -0
- package/dist/dashboard/constants/ui-config.ts +139 -0
- package/dist/dashboard/constants/ui-dimensions.ts +145 -0
- package/dist/dashboard/constants/ui-states.ts +28 -0
- package/dist/dashboard/constants/ui-types.ts +85 -0
- package/dist/dashboard/constants/validation-patterns.ts +26 -0
- package/dist/dashboard/gql/gql.ts +370 -0
- package/dist/dashboard/gql/graphql.ts +10378 -0
- package/dist/dashboard/gql/index.ts +1 -0
- package/dist/dashboard/hooks/api/index.ts +115 -0
- package/dist/dashboard/hooks/api/mutation-helpers.ts +34 -0
- package/dist/dashboard/hooks/api/use-adapters.ts +92 -0
- package/dist/dashboard/hooks/api/use-config-options.ts +513 -0
- package/dist/dashboard/hooks/api/use-connections.ts +84 -0
- package/dist/dashboard/hooks/api/use-entity-field-schemas.ts +99 -0
- package/dist/dashboard/hooks/api/use-entity-loaders.ts +45 -0
- package/dist/dashboard/hooks/api/use-hooks.ts +68 -0
- package/dist/dashboard/hooks/api/use-logs.ts +102 -0
- package/dist/dashboard/hooks/api/use-pipeline-runs.ts +221 -0
- package/dist/dashboard/hooks/api/use-pipelines.ts +279 -0
- package/dist/dashboard/hooks/api/use-queues.ts +141 -0
- package/dist/dashboard/hooks/api/use-secrets.ts +75 -0
- package/dist/dashboard/hooks/api/use-settings.ts +55 -0
- package/dist/dashboard/hooks/api/use-step-tester.ts +79 -0
- package/dist/dashboard/hooks/index.ts +13 -0
- package/dist/dashboard/hooks/use-adapter-catalog.ts +253 -0
- package/dist/dashboard/hooks/use-export-templates.ts +80 -0
- package/dist/dashboard/hooks/use-import-templates.ts +139 -0
- package/dist/dashboard/hooks/use-load-more.ts +29 -0
- package/dist/dashboard/hooks/use-stable-keys.ts +54 -0
- package/dist/dashboard/hooks/use-trigger-types.ts +100 -0
- package/dist/dashboard/hooks/use-wizard-navigation.ts +128 -0
- package/dist/dashboard/index.tsx +55 -0
- package/dist/dashboard/routes/adapters/AdapterCard.tsx +102 -0
- package/dist/dashboard/routes/adapters/AdapterConstants.tsx +20 -0
- package/dist/dashboard/routes/adapters/AdapterDetail.tsx +208 -0
- package/dist/dashboard/routes/adapters/AdapterTypeSection.tsx +105 -0
- package/dist/dashboard/routes/adapters/AdaptersPage.tsx +276 -0
- package/dist/dashboard/routes/adapters/AdaptersTable.tsx +107 -0
- package/dist/dashboard/routes/adapters/index.ts +1 -0
- package/dist/dashboard/routes/connections/ConnectionDetail.tsx +218 -0
- package/dist/dashboard/routes/connections/ConnectionsList.tsx +34 -0
- package/dist/dashboard/routes/connections/index.ts +2 -0
- package/dist/dashboard/routes/hooks/Hooks.tsx +425 -0
- package/dist/dashboard/routes/hooks/hook-stages.ts +52 -0
- package/dist/dashboard/routes/hooks/index.ts +1 -0
- package/dist/dashboard/routes/index.ts +8 -0
- package/dist/dashboard/routes/logs/Logs.tsx +93 -0
- package/dist/dashboard/routes/logs/components/LogDetailDrawer.tsx +118 -0
- package/dist/dashboard/routes/logs/components/LogExplorerTab.tsx +367 -0
- package/dist/dashboard/routes/logs/components/LogLevelBadge.tsx +34 -0
- package/dist/dashboard/routes/logs/components/LogTableRow.tsx +70 -0
- package/dist/dashboard/routes/logs/components/LogsOverviewTab.tsx +178 -0
- package/dist/dashboard/routes/logs/components/RealtimeLogTab.tsx +122 -0
- package/dist/dashboard/routes/logs/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/ErrorAuditList.tsx +39 -0
- package/dist/dashboard/routes/pipelines/ExportWizardPage.tsx +96 -0
- package/dist/dashboard/routes/pipelines/ImportWizardPage.tsx +104 -0
- package/dist/dashboard/routes/pipelines/PipelineDetail.tsx +211 -0
- package/dist/dashboard/routes/pipelines/PipelineRunsBlock.tsx +377 -0
- package/dist/dashboard/routes/pipelines/PipelinesList.tsx +87 -0
- package/dist/dashboard/routes/pipelines/RetryPatchHelper.tsx +51 -0
- package/dist/dashboard/routes/pipelines/RunDetailsPanel.tsx +238 -0
- package/dist/dashboard/routes/pipelines/RunErrorsList.tsx +116 -0
- package/dist/dashboard/routes/pipelines/StepCounters.tsx +24 -0
- package/dist/dashboard/routes/pipelines/StepSummaryTable.tsx +36 -0
- package/dist/dashboard/routes/pipelines/components/DryRunDialog.tsx +341 -0
- package/dist/dashboard/routes/pipelines/components/PipelineActionButtons.tsx +201 -0
- package/dist/dashboard/routes/pipelines/components/PipelineEditorToggle.tsx +116 -0
- package/dist/dashboard/routes/pipelines/components/PipelineFormFields.tsx +156 -0
- package/dist/dashboard/routes/pipelines/components/PipelineWebhookInfo.tsx +111 -0
- package/dist/dashboard/routes/pipelines/components/ReviewActionsPanel.tsx +342 -0
- package/dist/dashboard/routes/pipelines/components/ValidationPanel.tsx +121 -0
- package/dist/dashboard/routes/pipelines/components/VersionHistoryDialog.tsx +131 -0
- package/dist/dashboard/routes/pipelines/components/index.ts +25 -0
- package/dist/dashboard/routes/pipelines/hooks/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/hooks/use-pipeline-validation.ts +114 -0
- package/dist/dashboard/routes/pipelines/index.ts +4 -0
- package/dist/dashboard/routes/pipelines/utils/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/utils/pipeline-conversion.ts +261 -0
- package/dist/dashboard/routes/queues/ConsumersTable.tsx +134 -0
- package/dist/dashboard/routes/queues/DeadLettersTable.tsx +118 -0
- package/dist/dashboard/routes/queues/FailedRunsTable.tsx +74 -0
- package/dist/dashboard/routes/queues/QueuesPage.tsx +290 -0
- package/dist/dashboard/routes/queues/index.ts +1 -0
- package/dist/dashboard/routes/queues/types.ts +22 -0
- package/dist/dashboard/routes/secrets/SecretDetail.tsx +278 -0
- package/dist/dashboard/routes/secrets/SecretsList.tsx +34 -0
- package/dist/dashboard/routes/secrets/index.ts +2 -0
- package/dist/dashboard/routes/settings/Settings.tsx +343 -0
- package/dist/dashboard/routes/settings/index.ts +1 -0
- package/dist/dashboard/types/index.ts +89 -0
- package/dist/dashboard/types/pipeline.ts +51 -0
- package/dist/dashboard/types/ui-types.ts +400 -0
- package/dist/dashboard/types/wizard.ts +235 -0
- package/dist/dashboard/utils/adapter-grouping.ts +43 -0
- package/dist/dashboard/utils/column-analysis.ts +11 -0
- package/dist/dashboard/utils/field-preparation.ts +31 -0
- package/dist/dashboard/utils/form-validation.ts +373 -0
- package/dist/dashboard/utils/formatters.ts +92 -0
- package/dist/dashboard/utils/icon-resolver.ts +35 -0
- package/dist/dashboard/utils/index.ts +60 -0
- package/dist/dashboard/utils/query-key-factory.ts +54 -0
- package/dist/dashboard/utils/step-helpers.ts +32 -0
- package/dist/dashboard/utils/string-helpers.ts +4 -0
- package/dist/dashboard/utils/template-helpers.ts +26 -0
- package/dist/dashboard/utils/trigger-sync.ts +138 -0
- package/dist/dashboard/utils/wizard-to-pipeline.ts +569 -0
- package/package.json +2 -2
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo, memo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
CardDescription,
|
|
9
|
+
Label,
|
|
10
|
+
Textarea,
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogContent,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
DialogFooter,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
Separator,
|
|
18
|
+
} from '@vendure/dashboard';
|
|
19
|
+
import {
|
|
20
|
+
Trash2,
|
|
21
|
+
Settings,
|
|
22
|
+
GripVertical,
|
|
23
|
+
Zap,
|
|
24
|
+
Check,
|
|
25
|
+
ChevronRight,
|
|
26
|
+
ArrowRight,
|
|
27
|
+
} from 'lucide-react';
|
|
28
|
+
import { WizardStepContainer } from '../shared';
|
|
29
|
+
import { EmptyState } from '../../shared/feedback';
|
|
30
|
+
import { SchemaFormRenderer } from '../../shared/schema-form';
|
|
31
|
+
import {
|
|
32
|
+
AdvancedMapEditor,
|
|
33
|
+
AdvancedTemplateEditor,
|
|
34
|
+
AdvancedWhenEditor,
|
|
35
|
+
} from '../../shared/step-config/AdvancedEditors';
|
|
36
|
+
import { STEP_CONTENT } from './constants';
|
|
37
|
+
import type { TransformTypeOption } from './constants';
|
|
38
|
+
import type { ImportConfiguration, TransformationType } from './types';
|
|
39
|
+
import type { WizardTransformationStep } from '../../../types/wizard';
|
|
40
|
+
import type { AdapterSchemaField } from '../../../types';
|
|
41
|
+
import { useAdaptersByType } from '../../../hooks/api/use-adapters';
|
|
42
|
+
import { resolveIconName } from '../../../utils/icon-resolver';
|
|
43
|
+
|
|
44
|
+
interface TransformStepProps {
|
|
45
|
+
config: Partial<ImportConfiguration>;
|
|
46
|
+
updateConfig: (updates: Partial<ImportConfiguration>) => void;
|
|
47
|
+
errors?: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function TransformStep({ config, updateConfig, errors = {} }: TransformStepProps) {
|
|
51
|
+
const [editingTransform, setEditingTransform] = useState<WizardTransformationStep | null>(null);
|
|
52
|
+
const { data: operators } = useAdaptersByType('OPERATOR');
|
|
53
|
+
const transformTypes = useTransformTypesFromData(operators);
|
|
54
|
+
|
|
55
|
+
const addTransform = useCallback((type: TransformationType) => {
|
|
56
|
+
updateConfig({
|
|
57
|
+
transformations: [
|
|
58
|
+
...(config.transformations ?? []),
|
|
59
|
+
{ id: `${type}-${Date.now()}`, type, config: {} },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
}, [config.transformations, updateConfig]);
|
|
63
|
+
|
|
64
|
+
const removeTransform = useCallback((id: string) => {
|
|
65
|
+
updateConfig({
|
|
66
|
+
transformations: (config.transformations ?? []).filter(t => t.id !== id),
|
|
67
|
+
});
|
|
68
|
+
}, [config.transformations, updateConfig]);
|
|
69
|
+
|
|
70
|
+
const openSettings = useCallback((transform: WizardTransformationStep) => {
|
|
71
|
+
setEditingTransform({ ...transform, config: { ...transform.config } });
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const saveSettings = useCallback((updatedConfig: Record<string, unknown>) => {
|
|
75
|
+
if (!editingTransform) return;
|
|
76
|
+
updateConfig({
|
|
77
|
+
transformations: (config.transformations ?? []).map(t =>
|
|
78
|
+
t.id === editingTransform.id ? { ...t, config: updatedConfig } : t,
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
setEditingTransform(null);
|
|
82
|
+
}, [editingTransform, config.transformations, updateConfig]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<WizardStepContainer
|
|
86
|
+
title={STEP_CONTENT.transform.title}
|
|
87
|
+
description={STEP_CONTENT.transform.description}
|
|
88
|
+
>
|
|
89
|
+
<TransformTypeButtons onAdd={addTransform} operators={operators} />
|
|
90
|
+
|
|
91
|
+
{(config.transformations?.length ?? 0) > 0 && (
|
|
92
|
+
<TransformPipelineCard
|
|
93
|
+
transformations={config.transformations!}
|
|
94
|
+
transformTypes={transformTypes}
|
|
95
|
+
onRemove={removeTransform}
|
|
96
|
+
onSettings={openSettings}
|
|
97
|
+
/>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{(config.transformations?.length ?? 0) === 0 && (
|
|
101
|
+
<EmptyState
|
|
102
|
+
icon={<Zap className="h-8 w-8" />}
|
|
103
|
+
title={STEP_CONTENT.transform.emptyTitle}
|
|
104
|
+
description={STEP_CONTENT.transform.emptyDescription}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{editingTransform && (
|
|
109
|
+
<TransformConfigDialog
|
|
110
|
+
transform={editingTransform}
|
|
111
|
+
transformTypes={transformTypes}
|
|
112
|
+
operators={operators}
|
|
113
|
+
onSave={saveSettings}
|
|
114
|
+
onClose={() => setEditingTransform(null)}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
</WizardStepContainer>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Category color palette for operator groups.
|
|
123
|
+
* Maps backend category keys to Tailwind-compatible color classes.
|
|
124
|
+
*/
|
|
125
|
+
const CATEGORY_COLORS: Record<string, { bg: string; border: string; text: string; accent: string; badge: string }> = {
|
|
126
|
+
DATA: { bg: 'bg-blue-50 dark:bg-blue-950/30', border: 'border-blue-200 dark:border-blue-800', text: 'text-blue-700 dark:text-blue-400', accent: 'bg-blue-500', badge: 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-400' },
|
|
127
|
+
STRING: { bg: 'bg-emerald-50 dark:bg-emerald-950/30', border: 'border-emerald-200 dark:border-emerald-800', text: 'text-emerald-700 dark:text-emerald-400', accent: 'bg-emerald-500', badge: 'bg-emerald-100 dark:bg-emerald-900/40 text-emerald-700 dark:text-emerald-400' },
|
|
128
|
+
NUMERIC: { bg: 'bg-amber-50 dark:bg-amber-950/30', border: 'border-amber-200 dark:border-amber-800', text: 'text-amber-700 dark:text-amber-400', accent: 'bg-amber-500', badge: 'bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-400' },
|
|
129
|
+
DATE: { bg: 'bg-purple-50 dark:bg-purple-950/30', border: 'border-purple-200 dark:border-purple-800', text: 'text-purple-700 dark:text-purple-400', accent: 'bg-purple-500', badge: 'bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-400' },
|
|
130
|
+
LOGIC: { bg: 'bg-rose-50 dark:bg-rose-950/30', border: 'border-rose-200 dark:border-rose-800', text: 'text-rose-700 dark:text-rose-400', accent: 'bg-rose-500', badge: 'bg-rose-100 dark:bg-rose-900/40 text-rose-700 dark:text-rose-400' },
|
|
131
|
+
VALIDATION: { bg: 'bg-orange-50 dark:bg-orange-950/30', border: 'border-orange-200 dark:border-orange-800', text: 'text-orange-700 dark:text-orange-400', accent: 'bg-orange-500', badge: 'bg-orange-100 dark:bg-orange-900/40 text-orange-700 dark:text-orange-400' },
|
|
132
|
+
JSON: { bg: 'bg-cyan-50 dark:bg-cyan-950/30', border: 'border-cyan-200 dark:border-cyan-800', text: 'text-cyan-700 dark:text-cyan-400', accent: 'bg-cyan-500', badge: 'bg-cyan-100 dark:bg-cyan-900/40 text-cyan-700 dark:text-cyan-400' },
|
|
133
|
+
AGGREGATION: { bg: 'bg-indigo-50 dark:bg-indigo-950/30', border: 'border-indigo-200 dark:border-indigo-800', text: 'text-indigo-700 dark:text-indigo-400', accent: 'bg-indigo-500', badge: 'bg-indigo-100 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-400' },
|
|
134
|
+
ENRICHMENT: { bg: 'bg-teal-50 dark:bg-teal-950/30', border: 'border-teal-200 dark:border-teal-800', text: 'text-teal-700 dark:text-teal-400', accent: 'bg-teal-500', badge: 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-400' },
|
|
135
|
+
FILE: { bg: 'bg-slate-50 dark:bg-slate-950/30', border: 'border-slate-200 dark:border-slate-800', text: 'text-slate-700 dark:text-slate-400', accent: 'bg-slate-500', badge: 'bg-slate-100 dark:bg-slate-900/40 text-slate-700 dark:text-slate-400' },
|
|
136
|
+
SCRIPT: { bg: 'bg-violet-50 dark:bg-violet-950/30', border: 'border-violet-200 dark:border-violet-800', text: 'text-violet-700 dark:text-violet-400', accent: 'bg-violet-500', badge: 'bg-violet-100 dark:bg-violet-900/40 text-violet-700 dark:text-violet-400' },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const DEFAULT_CATEGORY_COLOR = { bg: 'bg-muted', border: 'border-border', text: 'text-muted-foreground', accent: 'bg-muted-foreground', badge: 'bg-muted text-muted-foreground' };
|
|
140
|
+
|
|
141
|
+
function getCategoryColor(category: string) {
|
|
142
|
+
return CATEGORY_COLORS[category] ?? DEFAULT_CATEGORY_COLOR;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Extended transform type option with icon and category metadata */
|
|
146
|
+
interface EnrichedTransformTypeOption extends TransformTypeOption {
|
|
147
|
+
icon?: string | null;
|
|
148
|
+
color?: string | null;
|
|
149
|
+
category?: string | null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface OperatorGroup {
|
|
153
|
+
category: string;
|
|
154
|
+
label: string;
|
|
155
|
+
order: number;
|
|
156
|
+
operators: EnrichedTransformTypeOption[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type OperatorData = Array<{
|
|
160
|
+
code: string;
|
|
161
|
+
name?: string | null;
|
|
162
|
+
description?: string | null;
|
|
163
|
+
category?: string | null;
|
|
164
|
+
categoryLabel?: string | null;
|
|
165
|
+
categoryOrder?: number | null;
|
|
166
|
+
wizardHidden?: boolean | null;
|
|
167
|
+
icon?: string | null;
|
|
168
|
+
color?: string | null;
|
|
169
|
+
schema?: { fields: Array<{ key: string; label?: string | null; type: string; required?: boolean | null; defaultValue?: unknown; placeholder?: string | null; description?: string | null; options?: Array<{ value: string; label: string }> | null; group?: string | null; dependsOn?: { field: string; value: unknown; operator?: string | null } | null; validation?: { min?: number | null; max?: number | null; minLength?: number | null; maxLength?: number | null; pattern?: string | null; patternMessage?: string | null } | null }> };
|
|
170
|
+
}>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build the transform type list from dynamic operator adapters.
|
|
174
|
+
* Returns an empty array while loading (the UI shows a loading skeleton).
|
|
175
|
+
*/
|
|
176
|
+
function useTransformTypesFromData(operators: OperatorData | undefined): EnrichedTransformTypeOption[] {
|
|
177
|
+
return useMemo(() => {
|
|
178
|
+
if (!operators?.length) return [];
|
|
179
|
+
return operators
|
|
180
|
+
.filter(op => op.wizardHidden !== true)
|
|
181
|
+
.map(op => ({
|
|
182
|
+
id: op.code,
|
|
183
|
+
label: op.name ?? op.code,
|
|
184
|
+
description: op.description ?? '',
|
|
185
|
+
icon: op.icon,
|
|
186
|
+
color: op.color,
|
|
187
|
+
category: op.category,
|
|
188
|
+
}));
|
|
189
|
+
}, [operators]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Build grouped operator list for the category-based UI.
|
|
194
|
+
* Uses `categoryLabel` and `categoryOrder` from backend adapter metadata.
|
|
195
|
+
*/
|
|
196
|
+
function useTransformGroupsFromData(operators: OperatorData | undefined): OperatorGroup[] {
|
|
197
|
+
return useMemo(() => {
|
|
198
|
+
if (!operators?.length) return [];
|
|
199
|
+
|
|
200
|
+
const groups = new Map<string, { label: string; order: number; operators: EnrichedTransformTypeOption[] }>();
|
|
201
|
+
|
|
202
|
+
for (const op of operators) {
|
|
203
|
+
if (op.wizardHidden === true) continue;
|
|
204
|
+
const cat = op.category ?? 'DATA';
|
|
205
|
+
let group = groups.get(cat);
|
|
206
|
+
if (!group) {
|
|
207
|
+
const label = op.categoryLabel ?? cat;
|
|
208
|
+
const order = op.categoryOrder ?? 999;
|
|
209
|
+
group = { label, order, operators: [] };
|
|
210
|
+
groups.set(cat, group);
|
|
211
|
+
}
|
|
212
|
+
group.operators.push({
|
|
213
|
+
id: op.code,
|
|
214
|
+
label: op.name ?? op.code,
|
|
215
|
+
description: op.description ?? '',
|
|
216
|
+
icon: op.icon,
|
|
217
|
+
color: op.color,
|
|
218
|
+
category: cat,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result: OperatorGroup[] = [];
|
|
223
|
+
for (const [cat, group] of groups) {
|
|
224
|
+
result.push({ category: cat, label: group.label, order: group.order, operators: group.operators });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
result.sort((a, b) => a.order - b.order);
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}, [operators]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface TransformTypeButtonsProps {
|
|
234
|
+
onAdd: (type: TransformationType) => void;
|
|
235
|
+
operators: OperatorData | undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Number of placeholder cards shown while operators are loading. */
|
|
239
|
+
const LOADING_OPERATOR_COUNT = 10;
|
|
240
|
+
|
|
241
|
+
function TransformTypeButtons({ onAdd, operators }: TransformTypeButtonsProps) {
|
|
242
|
+
const groups = useTransformGroupsFromData(operators);
|
|
243
|
+
|
|
244
|
+
// Show loading skeleton while operators are loading
|
|
245
|
+
if (!operators) {
|
|
246
|
+
return (
|
|
247
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2" data-testid="datahub-transform-type-buttons">
|
|
248
|
+
{Array.from({ length: LOADING_OPERATOR_COUNT }, (_, i) => (
|
|
249
|
+
<div key={`loading-${i}`} className="h-auto py-2 px-2.5 flex items-start gap-2 rounded-md border border-muted animate-pulse">
|
|
250
|
+
<div className="w-5 h-5 bg-muted rounded mt-0.5" />
|
|
251
|
+
<div className="flex-1 min-w-0">
|
|
252
|
+
<div className="h-3 bg-muted rounded w-3/4 mb-1" />
|
|
253
|
+
<div className="h-2 bg-muted rounded w-full" />
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// No operators registered (shouldn't happen in practice)
|
|
262
|
+
if (groups.length === 0) {
|
|
263
|
+
return (
|
|
264
|
+
<div className="text-sm text-muted-foreground p-4 text-center" data-testid="datahub-transform-type-buttons">
|
|
265
|
+
No operator types available
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className="space-y-4" data-testid="datahub-transform-type-buttons">
|
|
272
|
+
{groups.map(group => {
|
|
273
|
+
const colors = getCategoryColor(group.category);
|
|
274
|
+
return (
|
|
275
|
+
<div key={group.category}>
|
|
276
|
+
<div className="flex items-center gap-2 mb-2">
|
|
277
|
+
<div className={`w-1.5 h-4 rounded-full ${colors.accent}`} />
|
|
278
|
+
<h4 className={`text-xs font-semibold uppercase tracking-wider ${colors.text}`}>
|
|
279
|
+
{group.label}
|
|
280
|
+
</h4>
|
|
281
|
+
<span className="text-[10px] text-muted-foreground">
|
|
282
|
+
({group.operators.length})
|
|
283
|
+
</span>
|
|
284
|
+
</div>
|
|
285
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
|
|
286
|
+
{group.operators.map(type => (
|
|
287
|
+
<TransformTypeButton
|
|
288
|
+
key={type.id}
|
|
289
|
+
type={type}
|
|
290
|
+
category={group.category}
|
|
291
|
+
onAdd={onAdd}
|
|
292
|
+
/>
|
|
293
|
+
))}
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
})}
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface TransformTypeButtonProps {
|
|
303
|
+
type: EnrichedTransformTypeOption;
|
|
304
|
+
category?: string;
|
|
305
|
+
onAdd: (type: TransformationType) => void;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const TransformTypeButton = memo(function TransformTypeButton({ type, category, onAdd }: TransformTypeButtonProps) {
|
|
309
|
+
const handleClick = useCallback(() => {
|
|
310
|
+
onAdd(type.id as TransformationType);
|
|
311
|
+
}, [type.id, onAdd]);
|
|
312
|
+
|
|
313
|
+
const IconComponent = resolveIconName(type.icon ?? undefined);
|
|
314
|
+
const colors = getCategoryColor(category ?? type.category ?? 'DATA');
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
className={`group relative h-auto py-2 px-2.5 flex items-start gap-2 text-left rounded-md border transition-all duration-150 ${colors.border} ${colors.bg} hover:shadow-sm hover:scale-[1.02] active:scale-[0.98]`}
|
|
320
|
+
onClick={handleClick}
|
|
321
|
+
title={type.description}
|
|
322
|
+
data-testid={`datahub-transform-add-${type.id}-button`}
|
|
323
|
+
>
|
|
324
|
+
{IconComponent && (
|
|
325
|
+
<div className={`shrink-0 mt-0.5 w-5 h-5 rounded flex items-center justify-center ${colors.badge}`}>
|
|
326
|
+
<IconComponent className="w-3 h-3" />
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
<div className="min-w-0 flex-1">
|
|
330
|
+
<span className="text-xs font-medium leading-tight block">{type.label}</span>
|
|
331
|
+
<span className="text-[10px] text-muted-foreground leading-tight line-clamp-2">{type.description}</span>
|
|
332
|
+
</div>
|
|
333
|
+
</button>
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
interface TransformPipelineCardProps {
|
|
338
|
+
transformations: ImportConfiguration['transformations'];
|
|
339
|
+
transformTypes: EnrichedTransformTypeOption[];
|
|
340
|
+
onRemove: (id: string) => void;
|
|
341
|
+
onSettings: (transform: WizardTransformationStep) => void;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function TransformPipelineCard({ transformations, transformTypes, onRemove, onSettings }: TransformPipelineCardProps) {
|
|
345
|
+
return (
|
|
346
|
+
<Card>
|
|
347
|
+
<CardHeader className="pb-2">
|
|
348
|
+
<div className="flex items-center gap-2">
|
|
349
|
+
<div className="w-6 h-6 rounded-md bg-primary/10 flex items-center justify-center">
|
|
350
|
+
<ChevronRight className="w-3.5 h-3.5 text-primary" />
|
|
351
|
+
</div>
|
|
352
|
+
<div>
|
|
353
|
+
<CardTitle className="text-sm">Transformation Pipeline</CardTitle>
|
|
354
|
+
<CardDescription className="text-xs">
|
|
355
|
+
{transformations.length} step{transformations.length !== 1 ? 's' : ''} applied in order from top to bottom
|
|
356
|
+
</CardDescription>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</CardHeader>
|
|
360
|
+
<CardContent className="space-y-1.5 pt-0">
|
|
361
|
+
{transformations.map((transform, index) => (
|
|
362
|
+
<TransformPipelineRow
|
|
363
|
+
key={transform.id}
|
|
364
|
+
transform={transform}
|
|
365
|
+
transformTypes={transformTypes}
|
|
366
|
+
index={index}
|
|
367
|
+
isLast={index === transformations.length - 1}
|
|
368
|
+
onRemove={onRemove}
|
|
369
|
+
onSettings={onSettings}
|
|
370
|
+
/>
|
|
371
|
+
))}
|
|
372
|
+
</CardContent>
|
|
373
|
+
</Card>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
interface TransformPipelineRowProps {
|
|
378
|
+
transform: ImportConfiguration['transformations'][number];
|
|
379
|
+
transformTypes: EnrichedTransformTypeOption[];
|
|
380
|
+
index: number;
|
|
381
|
+
isLast: boolean;
|
|
382
|
+
onRemove: (id: string) => void;
|
|
383
|
+
onSettings: (transform: WizardTransformationStep) => void;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const TransformPipelineRow = memo(function TransformPipelineRow({ transform, transformTypes, index, isLast, onRemove, onSettings }: TransformPipelineRowProps) {
|
|
387
|
+
|
|
388
|
+
const handleRemove = useCallback(() => {
|
|
389
|
+
onRemove(transform.id);
|
|
390
|
+
}, [transform.id, onRemove]);
|
|
391
|
+
|
|
392
|
+
const handleSettings = useCallback(() => {
|
|
393
|
+
onSettings(transform);
|
|
394
|
+
}, [transform, onSettings]);
|
|
395
|
+
|
|
396
|
+
const hasConfig = Object.keys(transform.config).length > 0;
|
|
397
|
+
const typeMeta = useMemo(
|
|
398
|
+
() => transformTypes.find(t => t.id === transform.type),
|
|
399
|
+
[transformTypes, transform.type],
|
|
400
|
+
);
|
|
401
|
+
const typeDescription = typeMeta?.description;
|
|
402
|
+
const category = typeMeta?.category ?? 'DATA';
|
|
403
|
+
const colors = getCategoryColor(category);
|
|
404
|
+
const IconComponent = resolveIconName(typeMeta?.icon ?? undefined);
|
|
405
|
+
const summary = hasConfig ? summarizeConfig(transform.type, transform.config) : '';
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<div className="relative">
|
|
409
|
+
<div className={`group flex items-center gap-2 p-2.5 border rounded-lg transition-all hover:shadow-sm ${hasConfig ? 'border-border' : 'border-dashed border-muted-foreground/30'}`}>
|
|
410
|
+
{/* Category color accent bar */}
|
|
411
|
+
<div className={`absolute left-0 top-2 bottom-2 w-1 rounded-full ${colors.accent}`} />
|
|
412
|
+
|
|
413
|
+
{/* Drag handle */}
|
|
414
|
+
<div className="flex flex-col items-center gap-0.5 shrink-0 ml-2 cursor-grab opacity-40 group-hover:opacity-70 transition-opacity">
|
|
415
|
+
<GripVertical className="w-3.5 h-3.5 text-muted-foreground" />
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
{/* Step number badge with operator icon */}
|
|
419
|
+
<div className={`w-7 h-7 rounded-md flex items-center justify-center shrink-0 ${colors.badge}`}>
|
|
420
|
+
{IconComponent
|
|
421
|
+
? <IconComponent className="w-3.5 h-3.5" />
|
|
422
|
+
: <span className="text-[10px] font-bold">{index + 1}</span>
|
|
423
|
+
}
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
{/* Content */}
|
|
427
|
+
<div className="flex-1 min-w-0">
|
|
428
|
+
<div className="flex items-center gap-1.5">
|
|
429
|
+
<span className="text-sm font-medium truncate">{typeMeta?.label ?? transform.type}</span>
|
|
430
|
+
{hasConfig && (
|
|
431
|
+
<Check className="w-3 h-3 text-green-600 shrink-0" />
|
|
432
|
+
)}
|
|
433
|
+
</div>
|
|
434
|
+
<div className="text-[11px] text-muted-foreground truncate">
|
|
435
|
+
{summary ? (
|
|
436
|
+
<span className="font-mono">{summary}</span>
|
|
437
|
+
) : (
|
|
438
|
+
typeDescription
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
{/* Actions */}
|
|
444
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
445
|
+
<Button
|
|
446
|
+
variant="outline"
|
|
447
|
+
size="sm"
|
|
448
|
+
className="h-7 text-xs gap-1"
|
|
449
|
+
onClick={handleSettings}
|
|
450
|
+
data-testid={`datahub-transform-settings-${transform.id}-button`}
|
|
451
|
+
aria-label="Configure transform"
|
|
452
|
+
>
|
|
453
|
+
<Settings className="w-3.5 h-3.5" />
|
|
454
|
+
{hasConfig ? 'Edit' : 'Configure'}
|
|
455
|
+
</Button>
|
|
456
|
+
<Button
|
|
457
|
+
variant="ghost"
|
|
458
|
+
size="icon"
|
|
459
|
+
className="h-7 w-7 opacity-50 hover:opacity-100"
|
|
460
|
+
onClick={handleRemove}
|
|
461
|
+
aria-label={`Remove ${transform.type} transform`}
|
|
462
|
+
data-testid={`datahub-transform-remove-${transform.id}-button`}
|
|
463
|
+
>
|
|
464
|
+
<Trash2 className="w-3.5 h-3.5 text-destructive" />
|
|
465
|
+
</Button>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
{/* Connector line between steps */}
|
|
470
|
+
{!isLast && (
|
|
471
|
+
<div className="flex justify-center py-0.5">
|
|
472
|
+
<ArrowRight className="w-3 h-3 text-muted-foreground/40 rotate-90" />
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Produce a human-readable summary of an operator's configuration.
|
|
481
|
+
* Returns short strings like "field: price, format: USD" or "from: name -> to: productName".
|
|
482
|
+
*/
|
|
483
|
+
function summarizeConfig(type: string, config: Record<string, unknown>): string {
|
|
484
|
+
const entries = Object.entries(config).filter(([, v]) => v != null && v !== '');
|
|
485
|
+
if (entries.length === 0) return '';
|
|
486
|
+
|
|
487
|
+
// Type-specific summaries for common operators
|
|
488
|
+
if (type === 'rename' && config.from && config.to) {
|
|
489
|
+
return `${config.from} \u2192 ${config.to}`;
|
|
490
|
+
}
|
|
491
|
+
if (type === 'copy' && config.from && config.to) {
|
|
492
|
+
return `${config.from} \u2192 ${config.to}`;
|
|
493
|
+
}
|
|
494
|
+
if (type === 'set' && config.field) {
|
|
495
|
+
const val = config.value != null ? String(config.value) : '(empty)';
|
|
496
|
+
return `${config.field} = ${val.length > 20 ? val.slice(0, 20) + '\u2026' : val}`;
|
|
497
|
+
}
|
|
498
|
+
if (type === 'remove' && (config.fields || config.field)) {
|
|
499
|
+
const fields = config.fields ?? config.field;
|
|
500
|
+
return `remove: ${Array.isArray(fields) ? fields.join(', ') : String(fields)}`;
|
|
501
|
+
}
|
|
502
|
+
if (type === 'pick' && config.fields) {
|
|
503
|
+
const fields = config.fields;
|
|
504
|
+
return `keep: ${Array.isArray(fields) ? fields.join(', ') : String(fields)}`;
|
|
505
|
+
}
|
|
506
|
+
if (type === 'template' && config.template) {
|
|
507
|
+
const tmpl = String(config.template);
|
|
508
|
+
return tmpl.length > 30 ? tmpl.slice(0, 30) + '\u2026' : tmpl;
|
|
509
|
+
}
|
|
510
|
+
if ((type === 'filter' || type === 'when') && config.action) {
|
|
511
|
+
const ruleCount = Array.isArray(config.conditions) ? config.conditions.length : 0;
|
|
512
|
+
return `${config.action} (${ruleCount} rule${ruleCount !== 1 ? 's' : ''})`;
|
|
513
|
+
}
|
|
514
|
+
if (type === 'lookup' && config.field) {
|
|
515
|
+
return `lookup: ${config.field}`;
|
|
516
|
+
}
|
|
517
|
+
if (type === 'dateParse' && config.field) {
|
|
518
|
+
return `${config.field}${config.format ? ` (${config.format})` : ''}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Generic summary: show key=value pairs
|
|
522
|
+
if (entries.length <= 2) {
|
|
523
|
+
return entries.map(([k, v]) => {
|
|
524
|
+
const s = String(v);
|
|
525
|
+
return `${k}: ${s.length > 15 ? s.slice(0, 15) + '\u2026' : s}`;
|
|
526
|
+
}).join(', ');
|
|
527
|
+
}
|
|
528
|
+
return `${entries.length} fields configured`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// --- Transform Config Dialog ---
|
|
532
|
+
|
|
533
|
+
interface TransformConfigDialogProps {
|
|
534
|
+
transform: WizardTransformationStep;
|
|
535
|
+
transformTypes: EnrichedTransformTypeOption[];
|
|
536
|
+
operators: OperatorData | undefined;
|
|
537
|
+
onSave: (config: Record<string, unknown>) => void;
|
|
538
|
+
onClose: () => void;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function TransformConfigDialog({ transform, transformTypes, operators, onSave, onClose }: TransformConfigDialogProps) {
|
|
542
|
+
const [localConfig, setLocalConfig] = useState<Record<string, unknown>>(transform.config);
|
|
543
|
+
|
|
544
|
+
const handleConfigUpdate = useCallback((updated: Record<string, unknown>) => {
|
|
545
|
+
setLocalConfig(updated);
|
|
546
|
+
}, []);
|
|
547
|
+
|
|
548
|
+
const handleSave = useCallback(() => {
|
|
549
|
+
// Remove empty/undefined values and internal helper keys (prefixed with _)
|
|
550
|
+
const cleaned: Record<string, unknown> = {};
|
|
551
|
+
for (const [k, v] of Object.entries(localConfig)) {
|
|
552
|
+
if (k.startsWith('_')) continue;
|
|
553
|
+
if (v !== '' && v !== undefined && v !== null) {
|
|
554
|
+
cleaned[k] = v;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
onSave(cleaned);
|
|
558
|
+
}, [localConfig, onSave]);
|
|
559
|
+
|
|
560
|
+
const typeMeta = useMemo(
|
|
561
|
+
() => transformTypes.find(t => t.id === transform.type),
|
|
562
|
+
[transformTypes, transform.type],
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const category = typeMeta?.category ?? 'DATA';
|
|
566
|
+
const colors = getCategoryColor(category);
|
|
567
|
+
const IconComponent = resolveIconName(typeMeta?.icon ?? undefined);
|
|
568
|
+
const fieldCount = operators?.find(op => op.code === transform.type)?.schema?.fields?.length ?? 0;
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<Dialog open onOpenChange={(open) => { if (!open) onClose(); }}>
|
|
572
|
+
<DialogContent className="max-w-2xl">
|
|
573
|
+
<DialogHeader className="pb-3">
|
|
574
|
+
<div className="flex items-center gap-3">
|
|
575
|
+
<div className={`w-9 h-9 rounded-lg flex items-center justify-center ${colors.badge}`}>
|
|
576
|
+
{IconComponent
|
|
577
|
+
? <IconComponent className="w-4.5 h-4.5" />
|
|
578
|
+
: <Settings className="w-4.5 h-4.5" />
|
|
579
|
+
}
|
|
580
|
+
</div>
|
|
581
|
+
<div>
|
|
582
|
+
<DialogTitle className="text-base">
|
|
583
|
+
Configure {typeMeta?.label ?? transform.type}
|
|
584
|
+
</DialogTitle>
|
|
585
|
+
<DialogDescription className="mt-0.5">
|
|
586
|
+
{typeMeta?.description}
|
|
587
|
+
</DialogDescription>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
{fieldCount > 0 && (
|
|
591
|
+
<div className="mt-3">
|
|
592
|
+
<Separator />
|
|
593
|
+
<p className="text-[11px] text-muted-foreground mt-2">
|
|
594
|
+
{fieldCount} configuration field{fieldCount !== 1 ? 's' : ''} available
|
|
595
|
+
</p>
|
|
596
|
+
</div>
|
|
597
|
+
)}
|
|
598
|
+
</DialogHeader>
|
|
599
|
+
<div className="space-y-4 py-1">
|
|
600
|
+
<TransformConfigFields
|
|
601
|
+
type={transform.type}
|
|
602
|
+
config={localConfig}
|
|
603
|
+
onUpdate={handleConfigUpdate}
|
|
604
|
+
operators={operators}
|
|
605
|
+
/>
|
|
606
|
+
</div>
|
|
607
|
+
<Separator />
|
|
608
|
+
<DialogFooter className="pt-2">
|
|
609
|
+
<Button variant="outline" onClick={onClose}>Cancel</Button>
|
|
610
|
+
<Button onClick={handleSave}>Save Configuration</Button>
|
|
611
|
+
</DialogFooter>
|
|
612
|
+
</DialogContent>
|
|
613
|
+
</Dialog>
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
interface TransformConfigFieldsProps {
|
|
618
|
+
type: string;
|
|
619
|
+
config: Record<string, unknown>;
|
|
620
|
+
onUpdate: (config: Record<string, unknown>) => void;
|
|
621
|
+
operators?: OperatorData;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function TransformConfigFields({ type, config, onUpdate, operators }: TransformConfigFieldsProps) {
|
|
625
|
+
// Find the operator's schema from backend data
|
|
626
|
+
const operator = operators?.find(op => op.code === type);
|
|
627
|
+
const schema = operator?.schema;
|
|
628
|
+
|
|
629
|
+
// Operators with custom interactive editors keep their custom components
|
|
630
|
+
if (type === 'map') {
|
|
631
|
+
return <AdvancedMapEditor config={config} onChange={onUpdate} />;
|
|
632
|
+
}
|
|
633
|
+
if (type === 'template') {
|
|
634
|
+
return <AdvancedTemplateEditor config={config} onChange={onUpdate} />;
|
|
635
|
+
}
|
|
636
|
+
if (type === 'filter' || type === 'when') {
|
|
637
|
+
return <AdvancedWhenEditor config={config} onChange={onUpdate} />;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// For all other operators: render from schema
|
|
641
|
+
if (schema?.fields?.length) {
|
|
642
|
+
return (
|
|
643
|
+
<SchemaFormRenderer
|
|
644
|
+
schema={{ fields: schema.fields as AdapterSchemaField[] }}
|
|
645
|
+
values={config}
|
|
646
|
+
onChange={onUpdate}
|
|
647
|
+
compact
|
|
648
|
+
/>
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Fallback: JSON config editor
|
|
653
|
+
return (
|
|
654
|
+
<div className="space-y-2">
|
|
655
|
+
<Label className="text-sm font-medium">Configuration (JSON)</Label>
|
|
656
|
+
<Textarea
|
|
657
|
+
value={JSON.stringify(config, null, 2)}
|
|
658
|
+
onChange={(e) => {
|
|
659
|
+
try { onUpdate(JSON.parse(e.target.value)); } catch { /* ignore parse errors while typing */ }
|
|
660
|
+
}}
|
|
661
|
+
className="font-mono text-xs"
|
|
662
|
+
rows={4}
|
|
663
|
+
/>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
}
|