31 KiB
Typed Node Handover System — Spezifikation & Execution Plan
Ansatz: Greenfield. Keine Rückwärtskompatibilität, keine Migration, kein Deprecation.
1 Problemstellung
Der Graphical Editor hat keine formale Typisierung der Daten zwischen Nodes. Jede Node produziert ein freiformiges Dict; die Folge-Node extrahiert heuristisch. Kern-Symptome:
actionNodeExecutor.py(~860 Zeilen, >50 % handover-Heuristik pro Node-Paar)outputPreviewRegistry.tshartcodiert Beispiel-Schemas, die von Laufzeit-Outputs abweichen- Keine
data.transform,data.filter,data.aggregate,flow.mergeimplementiert - Frontend baut pro Node-Typ eine eigene Config-Component (12+ Dateien)
- Keine System-Variablen (Timestamp, Datum, User, etc.)
Die Method/Action-Schicht hat bereits WorkflowActionParameter mit FrontendType, _validateType, _validateParameters. Dieses System wird für den Graphen übernommen und erweitert.
2 Architektur
2.1 Port-Schema (Kern-Datenmodell)
Jede Node deklariert typisierte Ports für Ein- und Ausgänge sowie Parameter für die Konfiguration:
┌──────────────────────────────────────────────────────────────────────┐
│ Node Definition (Beispiel: email.draftEmail) │
│ │
│ inputPorts: │
│ [0]: { accepts: [EmailDraft, AiResult] } │
│ │
│ outputPorts: │
│ [0]: { schema: ActionResult } │
│ │
│ parameters: │
│ connectionId: { type: str, frontendType: USER_CONNECTION } │
│ subject: { type: str, frontendType: TEXT } │
│ body: { type: str, frontendType: TEXTAREA } │
│ to: { type: List[str], frontendType: TEXT } │
└──────────────────────────────────────────────────────────────────────┘
2.2 Port-Schema Pydantic-Modelle
Neue Datei: gateway/modules/features/graphicalEditor/portTypes.py
class PortField(BaseModel):
name: str
type: str # str, int, bool, List[str], List[Document], Dict[str,Any]
description: Dict[str, str] # {en, de, fr}
required: bool = True
class PortSchema(BaseModel):
name: str # z.B. "EmailDraft", "AiResult", "Transit"
fields: List[PortField]
class InputPortDef(BaseModel):
accepts: List[str] # Liste akzeptierter Schema-Namen
class OutputPortDef(BaseModel):
schema: str # Schema-Name aus dem Katalog
dynamic: bool = False # True = Schema wird aus Parametern abgeleitet
deriveFrom: Optional[str] = None # Parameter-Name für dynamische Ableitung
2.3 Port-Typen-Katalog
| Port-Typ | Felder | Produziert von | Konsumiert von |
|---|---|---|---|
DocumentList |
documents: List[{name, data, mimeType, metadata}] |
sharepoint.readFile, sharepoint.downloadFile, ai.*, file.create, input.upload | ai.*, file.create, email.draftEmail, sharepoint.uploadFile |
FileList |
files: List[{url, name, path, size}] |
sharepoint.listFiles, sharepoint.findFile | sharepoint.readFile, sharepoint.downloadFile, flow.loop |
EmailDraft |
subject: str, body: str, to: List[str], cc?: List[str], attachments?: List[Document] |
ai.prompt (outputFormat=emailDraft) | email.draftEmail |
EmailList |
emails: List[{subject, from, to, body, date, attachments}] |
email.checkEmail, email.searchEmail | ai.prompt, flow.loop |
TaskList |
tasks: List[{id, name, status, url}] |
clickup.searchTasks, clickup.listTasks | flow.loop, clickup.updateTask |
TaskResult |
success: bool, taskId: str, task: {id, name, status} |
clickup.createTask, clickup.updateTask | flow.ifElse |
FormPayload |
payload: Dict[str, Any] (dynamisch) |
trigger.form, input.form | Alle (via Feld-Referenz) |
AiResult |
prompt: str, response: str, responseData?: Dict, context: str, documents: List[Document] |
ai.* | email.draftEmail, file.create, sharepoint.uploadFile |
BoolResult |
result: bool, reason?: str |
input.approval, input.confirmation | flow.ifElse |
TextResult |
text: str |
input.comment | ai.*, file.create |
LoopItem |
currentItem: Any, currentIndex: int, items: List[Any], count: int |
flow.loop (pro Iteration) | Loop-Body Nodes |
AggregateResult |
items: List[Any], count: int |
data.aggregate | Alle |
MergeResult |
inputs: Dict[int, Any], first: Any, merged: Dict |
flow.merge | Alle |
Jeder Port-Typ bekommt implizit zwei Meta-Felder: _success: bool und _error: str?. Wenn ein Node fehlschlägt, liefert der Normalizer { _success: false, _error: "...", ...restliche Felder leer/default }.
2.4 Transit-Typ
Flow-Control-Nodes transformieren keine Daten — sie routen. Transit bedeutet: Output-Schema = Input-Schema des Upstream-Produzenten.
Transit-Nodes produzieren ein Envelope:
{
"_transit": True,
"_meta": { "branch": 0, "conditionResult": True }, # Routing-Metadaten
"data": <upstream-output-unverändert>,
}
| Node | Output-Typ | _meta-Felder |
|---|---|---|
flow.ifElse |
Transit | branch: int, conditionResult: bool |
flow.switch |
Transit | match: int, value: str |
data.filter |
Transit | originalCount: int, filteredCount: int |
Transit-Auflösung (rekursiv): DataPicker und Engine folgen _transit-Kette bis zum echten Produzenten. Multi-Output-Ports (ifElse hat 2): Transit-Auflösung berücksichtigt sourceOutput-Index, beide Ports zeigen dasselbe Upstream-Schema.
flow.loop ist kein Transit — er transformiert eine Liste in LoopItem pro Iteration.
2.5 Dynamische Schemas
Nodes mit konfigurationsabhängigem Output (Formulare, data.transform):
{
"id": "input.form",
"outputPorts": {
0: {"schema": "FormPayload", "dynamic": True, "deriveFrom": "fields"},
},
}
Schema-Ableitung: die Engine und das Frontend leiten aus dem Parameter fields die konkreten Output-Felder ab. Jedes Formular-Feld erscheint als wählbares Attribut im DataPicker.
3 Handover-Mechanismen
3.1 Prioritätsreihenfolge
Wenn ein Parameter-Wert aus mehreren Quellen kommen kann:
DataRef/SystemVar/Static > Wire-Handover > Default
- Wire-Handover: Extraktor füllt Input-Felder aus dem Upstream-Output.
- DataRef/System/Static: überschreiben Wire-Werte (= explizite User-Wahl gewinnt).
- Defaults aus Parameter-Definition, falls noch leer.
3.2 Mechanismus 1: DataRef (Hauptmechanismus, ~80 %)
User wählt im DataPicker, welches Feld eines Vorgängers in welchen Parameter fließt:
email.draftEmail.parameters:
subject = DataRef { nodeId: "ai_1", path: ["responseData", "subject"] }
body = DataRef { nodeId: "ai_1", path: ["responseData", "body"] }
to = DataRef { nodeId: "form_1", path: ["payload", "recipient"] }
3.3 Mechanismus 2: Wire-Handover (Extraktoren)
Pro Input-Port-Typ ein Extraktor (~12 Funktionen, nicht N×M):
INPUT_EXTRACTORS: Dict[str, Callable] = {
"EmailDraft": _extractEmailDraft,
"DocumentList": _extractDocuments,
"TextResult": _extractText,
...
}
Extraktor kennt das Upstream-Schema (aus outputPorts) und greift Felder direkt ab — kein Raten mit Fallbacks.
3.4 Mechanismus 3: data.transform
Expliziter Konverter-Node für Fälle, wo DataRef und Extraktor nicht reichen.
3.5 Mechanismus 4: System-Variablen
Dritter DynamicValue-Typ neben ref und value:
SYSTEM_VARIABLES = {
"system.timestamp": { "type": "int", "description": "Unix timestamp (ms)" },
"system.date": { "type": "str", "description": "ISO date (YYYY-MM-DD)" },
"system.datetime": { "type": "str", "description": "ISO datetime" },
"system.time": { "type": "str", "description": "HH:MM:SS" },
"system.userId": { "type": "str", "description": "Aktueller User-ID" },
"system.userName": { "type": "str", "description": "Aktueller User-Name" },
"system.userEmail": { "type": "str", "description": "Aktueller User-Email" },
"system.workflowId": { "type": "str", "description": "Workflow-ID" },
"system.runId": { "type": "str", "description": "Run-ID" },
"system.instanceId": { "type": "str", "description": "Feature-Instanz-ID" },
"system.mandateId": { "type": "str", "description": "Mandant-ID" },
"system.loopIndex": { "type": "int", "description": "Aktueller Loop-Index (nur in Loop)" },
"system.loopCount": { "type": "int", "description": "Anzahl Loop-Items (nur in Loop)" },
"system.uuid": { "type": "str", "description": "Neue zufällige UUID" },
}
4 Generisches Frontend-Rendering
4.1 Prinzip: Jede UI-Darstellung ist ein FrontendType
Es gibt keine Node-spezifischen Config-Components. NODE_CONFIG_REGISTRY entfällt komplett.
4.2 FrontendType-Katalog
class FrontendType(str, Enum):
# Standard Types
TEXT = "text"
TEXTAREA = "textarea"
NUMBER = "number"
CHECKBOX = "checkbox"
DATE = "date"
DATETIME = "datetime"
EMAIL = "email"
SELECT = "select"
MULTISELECT = "multiselect"
JSON = "json"
FILE = "file"
HIDDEN = "hidden"
# Picker Types (API-backed)
USER_CONNECTION = "userConnection"
SHAREPOINT_FOLDER = "sharepointFolder"
SHAREPOINT_FILE = "sharepointFile"
CLICKUP_LIST = "clickupList"
CLICKUP_TASK = "clickupTask"
# Complex Structure Types
CASE_LIST = "caseList"
FIELD_BUILDER = "fieldBuilder"
KEY_VALUE_ROWS = "keyValueRows"
CRON = "cron"
CONDITION = "condition"
MAPPING_TABLE = "mappingTable"
FILTER_EXPRESSION = "filterExpression"
4.3 Parameter-Abhängigkeiten
Picker-Parameter die von anderen Parametern desselben Nodes abhängen:
{
"name": "path",
"type": "str",
"frontendType": "sharepointFolder",
"frontendOptions": {"dependsOn": "connectionId"},
}
Der generische Renderer übergibt den Wert des abhängigen Parameters. Der Picker rendert sich disabled, solange die Abhängigkeit nicht erfüllt ist.
4.4 Generischer Renderer
const FRONTEND_TYPE_RENDERERS: Record<string, ComponentType<FieldRendererProps>> = {
text: TextInput,
textarea: TextareaInput,
number: NumberInput,
checkbox: CheckboxInput,
date: DatePicker,
select: SelectInput,
multiselect: MultiSelectInput,
json: JsonEditor,
userConnection: ConnectionPicker,
sharepointFolder: FolderPicker,
clickupList: ClickUpListPicker,
caseList: CaseListEditor,
fieldBuilder: FieldBuilderEditor,
keyValueRows: KeyValueRowsEditor,
cron: CronBuilder,
condition: ConditionBuilder,
mappingTable: MappingTableEditor,
filterExpression: FilterExpressionEditor,
};
function GenericNodeConfig({ node, nodeType }) {
return nodeType.parameters.map(param => {
const Renderer = FRONTEND_TYPE_RENDERERS[param.frontendType] ?? TextInput;
return <Renderer key={param.name} param={param} value={...} />;
});
}
5 Neue Nodes
5.1 data.aggregate
{
"id": "data.aggregate",
"category": "data",
"label": {"en": "Aggregate", "de": "Sammeln", "fr": "Agréger"},
"parameters": [
{"name": "mode", "type": "str", "frontendType": "select",
"options": ["collect", "concat", "sum", "count"], "default": "collect"},
],
"inputPorts": {0: {"accepts": ["Transit"]}},
"outputPorts": {0: {"schema": "AggregateResult"}},
"executor": "data",
}
Engine-Semantik: Wenn data.aggregate im Loop-Body steht, wird ihr Output nicht pro Iteration überschrieben, sondern in _aggregateAccumulators[nodeId] gesammelt. Nach Loop-Ende: nodeOutputs[nodeId] = { items: akkumulator, count: len }.
5.2 data.transform
{
"id": "data.transform",
"category": "data",
"label": {"en": "Transform", "de": "Umwandeln", "fr": "Transformer"},
"parameters": [
{"name": "mappings", "type": "json", "frontendType": "mappingTable"},
],
"inputPorts": {0: {"accepts": ["Transit"]}},
"outputPorts": {0: {"schema": "Dict", "dynamic": True, "deriveFrom": "mappings"}},
"executor": "data",
}
5.3 data.filter
{
"id": "data.filter",
"category": "data",
"label": {"en": "Filter", "de": "Filtern", "fr": "Filtrer"},
"parameters": [
{"name": "condition", "type": "str", "frontendType": "filterExpression"},
],
"inputPorts": {0: {"accepts": ["AggregateResult", "FileList", "TaskList", "EmailList", "DocumentList"]}},
"outputPorts": {0: {"schema": "Transit"}},
"executor": "data",
}
Filter-Expression-Sprache: Vergleiche (==, !=, <, >, <=, >=), Logik (and, or, not), String (contains, startsWith), Null (isEmpty, isNotEmpty). Der FILTER_EXPRESSION-Renderer baut einen visuellen Condition-Builder (Dropdown Feld → Operator → Wert).
5.4 flow.merge
{
"id": "flow.merge",
"category": "flow",
"label": {"en": "Merge", "de": "Zusammenführen", "fr": "Fusionner"},
"parameters": [
{"name": "mode", "type": "str", "frontendType": "select",
"options": ["first", "all", "append"], "default": "first"},
],
"inputs": 2,
"outputs": 1,
"inputPorts": {0: {"accepts": ["Transit"]}, 1: {"accepts": ["Transit"]}},
"outputPorts": {0: {"schema": "MergeResult"}},
"executor": "flow",
"meta": {"icon": "mdi-call-merge", "color": "#FF9800"},
}
Engine-Semantik: flow.merge wird erst ausgeführt, wenn alle verbundenen Vorgänger verarbeitet sind. Für inaktive Branches (nach ifElse): der übersprungene Vorgänger ist nicht in nodeOutputs → wird als „nicht verfügbar" markiert. mode=first nimmt den ersten verfügbaren, mode=all merged nur die verfügbaren.
6 Entscheidungen
| # | Frage | Entscheidung |
|---|---|---|
| 1 | Typ-Mismatch bei Kante? | Soft — Warnung + visuelle Markierung, kein Hard-Block. |
| 2 | Transit statt Any für Flow-Nodes? | Ja. Transit-Envelope mit _meta und data. DataPicker resolved rekursiv. |
| 3 | Dynamische Schemas? | Ja. deriveFrom-Parameter → Schema-Ableitung. |
| 4 | Migration? | Nein. Greenfield. |
| 5 | data.aggregate separat? | Ja. Separater Node. |
| 6 | flow.merge? | Ja, sofort. Komplettes Set. |
| 7 | _paramMap Zukunft? |
Entfällt. Node-Parameter = Action-Parameter-Namen. UI-Labels separat in description. |
| 8 | Parameter vs Wire Priorität? | DataRef > Wire > Default. |
| 9 | Fehler-Output? | Jeder Port-Typ hat _success/_error Meta-Felder. |
| 10 | AI Structured Output? | AiResult.responseData: Dict + Parameter outputFormat (text/json). |
| 11 | Pause/Resume? | Resume-Handler validiert User-Eingabe gegen Output-Schema der pausierten Node. |
7 Execution Plan — Backend
7.1 NEU: gateway/modules/features/graphicalEditor/portTypes.py
Erstellen. Inhalt:
PortField,PortSchema,InputPortDef,OutputPortDef(Pydantic-Modelle, Abschnitt 2.2)PORT_TYPE_CATALOG: Dict aller PortSchemas (Abschnitt 2.3)SYSTEM_VARIABLES: Dict (Abschnitt 3.5)- Output-Normalizer:
_normalizeToSchema(raw: Dict, schemaName: str) -> Dict+ pro Port-Typ eine Funktion - Input-Extraktoren:
INPUT_EXTRACTORS: Dict[str, Callable](Abschnitt 3.3) - Transit-Envelope-Helpers:
_wrapTransit(data, meta),_unwrapTransit(output),_resolveTransitChain(nodeId, nodeOutputs, connectionMap) - Schema-Ableitungsfunktionen:
_deriveFormPayloadSchema(node),_deriveTransformSchema(node)(Abschnitt 2.5)
7.2 ÄNDERN: gateway/modules/shared/frontendTypes.py
Aktuell: FrontendType Enum mit 14 Werten (Zeilen 16–63).
Änderung: Erweitern um Complex Structure Types:
Hinzufügen:
SHAREPOINT_FILE = "sharepointFile"
CLICKUP_LIST = "clickupList"
CLICKUP_TASK = "clickupTask"
CASE_LIST = "caseList"
FIELD_BUILDER = "fieldBuilder"
KEY_VALUE_ROWS = "keyValueRows"
CRON = "cron"
CONDITION = "condition"
MAPPING_TABLE = "mappingTable"
FILTER_EXPRESSION = "filterExpression"
CUSTOM_TYPE_OPTIONS_API und CUSTOM_TYPE_DESCRIPTIONS entsprechend erweitern.
7.3 NEU SCHREIBEN: gateway/modules/features/graphicalEditor/nodeDefinitions/*.py
Alle 10 Dateien (triggers.py, flow.py, input.py, ai.py, email.py, sharepoint.py, clickup.py, file.py, trustee.py, __init__.py) komplett neu.
Jede Node-Definition bekommt:
inputPorts: Dict mitInputPortDefpro Port-IndexoutputPorts: Dict mitOutputPortDefpro Port-Indexparameters: Liste von Dicts imWorkflowActionParameter-Format (mitfrontendType,frontendOptionsinkl.dependsOn)- Kein
_paramMapmehr — Parameter-Namen = Action-Parameter-Namen _methodund_actionbleiben (für ActionExecutor-Mapping)
NEU: data.py mit data.aggregate, data.transform, data.filter.
__init__.py: STATIC_NODE_TYPES um DATA_NODES erweitern; flow.py um flow.merge erweitern.
7.4 ÄNDERN: gateway/modules/features/graphicalEditor/nodeRegistry.py
Funktion getNodeTypesForApi (Zeile 54–75):
_localizeNodeanpassen:inputPortsundoutputPortsin API-Response einschließen (nicht als_-Prefix, also nicht wegstrippen)- Response erweitern um
systemVariablesausportTypes.SYSTEM_VARIABLES - Response erweitern um
portTypeCatalogausportTypes.PORT_TYPE_CATALOG
Funktion getNodeTypeToMethodAction (Zeile 78–89): bleibt, _method/_action weiterhin verwendet.
7.5 ÄNDERN: gateway/modules/workflows/automation2/graphUtils.py
Funktion resolveParameterReferences (Zeile 185–243):
- Neuen Case hinzufügen für
type: "system":
if isinstance(value, dict) and value.get("type") == "system":
return _resolveSystemVariable(value["variable"], context)
_resolveSystemVariable(variable, context)als neue Funktion (Abschnitt 3.5).
Funktion validateGraph (Zeile 86–120): erweitern um Port-Kompatibilitäts-Check (soft — Warnings sammeln, nicht hart ablehnen).
7.6 NEU SCHREIBEN: gateway/modules/workflows/automation2/executors/actionNodeExecutor.py
Komplett neu. Die ~860 Zeilen mit heuristischen Merge-Funktionen (_extractEmailContentFromUpstream, _getContextFromUpstream, _gatherAttachmentDocumentsFromUpstream, _formatEmailOutputAsContext, _unpackIncomingEmail, _getIncomingEmailFromUpstream, _buildActionParams, _paramMap-Logik, etc.) werden ersetzt durch:
- Node-Definition laden (
_getNodeDefinition) - Parameter per
resolveParameterReferencesauflösen (DataRef, SystemVar, Static) - Wire-Handover: wenn
inputSources[nodeId][0]existiert, Extraktor ausINPUT_EXTRACTORSaufrufen - Priorität anwenden (DataRef > Wire > Default)
ActionExecutor.executeAction(method, action, params)aufrufen- Ergebnis durch
_normalizeToSchema(result, outputSchema)normalisieren - Fehler-Envelope: bei Exception
{ _success: false, _error: str, ...defaults }zurückgeben
Keine node-type-spezifische Logik mehr im Executor.
7.7 NEU: gateway/modules/workflows/automation2/executors/dataExecutor.py
Neuer Executor für data.aggregate, data.transform, data.filter:
data.aggregate: im Loop-Kontext akkumuliert Ergebnisse (Modus: collect, concat, sum, count)data.transform: wendetmappingsan (jedes Mapping löst eine DataRef/Static-Referenz auf)data.filter: evaluiertconditionpro Item der Input-Liste, gibt gefilterte Liste zurück
7.8 ÄNDERN: gateway/modules/workflows/automation2/executors/flowExecutor.py
Funktion _ifElse (Zeile 55–65): Output ändern zu Transit-Envelope:
# Heute:
return {"branch": 0 if ok else 1, "conditionResult": ok, "input": inp}
# Neu:
return {"_transit": True, "_meta": {"branch": 0 if ok else 1, "conditionResult": ok}, "data": inp}
Funktion _switch (Zeile 199–207): analog Transit-Envelope.
Funktion _loop (Zeile 261–272): bleibt wie bisher (kein Transit, produziert LoopItem).
NEU: _merge Funktion: sammelt Outputs aller verbundenen Input-Ports aus nodeOutputs, wendet Modus an (first/all/append), gibt MergeResult zurück.
7.9 ÄNDERN: gateway/modules/workflows/automation2/executors/__init__.py
DataExecutor importieren und exportieren.
7.10 ÄNDERN: gateway/modules/workflows/automation2/executionEngine.py
Funktion _getExecutor (Zeile 71–85): Case für data.* hinzufügen → DataExecutor.
Funktion _is_node_on_active_path (Zeile 39–68): Transit-Envelope berücksichtigen — branch/match jetzt in out["_meta"] statt direkt in out.
Loop-Body-Verarbeitung (Zeile 400–460): Spezialbehandlung für data.aggregate Nodes: statt nodeOutputs[bnid] = result ein _aggregateAccumulators[bnid].append(result). Nach Loop-Ende: nodeOutputs[aggregateNodeId] = { items: akkumulator, count: len }.
Nach jedem Execute (Zeile 390+): _normalizeToSchema(result, outputSchema) aufrufen bevor in nodeOutputs gespeichert.
flow.merge Wartelogik: merge-Nodes werden von topoSort automatisch nach ihren Vorgängern eingeordnet (BFS). Vor Ausführung: prüfe ob alle verbundenen Vorgänger in nodeOutputs sind oder übersprungen wurden (inaktiver Branch → not in nodeOutputs und Node wurde als skipped geloggt).
Pause/Resume: Resume-Handler (initialNodeOutputs) — Validierung der User-Eingabe gegen Output-Schema der pausierten Node (in executeGraph nach Empfang von initialNodeOutputs).
7.11 ÄNDERN: gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py
Funktion get_node_types (Zeile 146–168): Response-Struktur erweitern:
# Heute:
return {"nodeTypes": localized, "categories": categories}
# Neu:
return {
"nodeTypes": localized, # mit inputPorts, outputPorts
"categories": categories,
"portTypeCatalog": PORT_TYPE_CATALOG, # alle Schemas
"systemVariables": SYSTEM_VARIABLES,
}
8 Execution Plan — Frontend
8.1 ÄNDERN: frontend_nyla/src/api/workflowApi.ts
Interface NodeTypeParameter (Zeile 14–20): erweitern um frontendType, frontendOptions, options, validation.
Interface NodeType (Zeile 22–39): erweitern um inputPorts, outputPorts.
Interface NodeTypesResponse (Zeile 46–49): erweitern um portTypeCatalog, systemVariables.
8.2 LÖSCHEN + NEU: frontend_nyla/src/components/FlowEditor/nodes/configs/
Löschen: index.ts, AiNodeConfig.tsx, EmailNodeConfig.tsx, SharePointNodeConfig.tsx, ClickUpNodeConfig.tsx, ApprovalNodeConfig.tsx, UploadNodeConfig.tsx, CommentNodeConfig.tsx, ReviewNodeConfig.tsx, SelectionNodeConfig.tsx, ConfirmationNodeConfig.tsx, FileCreateNodeConfig.tsx, TrusteeNodeConfig.tsx, types.ts.
Neu: frontendTypeRenderers/ Ordner mit einem Renderer pro FrontendType (TextInput, TextareaInput, NumberInput, CheckboxInput, DatePicker, SelectInput, MultiSelectInput, JsonEditor, ConnectionPicker, FolderPicker, ClickUpListPicker, CaseListEditor, FieldBuilderEditor, KeyValueRowsEditor, CronBuilder, ConditionBuilder, MappingTableEditor, FilterExpressionEditor).
Neu: frontendTypeRenderers/index.ts mit FRONTEND_TYPE_RENDERERS Registry.
Bestehende Logik aus den gelöschten Config-Components (z.B. ConnectionPicker aus EmailNodeConfig, CaseEditor aus SwitchNodeConfig, FieldBuilder aus FormNodeConfig) wird in die entsprechenden FrontendType-Renderer extrahiert.
8.3 ÄNDERN: frontend_nyla/src/components/FlowEditor/editor/NodeConfigPanel.tsx
Komplett neu. Statt NODE_CONFIG_REGISTRY-Lookup:
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
// Iteriere nodeType.parameters, rendere pro Parameter den passenden Renderer
nodeType.parameters.map(param => {
const Renderer = FRONTEND_TYPE_RENDERERS[param.frontendType] ?? TextInput;
return <Renderer param={param} value={...} onChange={...} />;
});
8.4 LÖSCHEN: frontend_nyla/src/components/FlowEditor/nodes/shared/outputPreviewRegistry.ts
Komplett entfernen. Ersetzen durch generische Funktion die aus dem portTypeCatalog + outputPorts Schema den Preview-Baum baut:
function buildPreviewFromSchema(
node: CanvasNode,
nodeTypes: NodeType[],
portTypeCatalog: Record<string, PortSchema>
): Record<string, unknown> {
const nt = nodeTypes.find(t => t.id === node.type);
const outputSchema = nt?.outputPorts?.[0]?.schema;
if (outputSchema === 'Transit') return {}; // resolved dynamisch
const schema = portTypeCatalog[outputSchema];
// Generiere Beispielwerte pro Feld aus schema.fields
}
8.5 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/dataRef.ts
DynamicValue Union (Zeile 20): erweitern um SystemVarRef:
interface SystemVarRef {
type: 'system';
variable: string;
}
type DynamicValue = DataRef | DataValue | SystemVarRef;
isRef, isValue etc. ergänzen um isSystemVar Type Guard.
8.6 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx
buildPickablePaths(Zeile 20–40): Schema-basiert statt aus Preview-Daten. ErhältportTypeCatalogund baut Baum aus Schema-Feldern.- Transit-Auflösung: wenn Output-Schema = Transit, folge connectionMap rückwärts bis zum echten Produzenten.
- Neue Sektion „System" mit allen Variablen aus
systemVariables, gruppiert (Datum/Zeit, User, Workflow). - Bei Pick eines System-Werts:
onPickliefertSystemVarRefstattDataRef.
8.7 ÄNDERN: frontend_nyla/src/components/FlowEditor/context/Automation2DataFlowContext.tsx
Automation2DataFlowContextValue(Zeile 10–19): erweitern umportTypeCatalog,systemVariables.nodeOutputsPreviewBerechnung: aus Schema statt ausoutputPreviewRegistry.
8.8 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/graphUtils.ts
fromApiGraph / toApiGraph: inputPorts und outputPorts mitserialisieren in CanvasNode.
8.9 ÄNDERN: frontend_nyla/src/components/FlowEditor/editor/FlowCanvas (Verbindungs-Validierung)
Beim Verbinden zweier Ports: prüfe sourceNode.outputPorts[outputIdx].schema gegen targetNode.inputPorts[inputIdx].accepts. Bei Mismatch: Kante gelb/orange markieren (Soft-Warnung).
9 Dateien-Übersicht
Neue Dateien
| Datei | Inhalt |
|---|---|
gateway/.../graphicalEditor/portTypes.py |
PortSchema, Katalog, Normalizer, Extraktoren, System-Variablen |
gateway/.../automation2/executors/dataExecutor.py |
Executor für data.aggregate, data.transform, data.filter |
gateway/.../nodeDefinitions/data.py |
Node-Definitionen: data.aggregate, data.transform, data.filter |
frontend_nyla/.../nodes/frontendTypeRenderers/index.ts |
FRONTEND_TYPE_RENDERERS Registry |
frontend_nyla/.../nodes/frontendTypeRenderers/*.tsx |
Ein Renderer pro FrontendType |
Komplett neu geschriebene Dateien
| Datei | Grund |
|---|---|
gateway/.../automation2/executors/actionNodeExecutor.py |
Heuristische Merge-Logik → Normalizer + Extraktor |
gateway/.../nodeDefinitions/triggers.py |
+ inputPorts, outputPorts, frontendType |
gateway/.../nodeDefinitions/flow.py |
+ flow.merge, Transit-Ports |
gateway/.../nodeDefinitions/input.py |
+ dynamische FormPayload-Schemas |
gateway/.../nodeDefinitions/ai.py |
+ AiResult-Ports, outputFormat-Param |
gateway/.../nodeDefinitions/email.py |
+ EmailDraft/EmailList-Ports |
gateway/.../nodeDefinitions/sharepoint.py |
+ FileList/DocumentList-Ports |
gateway/.../nodeDefinitions/clickup.py |
+ TaskList/TaskResult-Ports |
gateway/.../nodeDefinitions/file.py |
+ DocumentList-Ports |
gateway/.../nodeDefinitions/trustee.py |
+ Ports |
gateway/.../nodeDefinitions/__init__.py |
+ DATA_NODES Import |
frontend_nyla/.../editor/NodeConfigPanel.tsx |
Generischer Renderer statt NODE_CONFIG_REGISTRY |
Geänderte Dateien
| Datei | Änderung |
|---|---|
gateway/.../shared/frontendTypes.py |
FrontendType Enum erweitern (+10 Werte) |
gateway/.../automation2/graphUtils.py |
resolveParameterReferences + SystemVar, validateGraph + Port-Check |
gateway/.../automation2/executors/flowExecutor.py |
ifElse/switch → Transit-Envelope, + _merge |
gateway/.../automation2/executors/__init__.py |
+ DataExecutor Export |
gateway/.../automation2/executionEngine.py |
+ DataExecutor, Transit-_meta, aggregate-Akkumulator, merge-Wartelogik |
gateway/.../graphicalEditor/nodeRegistry.py |
API-Response + portTypeCatalog, systemVariables |
gateway/.../graphicalEditor/routeFeatureGraphicalEditor.py |
Response-Struktur erweitern |
frontend_nyla/.../api/workflowApi.ts |
Interfaces erweitern (inputPorts, outputPorts, frontendType) |
frontend_nyla/.../nodes/shared/dataRef.ts |
+ SystemVarRef |
frontend_nyla/.../nodes/shared/DataPicker.tsx |
Schema-basiert, Transit-Auflösung, System-Sektion |
frontend_nyla/.../nodes/shared/graphUtils.ts |
inputPorts/outputPorts serialisieren |
frontend_nyla/.../context/Automation2DataFlowContext.tsx |
+ portTypeCatalog, systemVariables |
frontend_nyla/.../editor/FlowCanvas |
Verbindungs-Validierung |
Gelöschte Dateien
| Datei | Grund |
|---|---|
frontend_nyla/.../nodes/configs/index.ts |
Ersetzt durch FRONTEND_TYPE_RENDERERS |
frontend_nyla/.../nodes/configs/AiNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/EmailNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/SharePointNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/ClickUpNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/ApprovalNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/UploadNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/CommentNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/ReviewNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/SelectionNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/ConfirmationNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/FileCreateNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/configs/TrusteeNodeConfig.tsx |
Generischer Renderer |
frontend_nyla/.../nodes/shared/outputPreviewRegistry.ts |
Schema-basierte Preview |