fix: handover fix, if/else node extended comparison mode
This commit is contained in:
parent
587dad5cf9
commit
b5084c028e
8 changed files with 302 additions and 187 deletions
|
|
@ -116,10 +116,19 @@ export interface FormFieldType {
|
|||
portType: string;
|
||||
}
|
||||
|
||||
export interface ConditionOperatorDef {
|
||||
id: string;
|
||||
label: string;
|
||||
labelKey?: string;
|
||||
needsValue: boolean;
|
||||
valueInput?: { kind: string; options?: string[] };
|
||||
}
|
||||
|
||||
export interface NodeTypesResponse {
|
||||
nodeTypes: NodeType[];
|
||||
categories: NodeTypeCategory[];
|
||||
portTypeCatalog?: Record<string, PortSchema>;
|
||||
conditionOperatorCatalog?: Record<string, ConditionOperatorDef[]>;
|
||||
systemVariables?: Record<string, SystemVariable>;
|
||||
formFieldTypes?: FormFieldType[];
|
||||
}
|
||||
|
|
@ -310,15 +319,17 @@ export async function fetchNodeTypes(
|
|||
const nodeTypes = data?.nodeTypes ?? [];
|
||||
const categories = data?.categories ?? [];
|
||||
const portTypeCatalog = data?.portTypeCatalog ?? undefined;
|
||||
const conditionOperatorCatalog = data?.conditionOperatorCatalog ?? undefined;
|
||||
const systemVariables = data?.systemVariables ?? undefined;
|
||||
const formFieldTypes = data?.formFieldTypes ?? undefined;
|
||||
console.log(
|
||||
`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories, ` +
|
||||
`${portTypeCatalog ? Object.keys(portTypeCatalog).length : 0} portTypes, ` +
|
||||
`${conditionOperatorCatalog ? Object.keys(conditionOperatorCatalog).length : 0} conditionKinds, ` +
|
||||
`${systemVariables ? Object.keys(systemVariables).length : 0} sysVars, ` +
|
||||
`${formFieldTypes ? formFieldTypes.length : 0} formFieldTypes`
|
||||
);
|
||||
return { nodeTypes, categories, portTypeCatalog, systemVariables, formFieldTypes };
|
||||
return { nodeTypes, categories, portTypeCatalog, conditionOperatorCatalog, systemVariables, formFieldTypes };
|
||||
}
|
||||
|
||||
export interface UpstreamPathEntry {
|
||||
|
|
@ -328,6 +339,39 @@ export interface UpstreamPathEntry {
|
|||
type: string;
|
||||
label: string;
|
||||
scopeOrigin: 'data' | 'loop' | 'system';
|
||||
valueKind?: string;
|
||||
}
|
||||
|
||||
export interface ConditionMetaResponse {
|
||||
valueKind: string;
|
||||
operators: ConditionOperatorDef[];
|
||||
}
|
||||
|
||||
export interface ConditionMetaRequest {
|
||||
graph: Automation2Graph;
|
||||
nodeId?: string;
|
||||
ref: { type: 'ref'; nodeId: string; path: (string | number)[] };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/workflows/{instanceId}/condition-meta — operators for a DataRef (If/Else).
|
||||
*/
|
||||
export async function fetchConditionMeta(
|
||||
request: ApiRequestFunction,
|
||||
instanceId: string,
|
||||
body: ConditionMetaRequest,
|
||||
language = 'de'
|
||||
): Promise<ConditionMetaResponse> {
|
||||
const data = await request({
|
||||
url: `/api/workflows/${instanceId}/condition-meta`,
|
||||
method: 'post',
|
||||
params: { language },
|
||||
data: body,
|
||||
});
|
||||
return {
|
||||
valueKind: String(data?.valueKind ?? 'unknown'),
|
||||
operators: (data?.operators ?? []) as ConditionOperatorDef[],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
|
||||
import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
|
||||
import type { ApiRequestFunction, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
|
||||
import type { ApiRequestFunction, ConditionOperatorDef, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
|
||||
|
||||
export interface Automation2DataFlowContextValue {
|
||||
currentNodeId: string;
|
||||
|
|
@ -19,6 +19,8 @@ export interface Automation2DataFlowContextValue {
|
|||
systemVariables: Record<string, SystemVariable>;
|
||||
/** Canonical form field types from the API — maps UI type id to portType primitive. */
|
||||
formFieldTypes: FormFieldType[];
|
||||
/** Backend-driven condition operators per valueKind (flow.ifElse). */
|
||||
conditionOperatorCatalog: Record<string, ConditionOperatorDef[]>;
|
||||
getNodeLabel: (node: { id: string; title?: string; label?: string; type?: string }) => string;
|
||||
getAvailableSourceIds: () => string[];
|
||||
/** Present when rendered inside the flow editor (ConnectionPicker / tools). */
|
||||
|
|
@ -44,6 +46,7 @@ interface Automation2DataFlowProviderProps {
|
|||
portTypeCatalog?: Record<string, PortSchema>;
|
||||
systemVariables?: Record<string, SystemVariable>;
|
||||
formFieldTypes?: FormFieldType[];
|
||||
conditionOperatorCatalog?: Record<string, ConditionOperatorDef[]>;
|
||||
instanceId?: string;
|
||||
request?: ApiRequestFunction;
|
||||
children: React.ReactNode;
|
||||
|
|
@ -59,6 +62,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
|||
portTypeCatalog = {},
|
||||
systemVariables = {},
|
||||
formFieldTypes = [],
|
||||
conditionOperatorCatalog = {},
|
||||
instanceId,
|
||||
request,
|
||||
children,
|
||||
|
|
@ -120,6 +124,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
|||
portTypeCatalog,
|
||||
systemVariables,
|
||||
formFieldTypes,
|
||||
conditionOperatorCatalog,
|
||||
getNodeLabel: (n: { id: string; title?: string; label?: string; type?: string }) =>
|
||||
n.title ?? n.label ?? n.type ?? n.id,
|
||||
getAvailableSourceIds: () => getAvailableSources(node.id, nodes, connections),
|
||||
|
|
@ -127,7 +132,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
|||
request,
|
||||
parseGraphDefinedSchema,
|
||||
};
|
||||
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, instanceId, request]);
|
||||
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, conditionOperatorCatalog, instanceId, request]);
|
||||
|
||||
return (
|
||||
<Automation2DataFlowContext.Provider value={value}>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
|||
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
|
||||
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
|
||||
const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowApi').FormFieldType[]>([]);
|
||||
const [conditionOperatorCatalog, setConditionOperatorCatalog] = useState<
|
||||
Record<string, import('../../../api/workflowApi').ConditionOperatorDef[]>
|
||||
>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [filter, setFilter] = useState('');
|
||||
|
|
@ -545,6 +548,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
|||
}
|
||||
if (data.systemVariables) setSystemVariables(data.systemVariables);
|
||||
if (data.formFieldTypes) setFormFieldTypes(data.formFieldTypes);
|
||||
if (data.conditionOperatorCatalog) setConditionOperatorCatalog(data.conditionOperatorCatalog);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setNodeTypes([]);
|
||||
|
|
@ -1024,6 +1028,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
|||
portTypeCatalog={portTypeCatalog as Record<string, never>}
|
||||
systemVariables={systemVariables as Record<string, never>}
|
||||
formFieldTypes={formFieldTypes}
|
||||
conditionOperatorCatalog={conditionOperatorCatalog}
|
||||
instanceId={instanceId}
|
||||
request={request}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* Backend-driven condition editor for flow.ifElse (depends on Item dataRef).
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { FieldRendererProps } from './index';
|
||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||
import { isRef, type DataRef } from '../shared/dataRef';
|
||||
import { toApiGraph } from '../shared/graphUtils';
|
||||
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowApi';
|
||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||
|
||||
export interface StructuredCondition {
|
||||
type: 'condition';
|
||||
operator: string;
|
||||
value?: string | number;
|
||||
/** Legacy — ignored when Item is set */
|
||||
ref?: DataRef | null;
|
||||
}
|
||||
|
||||
function parseCondition(v: unknown): StructuredCondition {
|
||||
if (v && typeof v === 'object' && (v as StructuredCondition).type === 'condition') {
|
||||
const c = v as StructuredCondition;
|
||||
return { type: 'condition', operator: c.operator ?? 'eq', value: c.value };
|
||||
}
|
||||
return { type: 'condition', operator: 'eq', value: '' };
|
||||
}
|
||||
|
||||
function operatorsFromCatalog(
|
||||
catalog: Record<string, ConditionOperatorDef[]> | undefined,
|
||||
valueKind: string
|
||||
): ConditionOperatorDef[] {
|
||||
if (!catalog) return [];
|
||||
return catalog[valueKind] ?? catalog.unknown ?? [];
|
||||
}
|
||||
|
||||
export const ConditionEditor: React.FC<FieldRendererProps> = ({
|
||||
param,
|
||||
value,
|
||||
onChange,
|
||||
allParams,
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const dataFlow = useAutomation2DataFlow();
|
||||
const dependsOn =
|
||||
param.frontendOptions && typeof param.frontendOptions === 'object'
|
||||
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'Item')
|
||||
: 'Item';
|
||||
|
||||
const itemRef = allParams?.[dependsOn];
|
||||
const ref: DataRef | null = isRef(itemRef) ? itemRef : null;
|
||||
|
||||
const cond = parseCondition(value);
|
||||
const [operators, setOperators] = React.useState<ConditionOperatorDef[]>([]);
|
||||
const [valueKind, setValueKind] = React.useState('unknown');
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const catalog = dataFlow?.conditionOperatorCatalog;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ref) {
|
||||
setOperators([]);
|
||||
setValueKind('unknown');
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const applyMeta = (vk: string, ops: ConditionOperatorDef[]) => {
|
||||
if (cancelled) return;
|
||||
setValueKind(vk);
|
||||
setOperators(ops);
|
||||
const valid = ops.some((o) => o.id === cond.operator);
|
||||
if (!valid && ops.length > 0) {
|
||||
const first = ops[0];
|
||||
onChange({
|
||||
type: 'condition',
|
||||
operator: first.id,
|
||||
value: first.needsValue ? cond.value ?? '' : undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (dataFlow?.instanceId && dataFlow.request) {
|
||||
setLoading(true);
|
||||
fetchConditionMeta(dataFlow.request, dataFlow.instanceId, {
|
||||
graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
|
||||
nodeId: dataFlow.currentNodeId,
|
||||
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },
|
||||
})
|
||||
.then((meta) => {
|
||||
applyMeta(meta.valueKind, meta.operators);
|
||||
})
|
||||
.catch(() => {
|
||||
const ops = operatorsFromCatalog(catalog, 'unknown');
|
||||
applyMeta('unknown', ops);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
} else {
|
||||
const ops = operatorsFromCatalog(catalog, 'unknown');
|
||||
applyMeta('unknown', ops);
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- reset operators when Item ref changes
|
||||
}, [ref?.nodeId, JSON.stringify(ref?.path), dataFlow?.currentNodeId, catalog]);
|
||||
|
||||
const currentOp = operators.find((o) => o.id === cond.operator) ?? operators[0];
|
||||
const needsValue = currentOp?.needsValue ?? true;
|
||||
const valueInput = currentOp?.valueInput;
|
||||
|
||||
const setCondition = (next: StructuredCondition) => {
|
||||
onChange(next);
|
||||
};
|
||||
|
||||
if (!ref) {
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 4 }}>
|
||||
{param.description || param.name}
|
||||
</label>
|
||||
<p style={{ fontSize: 12, color: 'var(--text-secondary)', margin: 0 }}>
|
||||
{t('Zuerst ein Item im Data Picker wählen')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleOperatorChange = (opId: string) => {
|
||||
const opDef = operators.find((o) => o.id === opId);
|
||||
setCondition({
|
||||
type: 'condition',
|
||||
operator: opId,
|
||||
value: opDef?.needsValue ? cond.value ?? '' : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const handleValueChange = (v: string | number) => {
|
||||
const kind = valueInput?.kind;
|
||||
const parsed =
|
||||
kind === 'number' || valueKind === 'number' ? parseFloat(String(v)) || 0 : String(v);
|
||||
setCondition({ type: 'condition', operator: cond.operator, value: parsed });
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 4 }}>
|
||||
{param.description || param.name}
|
||||
</label>
|
||||
<ConditionRow>
|
||||
<label>{t('Vergleich')}</label>
|
||||
<select
|
||||
value={cond.operator}
|
||||
onChange={(e) => handleOperatorChange(e.target.value)}
|
||||
disabled={loading || operators.length === 0}
|
||||
style={{ flex: 1, padding: '4px 6px', borderRadius: 4, border: '1px solid #ccc' }}
|
||||
>
|
||||
{operators.map((o) => (
|
||||
<option key={o.id} value={o.id}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</ConditionRow>
|
||||
{loading && (
|
||||
<div style={{ fontSize: 11, color: '#888', marginBottom: 4 }}>{t('Lade Operatoren…')}</div>
|
||||
)}
|
||||
{needsValue && (
|
||||
<ConditionRow>
|
||||
<label>{t('Wert')}</label>
|
||||
{valueInput?.kind === 'select' ||
|
||||
valueInput?.kind === 'contentType' ||
|
||||
valueInput?.kind === 'outputMode' ||
|
||||
valueInput?.kind === 'language' ||
|
||||
valueInput?.kind === 'mime' ? (
|
||||
<select
|
||||
value={String(cond.value ?? '')}
|
||||
onChange={(e) => handleValueChange(e.target.value)}
|
||||
style={{ flex: 1, padding: '4px 6px', borderRadius: 4, border: '1px solid #ccc' }}
|
||||
>
|
||||
<option value="">{t('— wählen —')}</option>
|
||||
{(valueInput.options ?? []).map((opt) => (
|
||||
<option key={opt} value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type={
|
||||
valueInput?.kind === 'number'
|
||||
? 'number'
|
||||
: valueInput?.kind === 'date'
|
||||
? 'date'
|
||||
: 'text'
|
||||
}
|
||||
value={String(cond.value ?? '')}
|
||||
onChange={(e) =>
|
||||
handleValueChange(
|
||||
valueInput?.kind === 'number' ? parseFloat(e.target.value) || 0 : e.target.value
|
||||
)
|
||||
}
|
||||
placeholder={
|
||||
valueInput?.kind === 'regex' ? t('Regex-Muster') : t('Wert eingeben')
|
||||
}
|
||||
style={{ flex: 1, padding: '4px 6px', borderRadius: 4, border: '1px solid #ccc' }}
|
||||
/>
|
||||
)}
|
||||
</ConditionRow>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ConditionRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 6, fontSize: 12 }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -53,6 +53,7 @@ import { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
|||
import { ContextAssignmentsEditor } from './ContextAssignmentsEditor';
|
||||
import { FeatureInstancePicker } from './FeatureInstancePicker';
|
||||
import { UserFileFolderPicker } from './UserFileFolderPicker';
|
||||
import { ConditionEditor } from './ConditionEditor';
|
||||
import { TemplateTextareaRenderer } from './TemplateTextareaRenderer';
|
||||
import { getApiBaseUrl } from '../../../../../config/config';
|
||||
|
||||
|
|
@ -907,30 +908,7 @@ const CronBuilder: React.FC<FieldRendererProps> = ({ param, value, onChange, all
|
|||
);
|
||||
};
|
||||
|
||||
const ConditionBuilder: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||
const { t } = useLanguage();
|
||||
const cond = (typeof value === 'object' && value !== null) ? value as Record<string, unknown> : {};
|
||||
const update = (field: string, val: unknown) => onChange({ ...cond, type: 'condition', [field]: val });
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<select value={String(cond.operator ?? 'eq')} onChange={(e) => update('operator', e.target.value)} style={{ flex: 1, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }}>
|
||||
<option value="eq">{t('ist gleich')}</option>
|
||||
<option value="neq">{t('ungleich')}</option>
|
||||
<option value="gt">{t('größer als')}</option>
|
||||
<option value="lt">{t('kleiner als')}</option>
|
||||
<option value="contains">{t('enthält')}</option>
|
||||
<option value="empty">{t('ist leer')}</option>
|
||||
<option value="not_empty">{t('ist nicht leer')}</option>
|
||||
<option value="is_true">{t('ist wahr')}</option>
|
||||
<option value="is_false">{t('ist falsch')}</option>
|
||||
</select>
|
||||
<input type="text" placeholder={t('Wert')} value={String(cond.value ?? '')} onChange={(e) => update('value', e.target.value)} style={{ flex: 2, padding: '2px 4px', borderRadius: 4, border: '1px solid #ccc' }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const ConditionBuilder = ConditionEditor;
|
||||
|
||||
const MappingTableEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||
const { t } = useLanguage();
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
/**
|
||||
* If/Else node config - inline UI: source dropdown, operator (type-dependent), value.
|
||||
* Kein Popup, alles in einer Zeile.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { NodeConfigRendererProps } from '../shared/types';
|
||||
import { RefSourceSelect, getFieldType } from '../shared/RefSourceSelect';
|
||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||
import { isRef } from '../shared/dataRef';
|
||||
import { getMimeTypeOptionsFromUploadParams } from '../runtime/fileTypeMimeMapping';
|
||||
import { operatorsForType } from '../shared/conditionOperators';
|
||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
||||
|
||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||
|
||||
export interface StructuredCondition {
|
||||
type: 'condition';
|
||||
ref: { type: 'ref'; nodeId: string; path: (string | number)[] } | null;
|
||||
operator: string;
|
||||
value?: string | number;
|
||||
}
|
||||
|
||||
function parseCondition(v: unknown): StructuredCondition | null {
|
||||
if (v && typeof v === 'object' && (v as StructuredCondition).type === 'condition') {
|
||||
const c = v as StructuredCondition;
|
||||
if (c.ref === null || isRef(c.ref)) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const IfElseNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
||||
const { t } = useLanguage();
|
||||
const dataFlow = useAutomation2DataFlow();
|
||||
|
||||
const cond = parseCondition(params.condition);
|
||||
const ref = cond?.ref ?? null;
|
||||
const operator = cond?.operator ?? 'eq';
|
||||
const value = cond?.value ?? '';
|
||||
|
||||
const fieldType = dataFlow ? getFieldType(ref, dataFlow.nodes, dataFlow.nodeOutputsPreview) : 'unknown';
|
||||
const operators = operatorsForType(fieldType);
|
||||
const currentOp = operators.find((o) => o.value === operator) ?? operators[0];
|
||||
const needsValue = currentOp?.needsValue ?? true;
|
||||
|
||||
const isMimeTypeRef =
|
||||
ref && ref.path?.length >= 2 && ref.path[ref.path.length - 1] === 'mimeType';
|
||||
const sourceNode = ref && dataFlow
|
||||
? dataFlow.nodes.find((n: { id: string; type?: string; parameters?: Record<string, unknown> }) => n.id === ref.nodeId)
|
||||
: null;
|
||||
const mimeTypeOptions =
|
||||
isMimeTypeRef && sourceNode?.type === 'input.upload' && sourceNode.parameters
|
||||
? getMimeTypeOptionsFromUploadParams(sourceNode.parameters as Record<string, unknown>)
|
||||
: [];
|
||||
|
||||
const setCondition = (next: StructuredCondition) => {
|
||||
updateParam('condition', next);
|
||||
};
|
||||
|
||||
const handleRefChange = (newRef: { type: 'ref'; nodeId: string; path: (string | number)[] } | null) => {
|
||||
if (!newRef) {
|
||||
setCondition({
|
||||
type: 'condition',
|
||||
ref: null,
|
||||
operator: 'eq',
|
||||
value: '',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newType = dataFlow ? getFieldType(newRef, dataFlow.nodes, dataFlow.nodeOutputsPreview) : 'unknown';
|
||||
const newOps = operatorsForType(newType);
|
||||
setCondition({
|
||||
type: 'condition',
|
||||
ref: newRef,
|
||||
operator: newOps[0]?.value ?? 'eq',
|
||||
value: cond?.value ?? '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleOperatorChange = (op: string) => {
|
||||
const opDef = operators.find((o) => o.value === op);
|
||||
setCondition({
|
||||
type: 'condition',
|
||||
ref: cond?.ref ?? null,
|
||||
operator: op,
|
||||
value: opDef?.needsValue ? value : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const handleValueChange = (v: string | number) => {
|
||||
setCondition({
|
||||
type: 'condition',
|
||||
ref: cond?.ref ?? null,
|
||||
operator,
|
||||
value: fieldType === 'number' ? (parseFloat(String(v)) || 0) : String(v),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.ifElseConditionEditor}>
|
||||
<div className={styles.ifElseConditionRow}>
|
||||
<label>{t('Datenquelle')}</label>
|
||||
<RefSourceSelect value={ref} onChange={handleRefChange} placeholder={t('Formularfeld wählen')} />
|
||||
</div>
|
||||
<div className={styles.ifElseConditionRow}>
|
||||
<label>Vergleich</label>
|
||||
<select value={operator} onChange={(e) => handleOperatorChange(e.target.value)}>
|
||||
{operators.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{needsValue && (
|
||||
<div className={styles.ifElseConditionRow}>
|
||||
<label>{t('Wert')}</label>
|
||||
{mimeTypeOptions.length > 0 ? (
|
||||
<select
|
||||
value={String(value ?? '')}
|
||||
onChange={(e) => handleValueChange(e.target.value)}
|
||||
>
|
||||
<option value="">{t('MIME-Typ wählen')}</option>
|
||||
{mimeTypeOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label} ({o.value})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type={fieldType === 'number' ? 'number' : fieldType === 'date' ? 'date' : 'text'}
|
||||
value={String(value ?? '')}
|
||||
onChange={(e) =>
|
||||
handleValueChange(
|
||||
fieldType === 'number' ? parseFloat(e.target.value) || 0 : e.target.value
|
||||
)
|
||||
}
|
||||
placeholder={
|
||||
fieldType === 'number'
|
||||
? '0'
|
||||
: fieldType === 'date'
|
||||
? 'TT.MM.JJJJ'
|
||||
: isMimeTypeRef
|
||||
? t('z.B. application/pdf')
|
||||
: t('z.B. ch')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1 +1,2 @@
|
|||
export { IfElseNodeConfig } from './IfElseNodeConfig';
|
||||
export { ConditionEditor as IfElseNodeConfig } from '../frontendTypeRenderers/ConditionEditor';
|
||||
export type { StructuredCondition } from '../frontendTypeRenderers/ConditionEditor';
|
||||
|
|
|
|||
|
|
@ -1177,6 +1177,13 @@ const _FileLinkList: React.FC<{ files: Array<{ id: string; fileName?: string }>
|
|||
);
|
||||
};
|
||||
|
||||
const _INTERNAL_EXTRACT_FILENAME_SUBSTR = 'extracted_content_transient';
|
||||
|
||||
/** Hide persisted transient extract JSON from user-facing Workspace file lists */
|
||||
function _isHiddenWorkflowArtifactFile(f: { fileName?: string }): boolean {
|
||||
return (f.fileName ?? '').toLowerCase().includes(_INTERNAL_EXTRACT_FILENAME_SUBSTR);
|
||||
}
|
||||
|
||||
const _ProducedFilesSection: React.FC<{
|
||||
steps: Array<{ outputFiles?: Array<{ id: string; fileName?: string }> }>;
|
||||
unassignedFiles?: Array<{ id: string; fileName?: string }>;
|
||||
|
|
@ -1186,10 +1193,12 @@ const _ProducedFilesSection: React.FC<{
|
|||
const allFiles: Array<{ id: string; fileName?: string }> = [];
|
||||
for (const step of steps) {
|
||||
for (const f of step.outputFiles ?? []) {
|
||||
if (_isHiddenWorkflowArtifactFile(f)) continue;
|
||||
if (!seen.has(f.id)) { seen.add(f.id); allFiles.push(f); }
|
||||
}
|
||||
}
|
||||
for (const f of unassignedFiles ?? []) {
|
||||
if (_isHiddenWorkflowArtifactFile(f)) continue;
|
||||
if (!seen.has(f.id)) { seen.add(f.id); allFiles.push(f); }
|
||||
}
|
||||
if (!allFiles.length) return null;
|
||||
|
|
@ -1312,8 +1321,8 @@ const _WorkspaceTab: React.FC<_WorkspaceTabProps> = ({ runId, onBack }) => {
|
|||
{steps.map((step) => {
|
||||
const inputData = _stripFileRefKeys(step.inputSnapshot ?? {});
|
||||
const outputData = _stripFileRefKeys(step.output ?? {});
|
||||
const inputFiles = step.inputFiles ?? [];
|
||||
const outputFiles = step.outputFiles ?? [];
|
||||
const inputFiles = (step.inputFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
|
||||
const outputFiles = (step.outputFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
|
||||
const hasInput = inputData !== undefined || inputFiles.length > 0;
|
||||
const hasOutput = outputData !== undefined || outputFiles.length > 0;
|
||||
return (
|
||||
|
|
@ -1374,12 +1383,16 @@ const _WorkspaceTab: React.FC<_WorkspaceTabProps> = ({ runId, onBack }) => {
|
|||
})}
|
||||
</div>
|
||||
)}
|
||||
{unassignedFiles && unassignedFiles.length > 0 && (
|
||||
{(() => {
|
||||
const visibleUnassigned = (unassignedFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
|
||||
if (!visibleUnassigned.length) return null;
|
||||
return (
|
||||
<>
|
||||
<h4 style={{ margin: '1rem 0 0.5rem' }}>{t('Sonstige Dokumente')}</h4>
|
||||
<_FileLinkList files={unassignedFiles} />
|
||||
<_FileLinkList files={visibleUnassigned} />
|
||||
</>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue