fix: alle Node definitionen korrigiert und im backend gesetzt - keine mapping layer sonder saubere quelldaten, fehlende dataRef parameter hinzugefügt, damit jede node kontext nutzen kann
This commit is contained in:
parent
992c0472c6
commit
1d2d247273
8 changed files with 74 additions and 25 deletions
|
|
@ -85,11 +85,19 @@ export interface SystemVariable {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Single form field type with its canonical port primitive. Delivered by GET /node-types. */
|
||||||
|
export interface FormFieldType {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
portType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NodeTypesResponse {
|
export interface NodeTypesResponse {
|
||||||
nodeTypes: NodeType[];
|
nodeTypes: NodeType[];
|
||||||
categories: NodeTypeCategory[];
|
categories: NodeTypeCategory[];
|
||||||
portTypeCatalog?: Record<string, PortSchema>;
|
portTypeCatalog?: Record<string, PortSchema>;
|
||||||
systemVariables?: Record<string, SystemVariable>;
|
systemVariables?: Record<string, SystemVariable>;
|
||||||
|
formFieldTypes?: FormFieldType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Automation2GraphNode {
|
export interface Automation2GraphNode {
|
||||||
|
|
@ -279,12 +287,14 @@ export async function fetchNodeTypes(
|
||||||
const categories = data?.categories ?? [];
|
const categories = data?.categories ?? [];
|
||||||
const portTypeCatalog = data?.portTypeCatalog ?? undefined;
|
const portTypeCatalog = data?.portTypeCatalog ?? undefined;
|
||||||
const systemVariables = data?.systemVariables ?? undefined;
|
const systemVariables = data?.systemVariables ?? undefined;
|
||||||
|
const formFieldTypes = data?.formFieldTypes ?? undefined;
|
||||||
console.log(
|
console.log(
|
||||||
`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories, ` +
|
`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories, ` +
|
||||||
`${portTypeCatalog ? Object.keys(portTypeCatalog).length : 0} portTypes, ` +
|
`${portTypeCatalog ? Object.keys(portTypeCatalog).length : 0} portTypes, ` +
|
||||||
`${systemVariables ? Object.keys(systemVariables).length : 0} sysVars`
|
`${systemVariables ? Object.keys(systemVariables).length : 0} sysVars, ` +
|
||||||
|
`${formFieldTypes ? formFieldTypes.length : 0} formFieldTypes`
|
||||||
);
|
);
|
||||||
return { nodeTypes, categories, portTypeCatalog, systemVariables };
|
return { nodeTypes, categories, portTypeCatalog, systemVariables, formFieldTypes };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpstreamPathEntry {
|
export interface UpstreamPathEntry {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import React, { createContext, useContext, useMemo } from 'react';
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
|
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
|
||||||
import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
|
import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
|
||||||
import type { ApiRequestFunction, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
|
import type { ApiRequestFunction, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
|
||||||
|
|
||||||
export interface Automation2DataFlowContextValue {
|
export interface Automation2DataFlowContextValue {
|
||||||
currentNodeId: string;
|
currentNodeId: string;
|
||||||
|
|
@ -17,6 +17,8 @@ export interface Automation2DataFlowContextValue {
|
||||||
language: string;
|
language: string;
|
||||||
portTypeCatalog: Record<string, PortSchema>;
|
portTypeCatalog: Record<string, PortSchema>;
|
||||||
systemVariables: Record<string, SystemVariable>;
|
systemVariables: Record<string, SystemVariable>;
|
||||||
|
/** Canonical form field types from the API — maps UI type id to portType primitive. */
|
||||||
|
formFieldTypes: FormFieldType[];
|
||||||
getNodeLabel: (node: { id: string; title?: string; label?: string; type?: string }) => string;
|
getNodeLabel: (node: { id: string; title?: string; label?: string; type?: string }) => string;
|
||||||
getAvailableSourceIds: () => string[];
|
getAvailableSourceIds: () => string[];
|
||||||
/** Present when rendered inside the flow editor (ConnectionPicker / tools). */
|
/** Present when rendered inside the flow editor (ConnectionPicker / tools). */
|
||||||
|
|
@ -41,6 +43,7 @@ interface Automation2DataFlowProviderProps {
|
||||||
language: string;
|
language: string;
|
||||||
portTypeCatalog?: Record<string, PortSchema>;
|
portTypeCatalog?: Record<string, PortSchema>;
|
||||||
systemVariables?: Record<string, SystemVariable>;
|
systemVariables?: Record<string, SystemVariable>;
|
||||||
|
formFieldTypes?: FormFieldType[];
|
||||||
instanceId?: string;
|
instanceId?: string;
|
||||||
request?: ApiRequestFunction;
|
request?: ApiRequestFunction;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
@ -55,12 +58,18 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
language,
|
language,
|
||||||
portTypeCatalog = {},
|
portTypeCatalog = {},
|
||||||
systemVariables = {},
|
systemVariables = {},
|
||||||
|
formFieldTypes = [],
|
||||||
instanceId,
|
instanceId,
|
||||||
request,
|
request,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const value = useMemo((): Automation2DataFlowContextValue | null => {
|
const value = useMemo((): Automation2DataFlowContextValue | null => {
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
|
const formTypeToPort: Record<string, string> = Object.fromEntries(
|
||||||
|
formFieldTypes.map((f) => [f.id, f.portType])
|
||||||
|
);
|
||||||
|
const resolvePortType = (rawType: string): string => formTypeToPort[rawType] ?? rawType;
|
||||||
|
|
||||||
const parseGraphDefinedSchema = (parameterKey: string): PortSchema | null => {
|
const parseGraphDefinedSchema = (parameterKey: string): PortSchema | null => {
|
||||||
const raw = node.parameters?.[parameterKey];
|
const raw = node.parameters?.[parameterKey];
|
||||||
if (!Array.isArray(raw)) return null;
|
if (!Array.isArray(raw)) return null;
|
||||||
|
|
@ -72,8 +81,8 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
const lab = rec.label;
|
const lab = rec.label;
|
||||||
const desc =
|
const desc =
|
||||||
typeof lab === 'string' ? lab : typeof lab === 'object' && lab !== null ? String((lab as Record<string, string>).de ?? '') : '';
|
typeof lab === 'string' ? lab : typeof lab === 'object' && lab !== null ? String((lab as Record<string, string>).de ?? '') : '';
|
||||||
const ftype = typeof rec.type === 'string' ? rec.type : 'str';
|
const rawType = typeof rec.type === 'string' ? rec.type : 'str';
|
||||||
if (ftype === 'group' && Array.isArray(rec.fields)) {
|
if (rawType === 'group' && Array.isArray(rec.fields)) {
|
||||||
for (const sub of rec.fields as Record<string, unknown>[]) {
|
for (const sub of rec.fields as Record<string, unknown>[]) {
|
||||||
if (!sub || typeof sub.name !== 'string') continue;
|
if (!sub || typeof sub.name !== 'string') continue;
|
||||||
const sl = sub.label;
|
const sl = sub.label;
|
||||||
|
|
@ -85,7 +94,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
: '';
|
: '';
|
||||||
fields.push({
|
fields.push({
|
||||||
name: `${rec.name}.${sub.name}`,
|
name: `${rec.name}.${sub.name}`,
|
||||||
type: typeof sub.type === 'string' ? sub.type : 'str',
|
type: resolvePortType(typeof sub.type === 'string' ? sub.type : 'str'),
|
||||||
description: (sdesc && sdesc.trim()) || `${rec.name}.${sub.name}`,
|
description: (sdesc && sdesc.trim()) || `${rec.name}.${sub.name}`,
|
||||||
required: Boolean(sub.required),
|
required: Boolean(sub.required),
|
||||||
});
|
});
|
||||||
|
|
@ -94,7 +103,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
}
|
}
|
||||||
fields.push({
|
fields.push({
|
||||||
name: rec.name,
|
name: rec.name,
|
||||||
type: ftype,
|
type: resolvePortType(rawType),
|
||||||
description: (desc && desc.trim()) || rec.name,
|
description: (desc && desc.trim()) || rec.name,
|
||||||
required: Boolean(rec.required),
|
required: Boolean(rec.required),
|
||||||
});
|
});
|
||||||
|
|
@ -110,6 +119,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
language,
|
language,
|
||||||
portTypeCatalog,
|
portTypeCatalog,
|
||||||
systemVariables,
|
systemVariables,
|
||||||
|
formFieldTypes,
|
||||||
getNodeLabel: (n: { id: string; title?: string; label?: string; type?: string }) =>
|
getNodeLabel: (n: { id: string; title?: string; label?: string; type?: string }) =>
|
||||||
n.title ?? n.label ?? n.type ?? n.id,
|
n.title ?? n.label ?? n.type ?? n.id,
|
||||||
getAvailableSourceIds: () => getAvailableSources(node.id, nodes, connections),
|
getAvailableSourceIds: () => getAvailableSources(node.id, nodes, connections),
|
||||||
|
|
@ -117,7 +127,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
request,
|
request,
|
||||||
parseGraphDefinedSchema,
|
parseGraphDefinedSchema,
|
||||||
};
|
};
|
||||||
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, instanceId, request]);
|
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, instanceId, request]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Automation2DataFlowContext.Provider value={value}>
|
<Automation2DataFlowContext.Provider value={value}>
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
|
const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
|
||||||
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
|
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
|
||||||
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
|
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
|
||||||
|
const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowApi').FormFieldType[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
|
@ -459,6 +460,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setRegistryCatalog(data.portTypeCatalog as never);
|
setRegistryCatalog(data.portTypeCatalog as never);
|
||||||
}
|
}
|
||||||
if (data.systemVariables) setSystemVariables(data.systemVariables);
|
if (data.systemVariables) setSystemVariables(data.systemVariables);
|
||||||
|
if (data.formFieldTypes) setFormFieldTypes(data.formFieldTypes);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
setError(err instanceof Error ? err.message : String(err));
|
||||||
setNodeTypes([]);
|
setNodeTypes([]);
|
||||||
|
|
@ -904,6 +906,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
language={language}
|
language={language}
|
||||||
portTypeCatalog={portTypeCatalog as Record<string, never>}
|
portTypeCatalog={portTypeCatalog as Record<string, never>}
|
||||||
systemVariables={systemVariables as Record<string, never>}
|
systemVariables={systemVariables as Record<string, never>}
|
||||||
|
formFieldTypes={formFieldTypes}
|
||||||
instanceId={instanceId}
|
instanceId={instanceId}
|
||||||
request={request}
|
request={request}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,16 @@ import { FaGripVertical, FaTimes } from 'react-icons/fa';
|
||||||
import type { FormField, NodeConfigRendererProps } from '../shared/types';
|
import type { FormField, NodeConfigRendererProps } from '../shared/types';
|
||||||
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/Automation2FlowEditor.module.css';
|
||||||
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const ctx = useAutomation2DataFlow();
|
||||||
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
||||||
|
? ctx.formFieldTypes
|
||||||
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
||||||
const fields = (params.fields as FormField[]) ?? [];
|
const fields = (params.fields as FormField[]) ?? [];
|
||||||
|
|
||||||
const moveField = (fromIndex: number, toIndex: number) => {
|
const moveField = (fromIndex: number, toIndex: number) => {
|
||||||
|
|
@ -88,8 +93,8 @@ export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, upda
|
||||||
}}
|
}}
|
||||||
style={{ width: 'auto', minWidth: 90 }}
|
style={{ width: 'auto', minWidth: 90 }}
|
||||||
>
|
>
|
||||||
{FORM_FIELD_TYPES.map(ft => (
|
{fieldTypeOptions.map((ft) => (
|
||||||
<option key={ft} value={ft}>{t(FORM_FIELD_TYPE_LABELS[ft])}</option>
|
<option key={ft.id} value={ft.id}>{t(ft.label)}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<label className={styles.formFieldRequiredLabel}>
|
<label className={styles.formFieldRequiredLabel}>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { ComponentType } from 'react';
|
||||||
import type { NodeTypeParameter } from '../../../../api/workflowApi';
|
import type { NodeTypeParameter } from '../../../../api/workflowApi';
|
||||||
import type { ApiRequestFunction } from '../../../../api/workflowApi';
|
import type { ApiRequestFunction } from '../../../../api/workflowApi';
|
||||||
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
||||||
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||||
|
|
||||||
export interface FieldRendererProps {
|
export interface FieldRendererProps {
|
||||||
param: NodeTypeParameter;
|
param: NodeTypeParameter;
|
||||||
|
|
@ -27,7 +28,6 @@ export type FieldRendererComponent = ComponentType<FieldRendererProps>;
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
|
||||||
import { toApiGraph } from '../shared/graphUtils';
|
import { toApiGraph } from '../shared/graphUtils';
|
||||||
import { postUpstreamPaths } from '../../../../api/workflowApi';
|
import { postUpstreamPaths } from '../../../../api/workflowApi';
|
||||||
import type { CanvasNode } from '../../editor/FlowCanvas';
|
import type { CanvasNode } from '../../editor/FlowCanvas';
|
||||||
|
|
@ -535,6 +535,10 @@ const CaseListEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }
|
||||||
|
|
||||||
const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const ctx = useAutomation2DataFlow();
|
||||||
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
||||||
|
? ctx.formFieldTypes
|
||||||
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
||||||
const fields = Array.isArray(value) ? value : [];
|
const fields = Array.isArray(value) ? value : [];
|
||||||
const addField = () => onChange([...fields, { name: '', type: 'text', label: '', required: false }]);
|
const addField = () => onChange([...fields, { name: '', type: 'text', label: '', required: false }]);
|
||||||
const removeField = (idx: number) => onChange(fields.filter((_: unknown, i: number) => i !== idx));
|
const removeField = (idx: number) => onChange(fields.filter((_: unknown, i: number) => i !== idx));
|
||||||
|
|
@ -550,8 +554,8 @@ const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChan
|
||||||
<div key={i} style={{ display: 'flex', gap: 4, marginBottom: 4, alignItems: 'center' }}>
|
<div key={i} style={{ display: 'flex', gap: 4, marginBottom: 4, alignItems: 'center' }}>
|
||||||
<input type="text" placeholder={t('Name')} value={String(f.name ?? '')} onChange={(e) => updateField(i, 'name', e.target.value)} style={{ flex: 1, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }} />
|
<input type="text" placeholder={t('Name')} value={String(f.name ?? '')} onChange={(e) => updateField(i, 'name', e.target.value)} style={{ flex: 1, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }} />
|
||||||
<select value={String(f.type ?? 'text')} onChange={(e) => updateField(i, 'type', e.target.value)} style={{ flex: 1, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }}>
|
<select value={String(f.type ?? 'text')} onChange={(e) => updateField(i, 'type', e.target.value)} style={{ flex: 1, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }}>
|
||||||
{FORM_FIELD_TYPES.map(ft => (
|
{fieldTypeOptions.map((ft) => (
|
||||||
<option key={ft} value={ft}>{t(FORM_FIELD_TYPE_LABELS[ft])}</option>
|
<option key={ft.id} value={ft.id}>{t(ft.label)}</option>
|
||||||
))}
|
))}
|
||||||
<option value="group">{t('Gruppe')}</option>
|
<option value="group">{t('Gruppe')}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -585,8 +589,8 @@ const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChan
|
||||||
}}
|
}}
|
||||||
style={{ flex: 1, minWidth: 80, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }}
|
style={{ flex: 1, minWidth: 80, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }}
|
||||||
>
|
>
|
||||||
{FORM_FIELD_TYPES.map(ft => (
|
{fieldTypeOptions.map((ft) => (
|
||||||
<option key={ft} value={ft}>{t(FORM_FIELD_TYPE_LABELS[ft])}</option>
|
<option key={ft.id} value={ft.id}>{t(ft.label)}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,9 @@ function _markIterableCandidates(paths: PickablePath[], expectedParamType?: stri
|
||||||
function _deriveFormPortSchemaFromParams(
|
function _deriveFormPortSchemaFromParams(
|
||||||
node: { parameters?: Record<string, unknown> },
|
node: { parameters?: Record<string, unknown> },
|
||||||
paramKey: string,
|
paramKey: string,
|
||||||
|
formTypeToPort: Record<string, string> = {},
|
||||||
): PortSchema | undefined {
|
): PortSchema | undefined {
|
||||||
|
const resolvePortType = (rawType: string) => formTypeToPort[rawType] ?? rawType;
|
||||||
const raw = node.parameters?.[paramKey];
|
const raw = node.parameters?.[paramKey];
|
||||||
if (!Array.isArray(raw)) return undefined;
|
if (!Array.isArray(raw)) return undefined;
|
||||||
const fields: Array<{ name: string; type: string; description: string | Record<string, string>; required: boolean }> = [];
|
const fields: Array<{ name: string; type: string; description: string | Record<string, string>; required: boolean }> = [];
|
||||||
|
|
@ -90,8 +92,8 @@ function _deriveFormPortSchemaFromParams(
|
||||||
let description: string | Record<string, string> = rec.name;
|
let description: string | Record<string, string> = rec.name;
|
||||||
if (typeof lab === 'string') description = lab;
|
if (typeof lab === 'string') description = lab;
|
||||||
else if (lab && typeof lab === 'object') description = lab as Record<string, string>;
|
else if (lab && typeof lab === 'object') description = lab as Record<string, string>;
|
||||||
const ftype = typeof rec.type === 'string' ? rec.type : 'str';
|
const rawType = typeof rec.type === 'string' ? rec.type : 'str';
|
||||||
if (ftype === 'group' && Array.isArray(rec.fields)) {
|
if (rawType === 'group' && Array.isArray(rec.fields)) {
|
||||||
for (const sub of rec.fields as Record<string, unknown>[]) {
|
for (const sub of rec.fields as Record<string, unknown>[]) {
|
||||||
if (!sub || typeof sub.name !== 'string') continue;
|
if (!sub || typeof sub.name !== 'string') continue;
|
||||||
const sl = sub.label;
|
const sl = sub.label;
|
||||||
|
|
@ -100,7 +102,7 @@ function _deriveFormPortSchemaFromParams(
|
||||||
else if (sl && typeof sl === 'object') sdesc = sl as Record<string, string>;
|
else if (sl && typeof sl === 'object') sdesc = sl as Record<string, string>;
|
||||||
fields.push({
|
fields.push({
|
||||||
name: `${rec.name}.${sub.name}`,
|
name: `${rec.name}.${sub.name}`,
|
||||||
type: typeof sub.type === 'string' ? sub.type : 'str',
|
type: resolvePortType(typeof sub.type === 'string' ? sub.type : 'str'),
|
||||||
description: sdesc,
|
description: sdesc,
|
||||||
required: Boolean(sub.required),
|
required: Boolean(sub.required),
|
||||||
});
|
});
|
||||||
|
|
@ -109,7 +111,7 @@ function _deriveFormPortSchemaFromParams(
|
||||||
}
|
}
|
||||||
fields.push({
|
fields.push({
|
||||||
name: rec.name,
|
name: rec.name,
|
||||||
type: ftype,
|
type: resolvePortType(rawType),
|
||||||
description,
|
description,
|
||||||
required: Boolean(rec.required),
|
required: Boolean(rec.required),
|
||||||
});
|
});
|
||||||
|
|
@ -151,6 +153,7 @@ function _resolveSchemaForNode(
|
||||||
connections: Array<{ source: string; target: string; sourceOutput?: number }>,
|
connections: Array<{ source: string; target: string; sourceOutput?: number }>,
|
||||||
catalog: Record<string, PortSchema>,
|
catalog: Record<string, PortSchema>,
|
||||||
visited: Set<string> = new Set(),
|
visited: Set<string> = new Set(),
|
||||||
|
formTypeToPort: Record<string, string> = {},
|
||||||
): PortSchema | undefined {
|
): PortSchema | undefined {
|
||||||
if (visited.has(nodeId)) return undefined;
|
if (visited.has(nodeId)) return undefined;
|
||||||
visited.add(nodeId);
|
visited.add(nodeId);
|
||||||
|
|
@ -170,10 +173,10 @@ function _resolveSchemaForNode(
|
||||||
const schemaSpec = port0.schema;
|
const schemaSpec = port0.schema;
|
||||||
if (typeof schemaSpec === 'object' && schemaSpec !== null && schemaSpec.kind === 'fromGraph') {
|
if (typeof schemaSpec === 'object' && schemaSpec !== null && schemaSpec.kind === 'fromGraph') {
|
||||||
const paramKey = schemaSpec.parameter ?? 'fields';
|
const paramKey = schemaSpec.parameter ?? 'fields';
|
||||||
return _deriveFormPortSchemaFromParams(node, paramKey);
|
return _deriveFormPortSchemaFromParams(node, paramKey, formTypeToPort);
|
||||||
}
|
}
|
||||||
if (port0.dynamic && port0.deriveFrom) {
|
if (port0.dynamic && port0.deriveFrom) {
|
||||||
return _deriveFormPortSchemaFromParams(node, port0.deriveFrom);
|
return _deriveFormPortSchemaFromParams(node, port0.deriveFrom, formTypeToPort);
|
||||||
}
|
}
|
||||||
if (typeof schemaSpec === 'string' && schemaSpec !== 'Transit') {
|
if (typeof schemaSpec === 'string' && schemaSpec !== 'Transit') {
|
||||||
return catalog[schemaSpec];
|
return catalog[schemaSpec];
|
||||||
|
|
@ -182,7 +185,7 @@ function _resolveSchemaForNode(
|
||||||
// Transit: follow the incoming connection to find the real producer
|
// Transit: follow the incoming connection to find the real producer
|
||||||
const incoming = connections.find((c) => c.target === nodeId);
|
const incoming = connections.find((c) => c.target === nodeId);
|
||||||
if (!incoming) return undefined;
|
if (!incoming) return undefined;
|
||||||
return _resolveSchemaForNode(incoming.source, nodes, nodeTypes, connections, catalog, visited);
|
return _resolveSchemaForNode(incoming.source, nodes, nodeTypes, connections, catalog, visited, formTypeToPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
|
|
@ -228,6 +231,9 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
const catalog = ctx?.portTypeCatalog ?? {};
|
const catalog = ctx?.portTypeCatalog ?? {};
|
||||||
const systemVars = ctx?.systemVariables ?? {};
|
const systemVars = ctx?.systemVariables ?? {};
|
||||||
const nodeTypes = ctx?.nodeTypes ?? [];
|
const nodeTypes = ctx?.nodeTypes ?? [];
|
||||||
|
const formTypeToPort: Record<string, string> = Object.fromEntries(
|
||||||
|
(ctx?.formFieldTypes ?? []).map((f) => [f.id, f.portType])
|
||||||
|
);
|
||||||
|
|
||||||
const toggleExpand = (nodeId: string) => {
|
const toggleExpand = (nodeId: string) => {
|
||||||
setExpandedNodes((prev) => {
|
setExpandedNodes((prev) => {
|
||||||
|
|
@ -395,6 +401,8 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
connections,
|
connections,
|
||||||
catalog,
|
catalog,
|
||||||
|
new Set(),
|
||||||
|
formTypeToPort,
|
||||||
);
|
);
|
||||||
const schemaPaths = _buildPathsFromSchema(resolvedSchema, catalog);
|
const schemaPaths = _buildPathsFromSchema(resolvedSchema, catalog);
|
||||||
const annotated = _markIterableCandidates(
|
const annotated = _markIterableCandidates(
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,11 @@ export function createRef(nodeId: string, path: (string | number)[] = [], expect
|
||||||
return { type: 'ref', nodeId, path, ...(expectedType ? { expectedType } : {}) };
|
return { type: 'ref', nodeId, path, ...(expectedType ? { expectedType } : {}) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Structural type compatibility (best-effort; same as gateway soft rules). */
|
/**
|
||||||
|
* Structural type compatibility using the canonical type vocabulary: str / int / float / bool / Any.
|
||||||
|
* All node parameters and form field schemas must use these types (no `string`, `number`, `boolean`
|
||||||
|
* aliases) so no alias-mapping is needed here.
|
||||||
|
*/
|
||||||
export function isCompatible(producedType: string, expectedType: string): 'ok' | 'coerce' | 'mismatch' {
|
export function isCompatible(producedType: string, expectedType: string): 'ok' | 'coerce' | 'mismatch' {
|
||||||
if (!expectedType || !producedType) return 'ok';
|
if (!expectedType || !producedType) return 'ok';
|
||||||
if (producedType === expectedType) return 'ok';
|
if (producedType === expectedType) return 'ok';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { NodeConfigRendererProps } from '../shared/types';
|
||||||
import type { FormField } from '../shared/types';
|
import type { FormField } from '../shared/types';
|
||||||
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/Automation2FlowEditor.module.css';
|
||||||
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -28,6 +29,10 @@ function _parseFields(params: Record<string, unknown>, t: (key: string) => strin
|
||||||
|
|
||||||
export const FormStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
export const FormStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const ctx = useAutomation2DataFlow();
|
||||||
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
||||||
|
? ctx.formFieldTypes
|
||||||
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
||||||
const fields = useMemo(() => _parseFields(params, t), [params, t]);
|
const fields = useMemo(() => _parseFields(params, t), [params, t]);
|
||||||
|
|
||||||
const setFields = (next: FormField[]) => {
|
const setFields = (next: FormField[]) => {
|
||||||
|
|
@ -73,8 +78,8 @@ export const FormStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ params,
|
||||||
setFields(next);
|
setFields(next);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{FORM_FIELD_TYPES.map(ft => (
|
{fieldTypeOptions.map((ft) => (
|
||||||
<option key={ft} value={ft}>{t(FORM_FIELD_TYPE_LABELS[ft])}</option>
|
<option key={ft.id} value={ft.id}>{t(ft.label)}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue