@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.
- package/CHANGELOG.md +10 -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 +4 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AlertCircle } from 'lucide-react';
|
|
3
|
+
import type { FieldErrorProps } from '../../types';
|
|
4
|
+
|
|
5
|
+
export function FieldError({ error, touched = true, showImmediately = false, className = '' }: FieldErrorProps) {
|
|
6
|
+
if (!error || (!touched && !showImmediately)) return null;
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={`flex items-center gap-1.5 mt-1.5 text-sm text-destructive animate-in fade-in slide-in-from-top-1 duration-200 ${className}`}
|
|
11
|
+
role="alert"
|
|
12
|
+
>
|
|
13
|
+
<AlertCircle className="w-3.5 h-3.5 flex-shrink-0" />
|
|
14
|
+
<span>{error}</span>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { HeadersEditor } from './HeadersEditor';
|
|
2
|
+
|
|
3
|
+
export { FieldError } from './ValidationFeedback';
|
|
4
|
+
|
|
5
|
+
export { ConnectionConfigEditor, useConnectionTypeOptions, createDefaultConnectionConfig, normalizeConnectionConfig } from './ConnectionConfigEditor';
|
|
6
|
+
export type { ConnectionConfigEditorProps } from './ConnectionConfigEditor';
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
FieldErrorProps,
|
|
10
|
+
} from '../../types';
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useCallback, useMemo, useState, memo } from 'react';
|
|
3
|
+
import { Button } from '@vendure/dashboard';
|
|
4
|
+
import {
|
|
5
|
+
Play,
|
|
6
|
+
Settings,
|
|
7
|
+
Clock,
|
|
8
|
+
AlertTriangle,
|
|
9
|
+
Zap,
|
|
10
|
+
Bell,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import { TriggersPanel } from '../shared/triggers-panel';
|
|
13
|
+
import { StepConfigPanel } from '../shared/step-config';
|
|
14
|
+
import { PipelineSettingsPanel, StepListItem } from './shared';
|
|
15
|
+
import {
|
|
16
|
+
MOVE_DIRECTION,
|
|
17
|
+
STEP_TYPE,
|
|
18
|
+
getStepTypeIcon,
|
|
19
|
+
PIPELINE_EDITOR_PANEL,
|
|
20
|
+
DEFAULT_STEP_CONFIGS,
|
|
21
|
+
} from '../../constants';
|
|
22
|
+
import { useStepConfigs, useTriggerTypes } from '../../hooks';
|
|
23
|
+
import type { MoveDirection, PipelineEditorPanel } from '../../constants';
|
|
24
|
+
import { useAdapterCatalog } from '../../hooks';
|
|
25
|
+
import type {
|
|
26
|
+
StepType,
|
|
27
|
+
PipelineStepDefinition,
|
|
28
|
+
PipelineTrigger,
|
|
29
|
+
PipelineEditorProps,
|
|
30
|
+
JsonObject,
|
|
31
|
+
} from '../../types';
|
|
32
|
+
import { getCombinedTriggers, updateDefinitionWithTriggers } from '../../utils';
|
|
33
|
+
|
|
34
|
+
const ADDABLE_STEP_TYPES = Object.keys(DEFAULT_STEP_CONFIGS).filter(
|
|
35
|
+
t => t !== STEP_TYPE.ROUTE && t !== STEP_TYPE.GATE
|
|
36
|
+
) as StepType[];
|
|
37
|
+
|
|
38
|
+
export function PipelineEditor({ definition, onChange, issues = [] }: PipelineEditorProps) {
|
|
39
|
+
const [selectedStepIndex, setSelectedStepIndex] = useState<number | null>(null);
|
|
40
|
+
const [activePanel, setActivePanel] = useState<PipelineEditorPanel>(PIPELINE_EDITOR_PANEL.STEPS);
|
|
41
|
+
|
|
42
|
+
const { adapters, connectionCodes, secretOptions } = useAdapterCatalog();
|
|
43
|
+
const { configList: triggerTypeList, isLoading: isTriggerTypesLoading } = useTriggerTypes();
|
|
44
|
+
|
|
45
|
+
const steps = definition.steps ?? [];
|
|
46
|
+
const selectedStep = selectedStepIndex !== null ? steps[selectedStepIndex] : null;
|
|
47
|
+
|
|
48
|
+
const addStep = useCallback((type: StepType) => {
|
|
49
|
+
const stepCfg = DEFAULT_STEP_CONFIGS[type];
|
|
50
|
+
const defaultConfig: JsonObject = stepCfg?.defaultConfig
|
|
51
|
+
? { ...stepCfg.defaultConfig }
|
|
52
|
+
: {};
|
|
53
|
+
|
|
54
|
+
const newStep: PipelineStepDefinition = {
|
|
55
|
+
key: `${type.toLowerCase()}-${Date.now()}`,
|
|
56
|
+
type,
|
|
57
|
+
config: defaultConfig,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const existingEdges = definition.edges ?? [];
|
|
61
|
+
const currentSteps = definition.steps ?? [];
|
|
62
|
+
|
|
63
|
+
let newEdges = existingEdges;
|
|
64
|
+
|
|
65
|
+
if (type === STEP_TYPE.TRIGGER) {
|
|
66
|
+
// TRIGGER steps connect TO the first non-trigger step (parallel entry points)
|
|
67
|
+
const firstExecutionStep = currentSteps.find(s => s.type !== STEP_TYPE.TRIGGER);
|
|
68
|
+
if (firstExecutionStep) {
|
|
69
|
+
newEdges = [
|
|
70
|
+
...existingEdges,
|
|
71
|
+
{ from: newStep.key, to: firstExecutionStep.key },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
} else if (existingEdges.length > 0 && currentSteps.length > 0) {
|
|
75
|
+
// Non-trigger steps chain to the last step
|
|
76
|
+
const lastStep = currentSteps[currentSteps.length - 1];
|
|
77
|
+
if (lastStep) {
|
|
78
|
+
newEdges = [
|
|
79
|
+
...existingEdges,
|
|
80
|
+
{ from: lastStep.key, to: newStep.key },
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onChange({
|
|
86
|
+
...definition,
|
|
87
|
+
steps: [...currentSteps, newStep],
|
|
88
|
+
edges: newEdges,
|
|
89
|
+
});
|
|
90
|
+
setSelectedStepIndex(currentSteps.length);
|
|
91
|
+
}, [definition, onChange]);
|
|
92
|
+
|
|
93
|
+
const updateStep = useCallback((index: number, updatedStep: PipelineStepDefinition) => {
|
|
94
|
+
const currentSteps = definition.steps ?? [];
|
|
95
|
+
const newSteps = [...currentSteps];
|
|
96
|
+
newSteps[index] = updatedStep;
|
|
97
|
+
onChange({ ...definition, steps: newSteps });
|
|
98
|
+
}, [definition, onChange]);
|
|
99
|
+
|
|
100
|
+
const removeStep = useCallback((index: number) => {
|
|
101
|
+
const currentSteps = definition.steps ?? [];
|
|
102
|
+
const stepToRemove = currentSteps[index];
|
|
103
|
+
const stepKey = stepToRemove?.key;
|
|
104
|
+
const newSteps = currentSteps.filter((_, i) => i !== index);
|
|
105
|
+
|
|
106
|
+
const existingEdges = definition.edges ?? [];
|
|
107
|
+
const incomingEdges = existingEdges.filter(edge => edge.to === stepKey);
|
|
108
|
+
const outgoingEdges = existingEdges.filter(edge => edge.from === stepKey);
|
|
109
|
+
|
|
110
|
+
let newEdges = existingEdges.filter(
|
|
111
|
+
edge => edge.from !== stepKey && edge.to !== stepKey
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (incomingEdges.length > 0 && outgoingEdges.length > 0) {
|
|
115
|
+
const reconnectionEdges: Array<{ from: string; to: string; branch?: string }> = [];
|
|
116
|
+
for (const inEdge of incomingEdges) {
|
|
117
|
+
for (const outEdge of outgoingEdges) {
|
|
118
|
+
const edgeExists = newEdges.some(
|
|
119
|
+
e => e.from === inEdge.from && e.to === outEdge.to
|
|
120
|
+
);
|
|
121
|
+
if (!edgeExists) {
|
|
122
|
+
reconnectionEdges.push({
|
|
123
|
+
from: inEdge.from,
|
|
124
|
+
to: outEdge.to,
|
|
125
|
+
...(inEdge.branch ? { branch: inEdge.branch } : {}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (reconnectionEdges.length > 0) {
|
|
131
|
+
newEdges = [...newEdges, ...reconnectionEdges];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
onChange({ ...definition, steps: newSteps, edges: newEdges });
|
|
136
|
+
setSelectedStepIndex(null);
|
|
137
|
+
}, [definition, onChange]);
|
|
138
|
+
|
|
139
|
+
const moveStep = useCallback((index: number, direction: MoveDirection) => {
|
|
140
|
+
const currentSteps = definition.steps ?? [];
|
|
141
|
+
if (direction === MOVE_DIRECTION.UP && index === 0) return;
|
|
142
|
+
if (direction === MOVE_DIRECTION.DOWN && index === currentSteps.length - 1) return;
|
|
143
|
+
|
|
144
|
+
const newSteps = [...currentSteps];
|
|
145
|
+
const targetIndex = direction === MOVE_DIRECTION.UP ? index - 1 : index + 1;
|
|
146
|
+
[newSteps[index], newSteps[targetIndex]] = [newSteps[targetIndex], newSteps[index]];
|
|
147
|
+
onChange({ ...definition, steps: newSteps });
|
|
148
|
+
setSelectedStepIndex(targetIndex);
|
|
149
|
+
}, [definition, onChange]);
|
|
150
|
+
|
|
151
|
+
// Sync triggers between steps and triggers array
|
|
152
|
+
// Both visual trigger nodes (in steps) and Triggers tab edit the same data
|
|
153
|
+
const updateTriggers = useCallback((triggers: PipelineTrigger[]) => {
|
|
154
|
+
onChange(updateDefinitionWithTriggers(definition, triggers));
|
|
155
|
+
}, [definition, onChange]);
|
|
156
|
+
|
|
157
|
+
// Get combined triggers from both steps and triggers array
|
|
158
|
+
const combinedTriggers = useMemo(() => getCombinedTriggers(definition), [definition]);
|
|
159
|
+
|
|
160
|
+
// Panel switch handlers
|
|
161
|
+
const handleSwitchToSteps = useCallback(() => setActivePanel(PIPELINE_EDITOR_PANEL.STEPS), []);
|
|
162
|
+
const handleSwitchToTriggers = useCallback(() => setActivePanel(PIPELINE_EDITOR_PANEL.TRIGGERS), []);
|
|
163
|
+
const handleSwitchToSettings = useCallback(() => setActivePanel(PIPELINE_EDITOR_PANEL.SETTINGS), []);
|
|
164
|
+
|
|
165
|
+
// Settings change handler
|
|
166
|
+
const handleContextChange = useCallback((context: JsonObject) => {
|
|
167
|
+
onChange({ ...definition, context });
|
|
168
|
+
}, [definition, onChange]);
|
|
169
|
+
|
|
170
|
+
// Step selection handler factory - memoized per step index
|
|
171
|
+
const handleStepClick = useCallback((index: number) => {
|
|
172
|
+
setSelectedStepIndex(index);
|
|
173
|
+
}, []);
|
|
174
|
+
|
|
175
|
+
// Step action handler factories
|
|
176
|
+
const handleMoveStepUp = useCallback((index: number) => {
|
|
177
|
+
moveStep(index, MOVE_DIRECTION.UP);
|
|
178
|
+
}, [moveStep]);
|
|
179
|
+
|
|
180
|
+
const handleMoveStepDown = useCallback((index: number) => {
|
|
181
|
+
moveStep(index, MOVE_DIRECTION.DOWN);
|
|
182
|
+
}, [moveStep]);
|
|
183
|
+
|
|
184
|
+
const handleRemoveStep = useCallback((index: number) => {
|
|
185
|
+
removeStep(index);
|
|
186
|
+
}, [removeStep]);
|
|
187
|
+
|
|
188
|
+
// Selected step update handler
|
|
189
|
+
const handleSelectedStepChange = useCallback((updated: { key: string; type: string; config: JsonObject; adapterCode?: string }) => {
|
|
190
|
+
if (selectedStepIndex === null || !selectedStep) return;
|
|
191
|
+
updateStep(selectedStepIndex, {
|
|
192
|
+
...selectedStep,
|
|
193
|
+
key: updated.key,
|
|
194
|
+
type: updated.type as StepType,
|
|
195
|
+
config: { ...updated.config, adapterCode: updated.adapterCode },
|
|
196
|
+
});
|
|
197
|
+
}, [selectedStepIndex, selectedStep, updateStep]);
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className="flex h-full border rounded-lg overflow-hidden bg-background">
|
|
201
|
+
<div className="w-80 border-r flex flex-col">
|
|
202
|
+
<div className="border-b">
|
|
203
|
+
<div className="flex" role="tablist" aria-label="Pipeline editor panels">
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
role="tab"
|
|
207
|
+
aria-selected={activePanel === PIPELINE_EDITOR_PANEL.STEPS}
|
|
208
|
+
aria-controls="panel-steps"
|
|
209
|
+
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
|
|
210
|
+
activePanel === PIPELINE_EDITOR_PANEL.STEPS
|
|
211
|
+
? 'bg-primary/10 text-primary border-b-2 border-primary'
|
|
212
|
+
: 'text-muted-foreground hover:bg-muted'
|
|
213
|
+
}`}
|
|
214
|
+
onClick={handleSwitchToSteps}
|
|
215
|
+
data-testid="datahub-editor-tab-steps"
|
|
216
|
+
>
|
|
217
|
+
<Play className="h-3 w-3 inline mr-1" />
|
|
218
|
+
Steps
|
|
219
|
+
</button>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
role="tab"
|
|
223
|
+
aria-selected={activePanel === PIPELINE_EDITOR_PANEL.TRIGGERS}
|
|
224
|
+
aria-controls="panel-triggers"
|
|
225
|
+
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
|
|
226
|
+
activePanel === PIPELINE_EDITOR_PANEL.TRIGGERS
|
|
227
|
+
? 'bg-primary/10 text-primary border-b-2 border-primary'
|
|
228
|
+
: 'text-muted-foreground hover:bg-muted'
|
|
229
|
+
}`}
|
|
230
|
+
onClick={handleSwitchToTriggers}
|
|
231
|
+
data-testid="datahub-editor-tab-triggers"
|
|
232
|
+
>
|
|
233
|
+
<Bell className="h-3 w-3 inline mr-1" />
|
|
234
|
+
Triggers
|
|
235
|
+
</button>
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
role="tab"
|
|
239
|
+
aria-selected={activePanel === PIPELINE_EDITOR_PANEL.SETTINGS}
|
|
240
|
+
aria-controls="panel-settings"
|
|
241
|
+
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
|
|
242
|
+
activePanel === PIPELINE_EDITOR_PANEL.SETTINGS
|
|
243
|
+
? 'bg-primary/10 text-primary border-b-2 border-primary'
|
|
244
|
+
: 'text-muted-foreground hover:bg-muted'
|
|
245
|
+
}`}
|
|
246
|
+
onClick={handleSwitchToSettings}
|
|
247
|
+
data-testid="datahub-editor-tab-settings"
|
|
248
|
+
>
|
|
249
|
+
<Settings className="h-3 w-3 inline mr-1" />
|
|
250
|
+
Settings
|
|
251
|
+
</button>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{activePanel === PIPELINE_EDITOR_PANEL.STEPS && (
|
|
256
|
+
<>
|
|
257
|
+
<div className="p-3 border-b bg-muted/50">
|
|
258
|
+
<h3 className="font-semibold text-sm">Pipeline Steps</h3>
|
|
259
|
+
<p className="text-xs text-muted-foreground">Click to configure each step</p>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex-1 overflow-auto p-2 space-y-1">
|
|
262
|
+
{steps.map((step, index) => (
|
|
263
|
+
<StepListItemWrapper
|
|
264
|
+
key={step.key}
|
|
265
|
+
step={step}
|
|
266
|
+
index={index}
|
|
267
|
+
edges={definition.edges ?? []}
|
|
268
|
+
selectedStepIndex={selectedStepIndex}
|
|
269
|
+
stepsLength={steps.length}
|
|
270
|
+
issues={issues}
|
|
271
|
+
onStepClick={handleStepClick}
|
|
272
|
+
onMoveUp={handleMoveStepUp}
|
|
273
|
+
onMoveDown={handleMoveStepDown}
|
|
274
|
+
onRemove={handleRemoveStep}
|
|
275
|
+
/>
|
|
276
|
+
))}
|
|
277
|
+
{steps.length === 0 && (
|
|
278
|
+
<div className="p-4 text-center text-muted-foreground text-sm">
|
|
279
|
+
No steps yet. Add a step to get started.
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
<div className="p-3 border-t bg-muted/50">
|
|
284
|
+
<p className="text-xs text-muted-foreground mb-2">Add Step:</p>
|
|
285
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-1" data-testid="datahub-editor-add-step-buttons">
|
|
286
|
+
{ADDABLE_STEP_TYPES.map((type) => (
|
|
287
|
+
<AddStepButton
|
|
288
|
+
key={type}
|
|
289
|
+
type={type}
|
|
290
|
+
onAddStep={addStep}
|
|
291
|
+
/>
|
|
292
|
+
))}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{activePanel === PIPELINE_EDITOR_PANEL.TRIGGERS && (
|
|
299
|
+
<TriggersPanel
|
|
300
|
+
triggers={combinedTriggers}
|
|
301
|
+
onChange={updateTriggers}
|
|
302
|
+
variant="compact"
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{activePanel === PIPELINE_EDITOR_PANEL.SETTINGS && (
|
|
307
|
+
<PipelineSettingsPanel
|
|
308
|
+
context={definition.context ?? {}}
|
|
309
|
+
onChange={handleContextChange}
|
|
310
|
+
/>
|
|
311
|
+
)}
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div className="flex-1 overflow-auto">
|
|
315
|
+
{activePanel === PIPELINE_EDITOR_PANEL.STEPS && selectedStep ? (
|
|
316
|
+
<div className="p-4">
|
|
317
|
+
<StepConfigPanel
|
|
318
|
+
data={{
|
|
319
|
+
key: selectedStep.key,
|
|
320
|
+
type: selectedStep.type,
|
|
321
|
+
config: selectedStep.config ?? {},
|
|
322
|
+
adapterCode: selectedStep.config?.adapterCode as string | undefined,
|
|
323
|
+
}}
|
|
324
|
+
onChange={handleSelectedStepChange}
|
|
325
|
+
catalog={adapters}
|
|
326
|
+
connectionCodes={connectionCodes}
|
|
327
|
+
secretOptions={secretOptions}
|
|
328
|
+
variant="inline"
|
|
329
|
+
showHeader={true}
|
|
330
|
+
showDeleteButton={false}
|
|
331
|
+
showKeyInput={true}
|
|
332
|
+
showCheatSheet={true}
|
|
333
|
+
showStepTester={true}
|
|
334
|
+
showAdvancedEditors={true}
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
) : activePanel === PIPELINE_EDITOR_PANEL.STEPS ? (
|
|
338
|
+
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
339
|
+
<div className="text-center">
|
|
340
|
+
<p className="text-sm">Select a step to configure</p>
|
|
341
|
+
<p className="text-xs mt-1">or add a new step from the left panel</p>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
) : activePanel === PIPELINE_EDITOR_PANEL.TRIGGERS ? (
|
|
345
|
+
<div className="p-6">
|
|
346
|
+
<h3 className="font-semibold mb-4">Trigger Configuration</h3>
|
|
347
|
+
<p className="text-sm text-muted-foreground">
|
|
348
|
+
Configure how and when this pipeline runs. You can have multiple triggers -
|
|
349
|
+
for example, run on a schedule AND allow manual triggering.
|
|
350
|
+
</p>
|
|
351
|
+
<div className="mt-4 p-4 bg-muted/50 rounded-lg">
|
|
352
|
+
<h4 className="text-sm font-medium mb-2">Trigger Types:</h4>
|
|
353
|
+
{isTriggerTypesLoading || triggerTypeList.length === 0 ? (
|
|
354
|
+
<ul className="text-sm text-muted-foreground space-y-1">
|
|
355
|
+
<li><strong>Manual</strong> - Run from dashboard or API</li>
|
|
356
|
+
<li><strong>Schedule</strong> - Run on cron schedule</li>
|
|
357
|
+
<li><strong>Webhook</strong> - Run when webhook is called</li>
|
|
358
|
+
<li><strong>Event</strong> - Run when Vendure event fires</li>
|
|
359
|
+
<li><strong>File Watch</strong> - Run when files appear</li>
|
|
360
|
+
</ul>
|
|
361
|
+
) : (
|
|
362
|
+
<ul className="text-sm text-muted-foreground space-y-1">
|
|
363
|
+
{triggerTypeList.map(trigger => (
|
|
364
|
+
<li key={trigger.type}>
|
|
365
|
+
<strong>{trigger.label}</strong> - {trigger.description}
|
|
366
|
+
</li>
|
|
367
|
+
))}
|
|
368
|
+
</ul>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
) : (
|
|
373
|
+
<div className="p-6">
|
|
374
|
+
<h3 className="font-semibold mb-4">Pipeline Settings</h3>
|
|
375
|
+
<p className="text-sm text-muted-foreground">
|
|
376
|
+
Configure execution behavior including error handling, checkpointing for
|
|
377
|
+
resumable runs, and throughput controls.
|
|
378
|
+
</p>
|
|
379
|
+
<div className="mt-4 space-y-4">
|
|
380
|
+
<div className="p-4 bg-muted/50 rounded-lg">
|
|
381
|
+
<h4 className="text-sm font-medium mb-2 flex items-center gap-2">
|
|
382
|
+
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
|
383
|
+
Error Handling
|
|
384
|
+
</h4>
|
|
385
|
+
<p className="text-xs text-muted-foreground">
|
|
386
|
+
Configure retries, backoff, and dead letter queue for failed records.
|
|
387
|
+
</p>
|
|
388
|
+
</div>
|
|
389
|
+
<div className="p-4 bg-muted/50 rounded-lg">
|
|
390
|
+
<h4 className="text-sm font-medium mb-2 flex items-center gap-2">
|
|
391
|
+
<Clock className="h-4 w-4 text-blue-500" />
|
|
392
|
+
Checkpointing
|
|
393
|
+
</h4>
|
|
394
|
+
<p className="text-xs text-muted-foreground">
|
|
395
|
+
Enable resumable execution with periodic checkpoints.
|
|
396
|
+
</p>
|
|
397
|
+
</div>
|
|
398
|
+
<div className="p-4 bg-muted/50 rounded-lg">
|
|
399
|
+
<h4 className="text-sm font-medium mb-2 flex items-center gap-2">
|
|
400
|
+
<Zap className="h-4 w-4 text-green-500" />
|
|
401
|
+
Throughput
|
|
402
|
+
</h4>
|
|
403
|
+
<p className="text-xs text-muted-foreground">
|
|
404
|
+
Control batch size, concurrency, and rate limiting.
|
|
405
|
+
</p>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
)}
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
interface StepListItemWrapperProps {
|
|
416
|
+
step: PipelineStepDefinition;
|
|
417
|
+
index: number;
|
|
418
|
+
edges: Array<{ from: string; to: string }>;
|
|
419
|
+
selectedStepIndex: number | null;
|
|
420
|
+
stepsLength: number;
|
|
421
|
+
issues: Array<{ stepKey?: string }>;
|
|
422
|
+
onStepClick: (index: number) => void;
|
|
423
|
+
onMoveUp: (index: number) => void;
|
|
424
|
+
onMoveDown: (index: number) => void;
|
|
425
|
+
onRemove: (index: number) => void;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const StepListItemWrapper = memo(function StepListItemWrapper({
|
|
429
|
+
step,
|
|
430
|
+
index,
|
|
431
|
+
edges,
|
|
432
|
+
selectedStepIndex,
|
|
433
|
+
stepsLength,
|
|
434
|
+
issues,
|
|
435
|
+
onStepClick,
|
|
436
|
+
onMoveUp,
|
|
437
|
+
onMoveDown,
|
|
438
|
+
onRemove,
|
|
439
|
+
}: StepListItemWrapperProps) {
|
|
440
|
+
const connectionCount = edges.filter(
|
|
441
|
+
e => e.from === step.key || e.to === step.key
|
|
442
|
+
).length;
|
|
443
|
+
|
|
444
|
+
const handleClick = useCallback(() => {
|
|
445
|
+
onStepClick(index);
|
|
446
|
+
}, [index, onStepClick]);
|
|
447
|
+
|
|
448
|
+
const handleMoveUp = useCallback(() => {
|
|
449
|
+
onMoveUp(index);
|
|
450
|
+
}, [index, onMoveUp]);
|
|
451
|
+
|
|
452
|
+
const handleMoveDown = useCallback(() => {
|
|
453
|
+
onMoveDown(index);
|
|
454
|
+
}, [index, onMoveDown]);
|
|
455
|
+
|
|
456
|
+
const handleRemove = useCallback(() => {
|
|
457
|
+
onRemove(index);
|
|
458
|
+
}, [index, onRemove]);
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<StepListItem
|
|
462
|
+
step={step}
|
|
463
|
+
index={index}
|
|
464
|
+
isSelected={selectedStepIndex === index}
|
|
465
|
+
onClick={handleClick}
|
|
466
|
+
onMoveUp={handleMoveUp}
|
|
467
|
+
onMoveDown={handleMoveDown}
|
|
468
|
+
onRemove={handleRemove}
|
|
469
|
+
isFirst={index === 0}
|
|
470
|
+
isLast={index === stepsLength - 1}
|
|
471
|
+
issueCount={issues.filter(i => i.stepKey === step.key).length}
|
|
472
|
+
connectionCount={connectionCount}
|
|
473
|
+
/>
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
interface AddStepButtonProps {
|
|
478
|
+
type: StepType;
|
|
479
|
+
onAddStep: (type: StepType) => void;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const AddStepButton = memo(function AddStepButton({ type, onAddStep }: AddStepButtonProps) {
|
|
483
|
+
const { getStepConfig } = useStepConfigs();
|
|
484
|
+
const config = getStepConfig(type);
|
|
485
|
+
const Icon = getStepTypeIcon(type) ?? Play;
|
|
486
|
+
|
|
487
|
+
const handleClick = useCallback(() => {
|
|
488
|
+
onAddStep(type);
|
|
489
|
+
}, [type, onAddStep]);
|
|
490
|
+
|
|
491
|
+
return (
|
|
492
|
+
<Button
|
|
493
|
+
variant="outline"
|
|
494
|
+
size="sm"
|
|
495
|
+
className="h-8 text-xs"
|
|
496
|
+
onClick={handleClick}
|
|
497
|
+
title={config?.description}
|
|
498
|
+
data-testid={`datahub-editor-add-step-${type.toLowerCase()}`}
|
|
499
|
+
>
|
|
500
|
+
<Icon className="h-3 w-3 mr-1" />
|
|
501
|
+
{config?.label ?? type}
|
|
502
|
+
</Button>
|
|
503
|
+
);
|
|
504
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Textarea } from '@vendure/dashboard';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { DIALOG_DIMENSIONS, TEXTAREA_HEIGHTS } from '../../constants';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
definition: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function PipelineExportDialog({ definition }: Readonly<Props>) {
|
|
10
|
+
const [open, setOpen] = React.useState(false);
|
|
11
|
+
const code = React.useMemo(() => toPipelineTs(definition), [definition]);
|
|
12
|
+
|
|
13
|
+
async function copyToClipboard() {
|
|
14
|
+
try {
|
|
15
|
+
await navigator.clipboard.writeText(code);
|
|
16
|
+
} catch {
|
|
17
|
+
// Clipboard API failed - silently ignore (user can still use download)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function downloadFile() {
|
|
22
|
+
const blob = new Blob([code], { type: 'text/plain' });
|
|
23
|
+
const url = URL.createObjectURL(blob);
|
|
24
|
+
const downloadLink = document.createElement('a');
|
|
25
|
+
downloadLink.href = url;
|
|
26
|
+
downloadLink.download = 'pipeline.ts';
|
|
27
|
+
downloadLink.click();
|
|
28
|
+
URL.revokeObjectURL(url);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
34
|
+
Export to code
|
|
35
|
+
</Button>
|
|
36
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
37
|
+
<DialogContent className={`${DIALOG_DIMENSIONS.MAX_WIDTH_2XL} ${DIALOG_DIMENSIONS.MAX_HEIGHT_80VH} flex flex-col`}>
|
|
38
|
+
<DialogHeader className="flex-none">
|
|
39
|
+
<DialogTitle>Export pipeline</DialogTitle>
|
|
40
|
+
<DialogDescription>Copy or download TypeScript DSL</DialogDescription>
|
|
41
|
+
</DialogHeader>
|
|
42
|
+
<div className="flex flex-col gap-3 flex-1 min-h-0">
|
|
43
|
+
<Textarea value={code} readOnly className={`font-mono text-xs flex-1 ${TEXTAREA_HEIGHTS.CODE_EXPORT_MIN} ${TEXTAREA_HEIGHTS.CODE_EXPORT_MAX} resize-none`} />
|
|
44
|
+
<div className="flex gap-2 flex-none">
|
|
45
|
+
<Button onClick={copyToClipboard}>Copy</Button>
|
|
46
|
+
<Button variant="secondary" onClick={downloadFile}>
|
|
47
|
+
Download
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</DialogContent>
|
|
52
|
+
</Dialog>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toPipelineTs(definition: unknown): string {
|
|
58
|
+
const json = JSON.stringify(definition, null, 2);
|
|
59
|
+
return `import { definePipeline } from '@vendure/data-hub';
|
|
60
|
+
|
|
61
|
+
export default definePipeline(${json} as const);
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Textarea } from '@vendure/dashboard';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { getErrorMessage } from '../../../shared';
|
|
4
|
+
import { useValidatePipelineDefinition } from '../../hooks';
|
|
5
|
+
import type { PipelineDefinition } from '../../types';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
onImport: (definition: PipelineDefinition) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function PipelineImportDialog({ onImport }: Readonly<Props>) {
|
|
12
|
+
const [open, setOpen] = React.useState(false);
|
|
13
|
+
const [text, setText] = React.useState('');
|
|
14
|
+
const [errors, setErrors] = React.useState<string[]>([]);
|
|
15
|
+
const [parsed, setParsed] = React.useState<PipelineDefinition | null>(null);
|
|
16
|
+
|
|
17
|
+
const validateMutation = useValidatePipelineDefinition();
|
|
18
|
+
|
|
19
|
+
async function handleValidate() {
|
|
20
|
+
setErrors([]);
|
|
21
|
+
setParsed(null);
|
|
22
|
+
try {
|
|
23
|
+
const def = JSON.parse(text);
|
|
24
|
+
const result = await validateMutation.mutateAsync(def);
|
|
25
|
+
if (result?.isValid) {
|
|
26
|
+
setParsed(def);
|
|
27
|
+
} else {
|
|
28
|
+
setErrors(result?.errors ?? ['Invalid definition']);
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
setErrors([getErrorMessage(e)]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handleImport() {
|
|
36
|
+
if (parsed) {
|
|
37
|
+
onImport(parsed);
|
|
38
|
+
setOpen(false);
|
|
39
|
+
setText('');
|
|
40
|
+
setErrors([]);
|
|
41
|
+
setParsed(null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
48
|
+
Import JSON
|
|
49
|
+
</Button>
|
|
50
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
51
|
+
<DialogContent className="max-w-3xl">
|
|
52
|
+
<DialogHeader>
|
|
53
|
+
<DialogTitle>Import pipeline from JSON</DialogTitle>
|
|
54
|
+
<DialogDescription>Paste a PipelineDefinition JSON and validate before importing.</DialogDescription>
|
|
55
|
+
</DialogHeader>
|
|
56
|
+
<div className="space-y-3">
|
|
57
|
+
<Textarea
|
|
58
|
+
value={text}
|
|
59
|
+
onChange={(e) => setText(e.target.value)}
|
|
60
|
+
placeholder='{"version":1,"steps":[]}'
|
|
61
|
+
className="font-mono min-h-[260px]"
|
|
62
|
+
/>
|
|
63
|
+
{errors.length > 0 && (
|
|
64
|
+
<div className="border border-destructive/40 rounded-md p-3">
|
|
65
|
+
<div className="text-sm font-medium text-destructive mb-1">Validation errors</div>
|
|
66
|
+
<ul className="list-disc pl-5 text-sm">
|
|
67
|
+
{/* Index as key acceptable - error messages are static after validation */}
|
|
68
|
+
{errors.map((e, errorIndex) => (
|
|
69
|
+
<li key={`error-${errorIndex}`}>{e}</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
<div className="flex gap-2">
|
|
75
|
+
<Button variant="outline" onClick={handleValidate} disabled={validateMutation.isPending}>
|
|
76
|
+
{validateMutation.isPending ? 'Validating…' : 'Validate'}
|
|
77
|
+
</Button>
|
|
78
|
+
<Button onClick={handleImport} disabled={!parsed}>
|
|
79
|
+
Import
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</DialogContent>
|
|
84
|
+
</Dialog>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
}
|